Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
313 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships1/**
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.2 * Memory, as a project's and a workspace's pages show it: what coding agents
3 * (with the MCP server's remember) and people saved, each with where it came
4 * from, to pin, edit or forget.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships5 * Both pages post to their own action with the intents in `memoryAction`.
6 */
7import { Bot, Brain, Pencil, Pin, PinOff, Trash2, User } from "lucide-react";
8import { useEffect, useRef, useState } from "react";
9import { Link, useFetcher } from "react-router";
10
11import { MEMORY_KINDS, type Memory, type MemoryKind, type MemoryScope, type Result, type User as Actor } from "@g1t/contracts";
12
13import { SubmitButton, TimeAgo } from "./ui";
14import {
15 AlertDialog,
16 AlertDialogAction,
17 AlertDialogCancel,
18 AlertDialogContent,
19 AlertDialogDescription,
20 AlertDialogFooter,
21 AlertDialogHeader,
22 AlertDialogTitle,
23 AlertDialogTrigger,
24} from "./ui/alert-dialog";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.25import { Button } from "./ui/button";
26import { Card } from "./ui/card";
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships27import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
28import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
Merge branch 'worktree-agent-ae1299e92e4462012'29import { Hint } from "./ui/hint";
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships30import { Switch } from "./ui/switch";
31
32const KIND: Record<MemoryKind, { label: string; about: string; tone: string }> = {
33 fact: { label: "Fact", about: "How something is", tone: "text-info border-info/40" },
34 convention: { label: "Convention", about: "How things are done here", tone: "text-accent border-accent/40" },
35 decision: { label: "Decision", about: "Something chosen, and why", tone: "text-merged border-merged/40" },
36 gotcha: { label: "Gotcha", about: "A trap to avoid", tone: "text-warn border-warn/40" },
37};
38
39const TEXTAREA =
40 "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim";
41
42type Done = { ok: boolean; error?: string } | undefined;
43
44function KindBadge({ kind }: { kind: MemoryKind }) {
45 return (
46 <span className={`inline-flex shrink-0 items-center rounded-full border px-2 py-px text-[0.6875rem] ${KIND[kind].tone}`}>
47 {KIND[kind].label}
48 </span>
49 );
50}
51
52function KindSelect({ value }: { value?: MemoryKind }) {
53 return (
54 <Select name="kind" defaultValue={value ?? "fact"}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit55 <SelectTrigger size="sm" className="w-40" aria-label="Kind of memory">
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships56 <SelectValue />
57 </SelectTrigger>
58 <SelectContent>
59 {MEMORY_KINDS.map((kind) => (
60 <SelectItem key={kind} value={kind} description={KIND[kind].about}>
61 {KIND[kind].label}
62 </SelectItem>
63 ))}
64 </SelectContent>
65 </Select>
66 );
67}
68
69/** Where a memory came from, linked where it can be. */
70function Source({ memory }: { memory: Memory }) {
71 const { source } = memory;
72 const repo = source.repo;
73 const where =
74 repo && source.number != null ? (
75 <>
76 {" "}
77 on{" "}
78 <Link to={`/${repo.namespace}/${repo.name}/pull/${source.number}`} className="hover:text-fg">
79 {memory.scope === "workspace" && `${repo.name}`}#{source.number}
80 </Link>
81 </>
82 ) : repo && memory.scope === "workspace" ? (
83 <>
84 {" "}
85 in <Link to={`/${repo.namespace}/${repo.name}`} className="hover:text-fg">{repo.name}</Link>
86 </>
87 ) : null;
88 if (source.kind === "person") {
89 return (
90 <span className="inline-flex items-center gap-1">
91 <User size={11} />
92 {memory.createdBy}
93 {where}
94 </span>
95 );
96 }
97 return (
98 <span className="inline-flex items-center gap-1">
99 <Bot size={11} />
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.100 {memory.createdBy}
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships101 {where}
102 </span>
103 );
104}
105
106function EditMemory({ memory, action }: { memory: Memory; action: string }) {
107 const fetcher = useFetcher<Done>();
108 const [open, setOpen] = useState(false);
109 useEffect(() => {
110 if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false);
111 }, [fetcher.state, fetcher.data]);
112 return (
113 <Dialog open={open} onOpenChange={setOpen}>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar114 <DialogTrigger aria-label="Edit" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-fg">
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships115 <Pencil size={13} />
116 </DialogTrigger>
117 <DialogContent>
118 <DialogHeader>
119 <DialogTitle>Edit memory</DialogTitle>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.120 <DialogDescription>Agents that recall it get the new wording from now on.</DialogDescription>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships121 </DialogHeader>
122 <fetcher.Form method="post" action={action} className="space-y-3">
123 <input type="hidden" name="intent" value="update" />
124 <input type="hidden" name="id" value={memory.id} />
125 <textarea name="text" required rows={4} maxLength={1000} defaultValue={memory.text} className={TEXTAREA} />
126 <div className="flex items-center justify-between gap-3">
127 <KindSelect value={memory.kind} />
128 <SubmitButton fetcher={fetcher} pending="Saving…">
129 Save
130 </SubmitButton>
131 </div>
132 {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>}
133 </fetcher.Form>
134 </DialogContent>
135 </Dialog>
136 );
137}
138
139function MemoryItem({ memory, action, editable }: { memory: Memory; action: string; editable: boolean }) {
140 const fetcher = useFetcher<Done>();
141 const pinned = fetcher.formData ? fetcher.formData.get("pinned") === "true" : memory.pinned;
142 const gone = fetcher.formData?.get("intent") === "delete";
143 if (gone) return null;
144 return (
145 <li className="group flex gap-3 px-4 py-3">
146 <div className="min-w-0 grow">
147 <div className="flex flex-wrap items-center gap-2">
148 <KindBadge kind={memory.kind} />
149 {pinned && (
150 <span className="inline-flex items-center gap-1 text-[0.6875rem] text-accent">
151 <Pin size={11} />
152 pinned
153 </span>
154 )}
155 </div>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar156 <p className="mt-1.5 text-sm whitespace-pre-wrap wrap-break-word">{memory.text}</p>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships157 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-faint">
158 <Source memory={memory} />
159 <span>
160 added <TimeAgo at={memory.createdAt} />
161 </span>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.162 <span>{memory.lastUsedAt ? <>recalled <TimeAgo at={memory.lastUsedAt} /></> : "not recalled yet"}</span>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships163 </p>
164 {fetcher.data?.error && <p className="mt-1.5 text-xs text-danger">{fetcher.data.error}</p>}
165 </div>
166 {editable && (
167 <div className="flex shrink-0 items-start gap-0.5">
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.168 <Hint label={pinned ? "Unpin" : "Pin: recalled first"}>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.169 <Button
Merge branch 'worktree-agent-ae1299e92e4462012'170 type="button"
171 aria-label={pinned ? "Unpin" : "Pin"}
172 onClick={() => fetcher.submit({ intent: "update", id: memory.id, pinned: String(!pinned) }, { method: "post", action })}
173 disabled={fetcher.state !== "idle"}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.174 variant="ghost"
175 size="inline"
176 className="rounded p-1 text-faint max-sm:p-2.5"
Merge branch 'worktree-agent-ae1299e92e4462012'177 >
178 {pinned ? <PinOff size={13} /> : <Pin size={13} />}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.179 </Button>
Merge branch 'worktree-agent-ae1299e92e4462012'180 </Hint>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships181 <EditMemory memory={memory} action={action} />
182 <AlertDialog>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar183 <AlertDialogTrigger aria-label="Forget" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-danger">
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships184 <Trash2 size={13} />
185 </AlertDialogTrigger>
186 <AlertDialogContent>
187 <AlertDialogHeader>
188 <AlertDialogTitle>Forget this?</AlertDialogTitle>
189 <AlertDialogDescription>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.190 No agent recalls it again. This cannot be undone.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships191 </AlertDialogDescription>
192 </AlertDialogHeader>
193 <AlertDialogFooter>
194 <AlertDialogCancel>Keep it</AlertDialogCancel>
195 <AlertDialogAction onClick={() => fetcher.submit({ intent: "delete", id: memory.id }, { method: "post", action })}>
196 Forget
197 </AlertDialogAction>
198 </AlertDialogFooter>
199 </AlertDialogContent>
200 </AlertDialog>
201 </div>
202 )}
203 </li>
204 );
205}
206
207export function MemoryList({
208 memories,
209 action,
210 editable,
211 empty,
212}: {
213 memories: Memory[];
214 action: string;
215 editable: boolean;
216 empty: string;
217}) {
218 if (memories.length === 0) {
219 return <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">{empty}</p>;
220 }
221 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.222 <Card asChild divided>
223 <ul>
224 {memories.map((memory) => (
225 <MemoryItem key={memory.id} memory={memory} action={action} editable={editable} />
226 ))}
227 </ul>
228 </Card>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships229 );
230}
231
232/** A form to add a memory at one level. */
233export function AddMemory({ scope, action, placeholder }: { scope: MemoryScope; action: string; placeholder: string }) {
234 const fetcher = useFetcher<Done>();
235 const form = useRef<HTMLFormElement>(null);
236 useEffect(() => {
237 if (fetcher.state === "idle" && fetcher.data?.ok) form.current?.reset();
238 }, [fetcher.state, fetcher.data]);
239 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.240 <Card asChild className="p-4">
241 <fetcher.Form ref={form} method="post" action={action}>
242 <input type="hidden" name="intent" value="add" />
243 <input type="hidden" name="scope" value={scope} />
244 <label className="flex items-center gap-2 text-sm font-medium">
245 <Brain size={15} className="text-accent" />
246 Add to {scope === "workspace" ? "the workspace's" : "this project's"} memory
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships247 </label>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.248 <textarea name="text" required rows={2} maxLength={1000} placeholder={placeholder} className={`${TEXTAREA} mt-3`} />
249 <div className="mt-3 flex flex-wrap items-center gap-3">
250 <KindSelect />
251 <label className="flex items-center gap-2 text-xs text-muted">
252 <Switch name="pinned" value="true" size="sm" />
253 Pin it
254 </label>
255 <span className="grow" />
256 <SubmitButton fetcher={fetcher} pending="Adding…">
257 Add
258 </SubmitButton>
259 </div>
260 {fetcher.data?.error && <p className="mt-2 text-sm text-danger">{fetcher.data.error}</p>}
261 <p className="mt-2 text-xs text-faint">
262 One fact, convention, decision or gotcha each. Never a secret: say where it lives instead, and
263 g1t refuses anything that looks like a key or a token.
264 </p>
265 </fetcher.Form>
266 </Card>
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships267 );
268}
269
270type MemoryApi = {
271 addMemory(actor: Actor, workspace: string, memory: { scope: MemoryScope; repo?: { namespace: string; name: string } | null; text: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>;
272 updateMemory(actor: Actor, workspace: string, id: string, change: { text?: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>;
273 deleteMemory(actor: Actor, workspace: string, id: string): Promise<Result<boolean>>;
274};
275
276/** The action both memory pages share: add, update and delete. */
277export async function memoryAction(
278 api: MemoryApi,
279 actor: Actor,
280 workspace: string,
281 repo: { namespace: string; name: string } | null,
282 form: FormData,
283): Promise<Done> {
284 const intent = String(form.get("intent") ?? "");
285 const kindValue = String(form.get("kind") ?? "");
286 const kind = MEMORY_KINDS.includes(kindValue as MemoryKind) ? (kindValue as MemoryKind) : undefined;
287 if (intent === "add") {
288 const scope: MemoryScope = form.get("scope") === "workspace" ? "workspace" : "project";
289 const added = await api.addMemory(actor, workspace, {
290 scope,
291 repo,
292 text: String(form.get("text") ?? ""),
293 kind,
294 pinned: form.get("pinned") === "true",
295 });
296 return added.ok ? { ok: true } : { ok: false, error: added.error.message };
297 }
298 if (intent === "update") {
299 const pinned = form.get("pinned");
300 const text = form.get("text");
301 const changed = await api.updateMemory(actor, workspace, String(form.get("id") ?? ""), {
302 text: text == null ? undefined : String(text),
303 kind,
304 pinned: pinned == null ? undefined : pinned === "true",
305 });
306 return changed.ok ? { ok: true } : { ok: false, error: changed.error.message };
307 }
308 if (intent === "delete") {
309 const gone = await api.deleteMemory(actor, workspace, String(form.get("id") ?? ""));
310 return gone.ok ? { ok: true } : { ok: false, error: gone.error.message };
311 }
312 return { ok: false, error: "Unknown action." };
313}

This file's history is long; its oldest lines are credited to the oldest commit read.