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
334 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.

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.1import { Check, Copy, LoaderCircle } from "lucide-react";
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md2import { type ComponentProps, Fragment, type ReactNode, useState } from "react";
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.3import { Link, type LinkProps, NavLink, useLocation, useNavigation } from "react-router";
The docs' Copy page menu keeps its own spacing, with an icon per item4
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.5import { cn } from "../../lib/cn";
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.6import { linkPaths } from "../../lib/compute";
Merge branch 'worktree-agent-a985c01f43538af01'7import { type Submission, isPending } from "../../lib/pending";
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.8import { Button, type ButtonSize, type ButtonVariant, buttonVariants } from "./button";
The docs' Copy page menu keeps its own spacing, with an icon per item9
10export function Field({
11 label,
12 hint,
13 children,
14}: {
15 label: string;
16 hint?: string;
17 children: ReactNode;
18}) {
19 return (
20 <label className="block">
21 <span className="mb-1.5 block text-sm font-medium text-muted">
22 {label}
23 </span>
24 {children}
25 {hint && <span className="mt-1.5 block text-xs text-faint">{hint}</span>}
26 </label>
27 );
28}
29
30const CONTROL =
31 "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";
32
33/**
34 * Attributes for a field that is not part of signing in. Password managers
35 * guess from names such as "name", "title" and "username" and offer to
36 * fill a login into them; these tell 1Password, LastPass, Bitwarden and
37 * Dashlane to leave the field alone. A field that states what it takes with
38 * `autoComplete` is left to them.
39 */
40export function notACredential(autoComplete?: string) {
41 if (autoComplete) return {};
42 return {
43 autoComplete: "off",
44 "data-1p-ignore": true,
45 "data-lpignore": "true",
46 "data-bwignore": true,
47 "data-form-type": "other",
48 };
49}
50
51export function Input(props: ComponentProps<"input">) {
52 return <input {...notACredential(props.autoComplete)} {...props} className={CONTROL} />;
53}
54
55export function Textarea(props: ComponentProps<"textarea">) {
56 return (
57 <textarea
58 {...notACredential(props.autoComplete)}
59 {...props}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit60 className={`${CONTROL} font-mono placeholder:font-sans`}
The docs' Copy page menu keeps its own spacing, with an icon per item61 />
62 );
63}
64
65/** One tab in the row under a repository's or a workspace's header. */
66export function TabLink({
67 to,
68 also,
69 end,
70 icon,
71 count,
72 children,
73}: {
74 to: string;
75 /** Another path prefix under which this tab is the current one. */
76 also?: string;
77 end?: boolean;
78 icon: ReactNode;
79 count?: number;
80 children: ReactNode;
81}) {
82 const { pathname } = useLocation();
83 return (
84 <NavLink
85 to={to}
86 end={end}
87 className={({ isActive }) =>
88 `-mb-px flex items-center gap-2 border-b-2 px-3 pb-3 text-sm whitespace-nowrap transition-colors ${
89 isActive || (also && pathname.startsWith(also + "/"))
90 ? "border-accent font-medium text-fg"
91 : "border-transparent text-muted hover:text-fg"
92 }`
93 }
94 >
95 {icon}
96 {children}
97 {count != null && count > 0 && (
98 <span className="rounded-full bg-raised px-1.5 py-px text-xs text-muted">
99 {count}
100 </span>
101 )}
102 </NavLink>
103 );
104}
105
Usage, Billing settings and prepaid AI credit; fixes from the UX audit106/** Soon, beside something that is coming: the same small pill in the sidebar and in tabs. */
107export function SoonPill() {
108 return (
109 <span className="shrink-0 rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line">
110 Soon
111 </span>
112 );
113}
The docs' Copy page menu keeps its own spacing, with an icon per item114
Merge branch 'worktree-agent-a985c01f43538af01'115/**
116 * Whether the submission `fields` names is still working (lib/pending.ts):
117 * the page's own navigation, or `fetcher`'s when the form is a fetcher's.
118 */
119export function usePending(fields?: Record<string, string | null | undefined>, fetcher?: Submission): boolean {
120 const navigation = useNavigation();
121 return isPending(fetcher ?? navigation, fields);
122}
123
124/**
125 * A form's submit button that says it is working: turned off, with a
126 * spinner and `pending` ("Saving…") in place of its words, from the moment
127 * it is pressed until the page has loaded what it changed. Its own
128 * `name`/`value` say which submission is its; `match` names it otherwise,
129 * such as the form's hidden `intent`. `fetcher` when the form is a
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.130 * fetcher's. It is drawn as the Button in ./button, with its `variant` and
131 * `size`; `icon` says the spinner takes the place of everything inside
132 * it. Words with a leading icon should pass `pending`, so the spinner
133 * replaces both.
Merge branch 'worktree-agent-a985c01f43538af01'134 */
135export function SubmitButton({
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.136 variant,
137 size,
Merge branch 'worktree-agent-a985c01f43538af01'138 pending,
139 match,
140 fetcher,
141 busy,
142 icon,
143 disabled,
144 className,
145 children,
146 ...props
147}: Omit<ComponentProps<"button">, "type"> & {
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.148 variant?: ButtonVariant;
149 size?: ButtonSize;
Merge branch 'worktree-agent-a985c01f43538af01'150 /** The words while it works, such as "Saving…"; its own words when absent. */
151 pending?: ReactNode;
152 match?: Record<string, string | null | undefined>;
153 fetcher?: Submission;
154 /** Working for a reason this button cannot see by itself. */
155 busy?: boolean;
156 /** An icon button: while it works, the spinner is all it shows. */
157 icon?: boolean;
158}) {
159 const own =
160 typeof props.name === "string" && props.value != null ? { [props.name]: String(props.value) } : undefined;
161 const working = usePending({ ...own, ...match }, fetcher) || Boolean(busy);
162 return (
163 <button
164 {...props}
165 type="submit"
166 disabled={disabled || working}
167 aria-busy={working || undefined}
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.168 data-slot="button"
169 className={cn(buttonVariants({ variant, size }), className)}
Merge branch 'worktree-agent-a985c01f43538af01'170 >
171 {working ? (
172 <>
173 <LoaderCircle size={14} aria-hidden="true" className="shrink-0 animate-spin" />
174 {icon ? null : (pending ?? children)}
175 </>
176 ) : (
177 children
178 )}
179 </button>
180 );
181}
182
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.183/** A link that looks like the Button in ./button, with its `variant` and `size`. */
184export function ButtonLink({ variant, size, className, ...props }: LinkProps & { variant?: ButtonVariant; size?: ButtonSize }) {
185 return <Link data-slot="button" {...props} className={cn(buttonVariants({ variant, size }), className)} />;
The docs' Copy page menu keeps its own spacing, with an icon per item186}
187
188/**
189 * A message with the g1t pages it names as links, such as the billing page
190 * a refusal to start compute points to.
191 */
192export function Linked({ text }: { text: string }) {
193 return (
194 <>
195 {linkPaths(text).map((piece, at) =>
196 piece.href ? (
197 <Link key={at} to={piece.href} className="underline underline-offset-2">
198 {piece.text}
199 </Link>
200 ) : (
201 piece.text
202 ),
203 )}
204 </>
205 );
206}
207
208export function ErrorText({ children }: { children: ReactNode }) {
209 if (!children) return null;
210 if (typeof children !== "string") return <p className="text-sm text-danger">{children}</p>;
211 return (
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.212 <p className="text-sm text-danger">
The docs' Copy page menu keeps its own spacing, with an icon per item213 <Linked text={children} />
214 </p>
215 );
216}
217
218/**
219 * What a workspace's plan says before someone tries to start compute it
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.220 * would refuse ("Workflows need a paid workspace or the free trial"), with
The docs' Copy page menu keeps its own spacing, with an icon per item221 * where to fix it.
222 */
223export function ComputeNote({ note }: { note: string | null | undefined }) {
224 return note ? (
225 <p className="text-sm text-muted">
226 <Linked text={note} />
227 </p>
228 ) : null;
229}
230
231/** A line of text (usually a command) with a copy button. */
232export function CopyLine({
233 text,
234 prompt,
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet235 disabled,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md236 breakAtSlashes,
The docs' Copy page menu keeps its own spacing, with an icon per item237}: {
238 text: string;
239 prompt?: boolean;
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet240 /** Shown, so it is clear what will be there, but not yet usable: dimmed, with no copy. */
241 disabled?: boolean;
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md242 /**
243 * For a narrow box: an address longer than the line may wrap after a
244 * `/`, so all of it shows, rather than scroll.
245 */
246 breakAtSlashes?: boolean;
The docs' Copy page menu keeps its own spacing, with an icon per item247}) {
248 const [copied, setCopied] = useState(false);
249 return (
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet250 <div
251 aria-disabled={disabled || undefined}
252 className={`group flex items-center gap-3 rounded-lg border border-line bg-surface py-2 pr-2 pl-3.5 font-mono text-[0.8125rem] ${disabled ? "cursor-not-allowed text-faint select-none" : ""}`}
253 >
Usage, Billing settings and prepaid AI credit; fixes from the UX audit254 {/* Wraps between words rather than hides: a command is no use half
255 seen. A word itself, an address or a flag, is never broken in
256 two, which would change what is copied by eye; one longer than
257 the line scrolls sideways instead. */}
258 <code className="min-w-0 grow overflow-x-auto whitespace-pre-wrap [scrollbar-width:thin]">
The docs' Copy page menu keeps its own spacing, with an icon per item259 {prompt && <span className="mr-2 text-faint select-none">$</span>}
260 {text.split(/(\s+)/).map((part, index) =>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit261 /\S/.test(part) ? (
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md262 breakAtSlashes ? (
263 // Each piece whole, with a chance to wrap after each `/`.
264 <span key={index}>
265 {part.split(/(?<=\/)(?!\/)/).map((piece, at) => (
266 <Fragment key={at}>
267 {at > 0 && <wbr />}
268 <span className="whitespace-nowrap">{piece}</span>
269 </Fragment>
270 ))}
271 </span>
272 ) : (
273 <span key={index} className="whitespace-nowrap">
274 {part}
275 </span>
276 )
The docs' Copy page menu keeps its own spacing, with an icon per item277 ) : (
278 part
279 ),
280 )}
281 </code>
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.282 <Button
The docs' Copy page menu keeps its own spacing, with an icon per item283 type="button"
284 aria-label="Copy"
Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet285 disabled={disabled}
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.286 variant="ghost"
287 size="inline"
288 className="p-1.5 text-faint"
The docs' Copy page menu keeps its own spacing, with an icon per item289 onClick={() => {
290 void navigator.clipboard.writeText(text);
291 setCopied(true);
292 setTimeout(() => setCopied(false), 1500);
293 }}
294 >
Merge branch 'worktree-agent-ad7c6d88d93adc817'295 {copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
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.296 </Button>
The docs' Copy page menu keeps its own spacing, with an icon per item297 </div>
298 );
299}
300
301const UNITS: [string, number][] = [
302 ["d", 86_400_000],
303 ["h", 3_600_000],
304 ["m", 60_000],
305];
306
307/** Compact relative time such as "5m ago". */
308export function TimeAgo({ at }: { at: string | number }) {
309 // Timestamps are RFC 3339 strings; a few older ones are still numbers.
310 const elapsed = Date.now() - new Date(at).getTime();
311 const unit = UNITS.find(([, size]) => elapsed >= size);
312 const label = unit ? `${Math.floor(elapsed / unit[1])}${unit[0]} ago` : "just now";
313 return (
314 // The server and the browser render at slightly different times.
315 <time dateTime={new Date(at).toISOString()} suppressHydrationWarning>
316 {label}
317 </time>
318 );
319}
320
321export function EmptyState({
322 title,
323 children,
324}: {
325 title: string;
326 children?: ReactNode;
327}) {
328 return (
329 <div className="rounded-lg border border-dashed border-line px-6 py-12 text-center">
330 <p className="font-medium">{title}</p>
331 {children && <div className="mt-1.5 text-sm text-muted">{children}</div>}
332 </div>
333 );
334}

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