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

Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1import { Bot, Boxes, Cpu, Plus, ServerCog, ShieldAlert, Trash2 } from "lucide-react";
Merge branch 'worktree-agent-a985c01f43538af01'2import { useEffect, useState } from "react";
3import { Form, Link } from "react-router";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily4
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.5import { DESKTOP_LABEL, type RegistrationToken, RUNNER_DOWNLOADS, RUNNER_FILES, RUNNER_IMAGE, type Runner, type RunnerGroup } from "@g1t/contracts";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily6
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.7import { agentRunHref, workflowRunHref } from "../lib/runners";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily8import type { RunnersAction, RunnersData } from "../lib/runners.server";
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.9import { CopyLine, EmptyState, ErrorText, Field, Input, SubmitButton, TimeAgo } from "./ui";
10import { Badge } from "./ui/badge";
11import { Button } from "./ui/button";
12import { Card } from "./ui/card";
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.13import { Checkbox } from "./ui/checkbox";
14import { RadioGroup, RadioOption } from "./ui/radio-group";
Chat controls, public profiles, shadcn selects, and no Docs tab in a project15import { SelectField } from "./ui/select";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily16import { Switch } from "./ui/switch";
17import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
18
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.19/** Online and waiting for work reads as Idle; online and working, Busy. */
20export const STATUS: Record<Runner["status"], { dot: string; text: string; label: string }> = {
21 online: { dot: "bg-success", text: "text-success", label: "Idle" },
22 busy: { dot: "bg-info", text: "text-info", label: "Busy" },
23 offline: { dot: "bg-faint", text: "text-muted", label: "Offline" },
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily24};
25
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.26/** Where a runner's current work is: its workflow run, or the agent run it is. */
27function workHref(work: NonNullable<Runner["work"]>): string | null {
28 if (!work.runId || !work.repo) return null;
29 return work.kind === "workflow" ? workflowRunHref(work.repo, work.runId) : agentRunHref(work.repo, work.runId);
30}
31
32export const OS_NAMES: Record<Runner["os"], string> = { linux: "Linux", macos: "macOS", windows: "Windows" };
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily33
34function Section({ icon, title, about, children }: { icon: React.ReactNode; title: string; about: React.ReactNode; children: React.ReactNode }) {
35 return (
36 <section className="grid gap-x-10 gap-y-4 border-t border-line pt-8 first:border-t-0 first:pt-0 lg:grid-cols-[16rem_1fr]">
37 <div>
38 <h2 className="flex items-center gap-2 font-medium">
39 {icon}
40 {title}
41 </h2>
42 <div className="mt-1 text-sm text-muted">{about}</div>
43 </div>
44 <div className="min-w-0">{children}</div>
45 </section>
46 );
47}
48
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.49export function RunnerRow({ runner, manage }: { runner: Runner; manage: boolean }) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily50 const status = STATUS[runner.status];
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.51 const href = runner.work ? workHref(runner.work) : null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily52 return (
53 <li className="flex flex-wrap items-start gap-x-4 gap-y-2 px-4 py-3">
54 <span className={`mt-1.5 size-2 shrink-0 rounded-full ${status.dot}`} aria-hidden />
55 <div className="min-w-0 grow basis-60">
56 <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
57 <span className="font-medium">{runner.name}</span>
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.58 <span className={`text-xs font-medium ${status.text}`}>
59 {/* A desktop app's runner runs while g1t is open there: offline is g1t closed. */}
60 {runner.status === "offline" && runner.labels.includes(DESKTOP_LABEL) ? `g1t is closed on ${runner.name}` : status.label}
61 </span>
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.62 {runner.ephemeral && <Badge size="md">ephemeral</Badge>}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily63 {runner.group && <span className="text-xs text-faint">in {runner.group}</span>}
64 </div>
65 <div className="mt-1.5 flex flex-wrap gap-1">
66 {runner.labels.map((label) => (
67 <span key={label} className="rounded-md bg-raised px-1.5 py-0.5 font-mono text-[0.6875rem] text-fg-soft">
68 {label}
69 </span>
70 ))}
71 </div>
72 {runner.work && (
73 <p className="mt-1.5 truncate text-xs text-muted">
74 {runner.work.kind === "workflow" ? "Running " : "Agent work: "}
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.75 {href ? (
76 <Link to={href} className="text-fg hover:underline">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily77 {runner.work.name}
78 </Link>
79 ) : (
80 <span className="text-fg">{runner.work.name}</span>
81 )}
82 {runner.work.repo && <> in {runner.work.repo}</>}
83 </p>
84 )}
85 </div>
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.86 <div className="ml-6 grow text-xs text-faint sm:ml-0 sm:shrink-0 sm:grow-0 sm:text-right">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily87 <div>
88 {OS_NAMES[runner.os]} {runner.arch}
89 {runner.version && <> · {runner.version}</>}
90 </div>
91 <div className="mt-0.5">{runner.lastSeenAt ? <>Seen <TimeAgo at={runner.lastSeenAt} /></> : "Never seen"}</div>
92 </div>
93 {manage && (
94 <Form
95 method="post"
96 className="shrink-0"
97 onSubmit={(event) => {
98 if (!confirm(`Remove ${runner.name}? Its credential stops working at once${runner.work ? ", and the job it is running fails" : ""}.`)) event.preventDefault();
99 }}
100 >
101 <input type="hidden" name="intent" value="remove" />
102 <input type="hidden" name="id" value={runner.id} />
103 <input type="hidden" name="name" value={runner.name} />
Merge branch 'worktree-agent-a985c01f43538af01'104 <SubmitButton
105 icon
106 match={{ intent: "remove", id: runner.id }}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily107 aria-label={`Remove ${runner.name}`}
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.108 variant="ghost"
109 size="inline"
110 className="p-1.5 text-faint hover:text-danger"
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily111 >
112 <Trash2 size={15} />
Merge branch 'worktree-agent-a985c01f43538af01'113 </SubmitButton>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily114 </Form>
115 )}
116 </li>
117 );
118}
119
120type Platform = "linux" | "macos" | "windows" | "docker";
121
122/** The copy-paste steps for one platform. */
123export function installSteps(platform: Platform, token: RegistrationToken | null, arch: "x64" | "arm64" = "x64"): { title: string; lines: string[] }[] {
124 const url = token?.url ?? "https://g1t.sh";
125 const value = token?.token ?? "<registration token>";
126 const register = (exe: string) => `${exe} register --url ${url} --token ${value}`;
127 switch (platform) {
128 case "linux": {
129 const file = arch === "arm64" ? RUNNER_FILES["linux-arm64"] : RUNNER_FILES["linux-x64"];
130 return [
131 { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] },
132 { title: "Register", lines: [register("./g1t-runner")] },
133 { title: "Run it, or install it as a service", lines: ["./g1t-runner run", "sudo ./g1t-runner service install"] },
134 ];
135 }
136 case "macos": {
137 const file = arch === "x64" ? RUNNER_FILES["macos-x64"] : RUNNER_FILES["macos-arm64"];
138 return [
139 { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] },
140 { title: "Register", lines: [register("./g1t-runner")] },
141 { title: "Run it, or install it as a launch agent", lines: ["./g1t-runner run", "./g1t-runner service install"] },
142 ];
143 }
144 case "windows":
145 return [
146 {
147 title: "Download (PowerShell)",
148 lines: [`Invoke-WebRequest ${RUNNER_DOWNLOADS}/latest/${RUNNER_FILES["windows-x64"]} -OutFile g1t-runner.exe`],
149 },
150 { title: "Register", lines: [register(".\\g1t-runner.exe")] },
151 { title: "Run it, or install it as a service (as Administrator)", lines: [".\\g1t-runner.exe run", ".\\g1t-runner.exe service install"] },
152 ];
153 case "docker":
154 return [
155 {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9156 title: "Register and run under Sysbox, each job in a container inside the runner's",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily157 lines: [
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9158 `docker run -d --name g1t-runner --restart unless-stopped --runtime=sysbox-runc -v g1t-runner:/data ${RUNNER_IMAGE} register-and-run --url ${url} --token ${value}`,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily159 ],
160 },
161 ];
162 }
163}
164
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.165export function NewRunner({ token, groups, repoScoped }: { token: RegistrationToken | null; groups: RunnerGroup[]; repoScoped: boolean }) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily166 const [platform, setPlatform] = useState<Platform>("linux");
167 const [arch, setArch] = useState<"x64" | "arm64">("x64");
168 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.169 <Card className="space-y-4 p-4">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily170 {!token ? (
171 <Form method="post" className="flex flex-wrap items-end gap-3">
172 <input type="hidden" name="intent" value="token" />
173 {!repoScoped && groups.length > 1 && (
174 <Field label="Group">
Chat controls, public profiles, shadcn selects, and no Docs tab in a project175 <SelectField
176 name="group"
177 defaultValue={groups.find((g) => g.default)?.name ?? groups[0]?.name}
178 className="w-auto min-w-40"
179 options={groups.map((group) => ({ value: group.name, label: group.name }))}
180 />
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily181 </Field>
182 )}
Merge branch 'worktree-agent-a985c01f43538af01'183 <SubmitButton match={{ intent: "token" }} pending="Making a token…">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily184 <Plus size={15} />
185 New runner
Merge branch 'worktree-agent-a985c01f43538af01'186 </SubmitButton>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily187 <p className="basis-full text-xs text-faint">Makes a registration token that lasts an hour and registers any number of runners. It is shown once.</p>
188 </Form>
189 ) : (
190 <p className="text-sm">
191 Registration token made. It expires <TimeAgoFuture at={token.expiresAt} />
192 {token.group ? <>; its runners join {token.group}</> : null}. Copy it now: it is not shown again.
193 </p>
194 )}
195 <Tabs value={platform} onValueChange={(value) => setPlatform(value as Platform)}>
196 <div className="flex flex-wrap items-center gap-3">
197 <TabsList aria-label="Where the runner runs">
198 <TabsTrigger value="linux">Linux</TabsTrigger>
199 <TabsTrigger value="macos">macOS</TabsTrigger>
200 <TabsTrigger value="windows">Windows</TabsTrigger>
201 <TabsTrigger value="docker">Docker</TabsTrigger>
202 </TabsList>
203 {(platform === "linux" || platform === "macos") && (
204 <TabsList aria-label="Architecture">
205 <button
206 type="button"
207 onClick={() => setArch("x64")}
208 className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "x64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`}
209 >
210 {platform === "macos" ? "Intel" : "x64"}
211 </button>
212 <button
213 type="button"
214 onClick={() => setArch("arm64")}
215 className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "arm64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`}
216 >
217 {platform === "macos" ? "Apple silicon" : "arm64"}
218 </button>
219 </TabsList>
220 )}
221 </div>
222 </Tabs>
223 <ol className="space-y-3">
224 {installSteps(platform, token, platform === "macos" && arch === "x64" ? "x64" : arch).map((step, at) => (
225 <li key={step.title}>
226 <p className="mb-1.5 text-xs text-muted">
227 {at + 1}. {step.title}
228 </p>
229 <div className="space-y-1.5">
230 {step.lines.map((line) => (
231 <CopyLine key={line} text={line} prompt />
232 ))}
233 </div>
234 </li>
235 ))}
236 </ol>
237 <p className="text-xs text-faint">
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9238 Each job runs in a container of its own; on Linux, run the Docker command under Sysbox so neither the runner nor its jobs reach the machine.{" "}
239 <code className="font-mono">--no-docker</code>, which runs jobs on the machine itself, is deprecated. Add{" "}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily240 <code className="font-mono">--labels gpu,cuda</code> for jobs to ask for, or <code className="font-mono">--ephemeral</code> for a runner that takes
241 one job and removes itself.{" "}
242 <a href="https://docs.g1t.sh/guides/self-hosted-runners/" className="underline underline-offset-2">
243 Self-hosted runners
244 </a>
245 </p>
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.246 </Card>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily247 );
248}
249
250/** "in 59m", for a time still to come. */
251function TimeAgoFuture({ at }: { at: string }) {
252 const minutes = Math.max(0, Math.round((new Date(at).getTime() - Date.now()) / 60_000));
253 return (
254 <time dateTime={at} suppressHydrationWarning>
255 {minutes >= 60 ? "in an hour" : `in ${minutes}m`}
256 </time>
257 );
258}
259
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.260export function Groups({
Merge branch 'worktree-agent-a985c01f43538af01'261 groups,
262 repositories,
263 manage,
264 action,
265}: {
266 groups: RunnerGroup[];
267 repositories: string[];
268 manage: boolean;
269 action: RunnersAction | undefined;
270}) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily271 const [editing, setEditing] = useState<string | null>(null);
Merge branch 'worktree-agent-a985c01f43538af01'272 // A group saved closes its form: the list above now shows it as saved.
273 useEffect(() => {
274 if (action?.notice) setEditing(null);
275 }, [action]);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily276 return (
277 <div className="space-y-3">
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.278 <Card asChild divided>
279 <ul>
280 {groups.map((group) => (
281 <li key={group.id} className="px-4 py-3">
282 <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
283 <span className="font-medium">{group.name}</span>
284 {group.default && <Badge size="md">default</Badge>}
285 <span className="text-xs text-muted">
286 {group.runners} {group.runners === 1 ? "runner" : "runners"} ·{" "}
287 {group.repositories.length === 0 ? "every repository" : group.repositories.join(", ")}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily288 </span>
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.289 {manage && (
290 <span className="ml-auto flex items-center gap-2">
291 <Button type="button" variant="outline" onClick={() => setEditing(editing === group.id ? null : group.id)}>
292 {editing === group.id ? "Close" : "Edit"}
293 </Button>
294 {!group.default && (
295 <Form method="post" onSubmit={(event) => { if (!confirm(`Delete ${group.name}? Its runners join the default group.`)) event.preventDefault(); }}>
296 <input type="hidden" name="intent" value="delete-group" />
297 <input type="hidden" name="id" value={group.id} />
298 <SubmitButton variant="outline" match={{ intent: "delete-group", id: group.id }} pending="Deleting…">
299 Delete
300 </SubmitButton>
301 </Form>
302 )}
303 </span>
304 )}
305 </div>
306 {editing === group.id && <GroupForm group={group} repositories={repositories} />}
307 </li>
308 ))}
309 </ul>
310 </Card>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily311 {manage && (editing === "new" ? <GroupForm group={null} repositories={repositories} /> : (
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.312 <Button type="button" variant="outline" onClick={() => setEditing("new")}>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily313 <Plus size={15} />
314 New group
315 </Button>
316 ))}
317 </div>
318 );
319}
320
321function GroupForm({ group, repositories }: { group: RunnerGroup | null; repositories: string[] }) {
322 const [reach, setReach] = useState(group && group.repositories.length > 0 ? "some" : "all");
323 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.324 <Card asChild tone="bg" radius="lg" className="mt-3 space-y-3 p-3">
325 <Form method="post">
326 <input type="hidden" name="intent" value="group" />
327 {group && <input type="hidden" name="id" value={group.id} />}
328 <Field label="Name">
329 <Input name="name" defaultValue={group?.name ?? ""} placeholder="GPU machines" maxLength={64} required={!group} />
330 </Field>
331 <fieldset className="space-y-2 text-sm">
332 <legend className="mb-1 text-xs text-muted">Repositories that may use its runners</legend>
333 <RadioGroup name="reach" value={reach} onValueChange={setReach}>
334 <RadioOption value="all" label="Every repository in the workspace" />
335 <RadioOption value="some" label="Only these" />
336 </RadioGroup>
337 {reach === "some" && (
338 <div className="grid gap-1 pl-6 sm:grid-cols-2">
339 {repositories.map((name) => (
340 <label key={name} className="flex min-h-8 cursor-pointer items-center gap-2 font-mono text-xs">
341 <Checkbox name="repository" value={name} defaultChecked={group?.repositories.includes(name)} />
342 {name}
343 </label>
344 ))}
345 </div>
346 )}
347 </fieldset>
348 <SubmitButton match={{ intent: "group", id: group?.id }} pending="Saving…">
349 Save group
350 </SubmitButton>
351 </Form>
352 </Card>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily353 );
354}
355
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.356export function RunnerSettingsForm({ data, manage, scope }: { data: RunnersData; manage: boolean; scope: "workspace" | "project" }) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily357 const settings = data.settings;
358 const [agents, setAgents] = useState(settings?.agentsOnSelfHosted ?? false);
359 const [forks, setForks] = useState(settings?.forkPullRequests ?? false);
360 if (!settings) return null;
Merge branch 'worktree-agent-a985c01f43538af01'361 // The intent comes from the button pressed: a hidden one before it would
362 // win the form's first "intent", and Follow the workspace would only save.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily363 return (
364 <Form method="post" className="space-y-4">
365 {scope === "project" && settings.inherited && (
366 <p className="text-sm text-muted">These are the workspace&apos;s settings. Saving here gives this project its own.</p>
367 )}
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.368 <Card asChild className="flex items-start justify-between gap-4 p-4">
369 <label>
370 <span className="min-w-0">
371 <span className="flex items-center gap-2 text-sm font-medium">
372 <Bot size={15} className="text-muted" />
373 Run g1t's work on self-hosted runners
374 </span>
375 <span className="mt-1 block text-sm text-muted">
376 Agent runs, checks, reviews and the merge queue run on your runners with these labels instead of g1t&apos;s sandboxes. The machine time is
377 free; the model is paid as before, through g1t&apos;s model proxy, or by your own provider if you connected one. Agent work needs Docker, or a
378 Linux runner.
379 </span>
380 <span className="mt-3 block max-w-sm">
381 <Input name="agentLabels" defaultValue={settings.agentLabels.join(", ")} disabled={!manage} aria-label="Labels for agent work" />
382 </span>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily383 </span>
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.384 <Switch name="agents" checked={agents} onCheckedChange={setAgents} disabled={!manage} aria-label="Run g1t's work on self-hosted runners" />
385 </label>
386 </Card>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily387 <label className={`flex items-start justify-between gap-4 rounded-xl border p-4 ${forks ? "border-danger/50 bg-danger/5" : "border-line bg-surface"}`}>
388 <span className="min-w-0">
389 <span className="flex items-center gap-2 text-sm font-medium">
390 <ShieldAlert size={15} className={forks ? "text-danger" : "text-muted"} />
391 Let pull requests from forks use self-hosted runners
392 </span>
393 <span className="mt-1 block text-sm text-muted">
394 Off by default, and best left off. Anyone who can open a pull request could then run any code on your machines, read what they can
395 reach, and leave something behind for the next job. Their jobs never get secrets either way.
396 </span>
397 </span>
398 <Switch name="forks" checked={forks} onCheckedChange={setForks} disabled={!manage} />
399 </label>
400 <p className="text-xs text-faint">
401 g1t&apos;s network guardrails are not enforced on your own machines: a job or an agent there reaches whatever the machine can.
402 </p>
403 {manage && (
404 <div className="flex flex-wrap gap-3">
Merge branch 'worktree-agent-a985c01f43538af01'405 <SubmitButton name="intent" value="settings" pending="Saving…">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily406 Save
Merge branch 'worktree-agent-a985c01f43538af01'407 </SubmitButton>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily408 {scope === "project" && !settings.inherited && (
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.409 <SubmitButton name="intent" value="inherit" variant="outline" pending="Following…">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily410 Follow the workspace
Merge branch 'worktree-agent-a985c01f43538af01'411 </SubmitButton>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily412 )}
413 </div>
414 )}
415 </Form>
416 );
417}
418
419/** Settings, Runners: a workspace's, or a project's own. */
420export function RunnersPanel({
421 data,
422 action,
423 scope,
424 manage,
425}: {
426 data: RunnersData;
427 action: RunnersAction | undefined;
428 scope: "workspace" | "project";
429 manage: boolean;
430}) {
431 const online = data.runners.filter((runner) => runner.status !== "offline").length;
432 return (
433 <div className="max-w-5xl space-y-8">
434 <div className="min-h-6">
Merge branch 'worktree-agent-ad7c6d88d93adc817'435 {action?.notice && <p className="text-sm text-success">{action.notice}</p>}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily436 <ErrorText>{action?.error ?? data.error ?? null}</ErrorText>
437 </div>
438 <Section
439 icon={<ServerCog size={15} className="text-muted" />}
440 title="Runners"
441 about={
442 <>
443 <p>
444 Your own machines, for jobs with <code className="font-mono text-fg">runs-on: self-hosted</code>. Their time is free on every plan.
445 </p>
446 <p className="mt-2">
447 {data.runners.length === 0 ? "None yet." : `${online} of ${data.runners.length} online.`}
448 </p>
449 </>
450 }
451 >
452 {data.runners.length === 0 ? (
453 <EmptyState title="No self-hosted runners">
454 Add one below. It connects out to g1t; nothing needs to reach the machine.
455 </EmptyState>
456 ) : (
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.457 <Card asChild divided>
458 <ul>
459 {data.runners.map((runner) => (
460 <RunnerRow key={runner.id} runner={runner} manage={manage && (scope === "workspace" || runner.repo !== null)} />
461 ))}
462 </ul>
463 </Card>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily464 )}
465 </Section>
466 {manage && (
467 <Section
468 icon={<Plus size={15} className="text-muted" />}
469 title="New runner"
470 about="Download g1t-runner, register it with a token made here, and start it. Linux, macOS and Windows, x64 and arm64."
471 >
472 <NewRunner token={action?.token ?? null} groups={data.groups} repoScoped={scope === "project"} />
473 </Section>
474 )}
475 {scope === "workspace" && (
476 <Section
477 icon={<Boxes size={15} className="text-muted" />}
478 title="Groups"
479 about="Which repositories may use which runners. A runner joins the default group, every repository, unless its token names another."
480 >
Merge branch 'worktree-agent-a985c01f43538af01'481 <Groups groups={data.groups} repositories={data.repositories} manage={manage} action={action} />
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily482 </Section>
483 )}
484 <Section
485 icon={<Cpu size={15} className="text-muted" />}
486 title="Where work runs"
487 about={scope === "workspace" ? "For every project in the workspace, unless a project says otherwise." : "For this project."}
488 >
Merge branch 'worktree-agent-a985c01f43538af01'489 {/* Keyed to what is saved, so following the workspace again shows its settings, not the switches as they were. */}
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.490 <RunnerSettingsForm
Merge branch 'worktree-agent-a985c01f43538af01'491 key={data.settings ? `${data.settings.inherited}:${data.settings.agentsOnSelfHosted}:${data.settings.forkPullRequests}:${data.settings.agentLabels.join(",")}` : "none"}
492 data={data}
493 manage={manage}
494 scope={scope}
495 />
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily496 </Section>
497 </div>
498 );
499}

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