Skip to content
496 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.

What g1t can't do yet, and an open letter to Cloudflare1import { 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";
What g1t can't do yet, and an open letter to Cloudflare4
5import { type RegistrationToken, RUNNER_DOWNLOADS, RUNNER_FILES, RUNNER_IMAGE, type Runner, type RunnerGroup } from "@g1t/contracts";
6
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";
What g1t can't do yet, and an open letter to Cloudflare8import 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";
What g1t can't do yet, and an open letter to Cloudflare16import { 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" },
What g1t can't do yet, and an open letter to Cloudflare24};
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" };
What g1t can't do yet, and an open letter to Cloudflare33
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 }) {
What g1t can't do yet, and an open letter to Cloudflare50 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;
What g1t can't do yet, and an open letter to Cloudflare52 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>
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.58 <span className={`text-xs font-medium ${status.text}`}>{status.label}</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.59 {runner.ephemeral && <Badge size="md">ephemeral</Badge>}
What g1t can't do yet, and an open letter to Cloudflare60 {runner.group && <span className="text-xs text-faint">in {runner.group}</span>}
61 </div>
62 <div className="mt-1.5 flex flex-wrap gap-1">
63 {runner.labels.map((label) => (
64 <span key={label} className="rounded-md bg-raised px-1.5 py-0.5 font-mono text-[0.6875rem] text-fg-soft">
65 {label}
66 </span>
67 ))}
68 </div>
69 {runner.work && (
70 <p className="mt-1.5 truncate text-xs text-muted">
71 {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.72 {href ? (
73 <Link to={href} className="text-fg hover:underline">
What g1t can't do yet, and an open letter to Cloudflare74 {runner.work.name}
75 </Link>
76 ) : (
77 <span className="text-fg">{runner.work.name}</span>
78 )}
79 {runner.work.repo && <> in {runner.work.repo}</>}
80 </p>
81 )}
82 </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.83 <div className="ml-6 grow text-xs text-faint sm:ml-0 sm:shrink-0 sm:grow-0 sm:text-right">
What g1t can't do yet, and an open letter to Cloudflare84 <div>
85 {OS_NAMES[runner.os]} {runner.arch}
86 {runner.version && <> · {runner.version}</>}
87 </div>
88 <div className="mt-0.5">{runner.lastSeenAt ? <>Seen <TimeAgo at={runner.lastSeenAt} /></> : "Never seen"}</div>
89 </div>
90 {manage && (
91 <Form
92 method="post"
93 className="shrink-0"
94 onSubmit={(event) => {
95 if (!confirm(`Remove ${runner.name}? Its credential stops working at once${runner.work ? ", and the job it is running fails" : ""}.`)) event.preventDefault();
96 }}
97 >
98 <input type="hidden" name="intent" value="remove" />
99 <input type="hidden" name="id" value={runner.id} />
100 <input type="hidden" name="name" value={runner.name} />
Merge branch 'worktree-agent-a985c01f43538af01'101 <SubmitButton
102 icon
103 match={{ intent: "remove", id: runner.id }}
What g1t can't do yet, and an open letter to Cloudflare104 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.105 variant="ghost"
106 size="inline"
107 className="p-1.5 text-faint hover:text-danger"
What g1t can't do yet, and an open letter to Cloudflare108 >
109 <Trash2 size={15} />
Merge branch 'worktree-agent-a985c01f43538af01'110 </SubmitButton>
What g1t can't do yet, and an open letter to Cloudflare111 </Form>
112 )}
113 </li>
114 );
115}
116
117type Platform = "linux" | "macos" | "windows" | "docker";
118
119/** The copy-paste steps for one platform. */
120export function installSteps(platform: Platform, token: RegistrationToken | null, arch: "x64" | "arm64" = "x64"): { title: string; lines: string[] }[] {
121 const url = token?.url ?? "https://g1t.sh";
122 const value = token?.token ?? "<registration token>";
123 const register = (exe: string) => `${exe} register --url ${url} --token ${value}`;
124 switch (platform) {
125 case "linux": {
126 const file = arch === "arm64" ? RUNNER_FILES["linux-arm64"] : RUNNER_FILES["linux-x64"];
127 return [
128 { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] },
129 { title: "Register", lines: [register("./g1t-runner")] },
130 { title: "Run it, or install it as a service", lines: ["./g1t-runner run", "sudo ./g1t-runner service install"] },
131 ];
132 }
133 case "macos": {
134 const file = arch === "x64" ? RUNNER_FILES["macos-x64"] : RUNNER_FILES["macos-arm64"];
135 return [
136 { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] },
137 { title: "Register", lines: [register("./g1t-runner")] },
138 { title: "Run it, or install it as a launch agent", lines: ["./g1t-runner run", "./g1t-runner service install"] },
139 ];
140 }
141 case "windows":
142 return [
143 {
144 title: "Download (PowerShell)",
145 lines: [`Invoke-WebRequest ${RUNNER_DOWNLOADS}/latest/${RUNNER_FILES["windows-x64"]} -OutFile g1t-runner.exe`],
146 },
147 { title: "Register", lines: [register(".\\g1t-runner.exe")] },
148 { title: "Run it, or install it as a service (as Administrator)", lines: [".\\g1t-runner.exe run", ".\\g1t-runner.exe service install"] },
149 ];
150 case "docker":
151 return [
152 {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9153 title: "Register and run under Sysbox, each job in a container inside the runner's",
What g1t can't do yet, and an open letter to Cloudflare154 lines: [
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9155 `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}`,
What g1t can't do yet, and an open letter to Cloudflare156 ],
157 },
158 ];
159 }
160}
161
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.162export function NewRunner({ token, groups, repoScoped }: { token: RegistrationToken | null; groups: RunnerGroup[]; repoScoped: boolean }) {
What g1t can't do yet, and an open letter to Cloudflare163 const [platform, setPlatform] = useState<Platform>("linux");
164 const [arch, setArch] = useState<"x64" | "arm64">("x64");
165 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.166 <Card className="space-y-4 p-4">
What g1t can't do yet, and an open letter to Cloudflare167 {!token ? (
168 <Form method="post" className="flex flex-wrap items-end gap-3">
169 <input type="hidden" name="intent" value="token" />
170 {!repoScoped && groups.length > 1 && (
171 <Field label="Group">
Chat controls, public profiles, shadcn selects, and no Docs tab in a project172 <SelectField
173 name="group"
174 defaultValue={groups.find((g) => g.default)?.name ?? groups[0]?.name}
175 className="w-auto min-w-40"
176 options={groups.map((group) => ({ value: group.name, label: group.name }))}
177 />
What g1t can't do yet, and an open letter to Cloudflare178 </Field>
179 )}
Merge branch 'worktree-agent-a985c01f43538af01'180 <SubmitButton match={{ intent: "token" }} pending="Making a token…">
What g1t can't do yet, and an open letter to Cloudflare181 <Plus size={15} />
182 New runner
Merge branch 'worktree-agent-a985c01f43538af01'183 </SubmitButton>
What g1t can't do yet, and an open letter to Cloudflare184 <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>
185 </Form>
186 ) : (
187 <p className="text-sm">
188 Registration token made. It expires <TimeAgoFuture at={token.expiresAt} />
189 {token.group ? <>; its runners join {token.group}</> : null}. Copy it now: it is not shown again.
190 </p>
191 )}
192 <Tabs value={platform} onValueChange={(value) => setPlatform(value as Platform)}>
193 <div className="flex flex-wrap items-center gap-3">
194 <TabsList aria-label="Where the runner runs">
195 <TabsTrigger value="linux">Linux</TabsTrigger>
196 <TabsTrigger value="macos">macOS</TabsTrigger>
197 <TabsTrigger value="windows">Windows</TabsTrigger>
198 <TabsTrigger value="docker">Docker</TabsTrigger>
199 </TabsList>
200 {(platform === "linux" || platform === "macos") && (
201 <TabsList aria-label="Architecture">
202 <button
203 type="button"
204 onClick={() => setArch("x64")}
205 className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "x64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`}
206 >
207 {platform === "macos" ? "Intel" : "x64"}
208 </button>
209 <button
210 type="button"
211 onClick={() => setArch("arm64")}
212 className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "arm64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`}
213 >
214 {platform === "macos" ? "Apple silicon" : "arm64"}
215 </button>
216 </TabsList>
217 )}
218 </div>
219 </Tabs>
220 <ol className="space-y-3">
221 {installSteps(platform, token, platform === "macos" && arch === "x64" ? "x64" : arch).map((step, at) => (
222 <li key={step.title}>
223 <p className="mb-1.5 text-xs text-muted">
224 {at + 1}. {step.title}
225 </p>
226 <div className="space-y-1.5">
227 {step.lines.map((line) => (
228 <CopyLine key={line} text={line} prompt />
229 ))}
230 </div>
231 </li>
232 ))}
233 </ol>
234 <p className="text-xs text-faint">
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9235 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.{" "}
236 <code className="font-mono">--no-docker</code>, which runs jobs on the machine itself, is deprecated. Add{" "}
What g1t can't do yet, and an open letter to Cloudflare237 <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
238 one job and removes itself.{" "}
239 <a href="https://docs.g1t.sh/guides/self-hosted-runners/" className="underline underline-offset-2">
240 Self-hosted runners
241 </a>
242 </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.243 </Card>
What g1t can't do yet, and an open letter to Cloudflare244 );
245}
246
247/** "in 59m", for a time still to come. */
248function TimeAgoFuture({ at }: { at: string }) {
249 const minutes = Math.max(0, Math.round((new Date(at).getTime() - Date.now()) / 60_000));
250 return (
251 <time dateTime={at} suppressHydrationWarning>
252 {minutes >= 60 ? "in an hour" : `in ${minutes}m`}
253 </time>
254 );
255}
256
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.257export function Groups({
Merge branch 'worktree-agent-a985c01f43538af01'258 groups,
259 repositories,
260 manage,
261 action,
262}: {
263 groups: RunnerGroup[];
264 repositories: string[];
265 manage: boolean;
266 action: RunnersAction | undefined;
267}) {
What g1t can't do yet, and an open letter to Cloudflare268 const [editing, setEditing] = useState<string | null>(null);
Merge branch 'worktree-agent-a985c01f43538af01'269 // A group saved closes its form: the list above now shows it as saved.
270 useEffect(() => {
271 if (action?.notice) setEditing(null);
272 }, [action]);
What g1t can't do yet, and an open letter to Cloudflare273 return (
274 <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.275 <Card asChild divided>
276 <ul>
277 {groups.map((group) => (
278 <li key={group.id} className="px-4 py-3">
279 <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
280 <span className="font-medium">{group.name}</span>
281 {group.default && <Badge size="md">default</Badge>}
282 <span className="text-xs text-muted">
283 {group.runners} {group.runners === 1 ? "runner" : "runners"} ·{" "}
284 {group.repositories.length === 0 ? "every repository" : group.repositories.join(", ")}
What g1t can't do yet, and an open letter to Cloudflare285 </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.286 {manage && (
287 <span className="ml-auto flex items-center gap-2">
288 <Button type="button" variant="outline" onClick={() => setEditing(editing === group.id ? null : group.id)}>
289 {editing === group.id ? "Close" : "Edit"}
290 </Button>
291 {!group.default && (
292 <Form method="post" onSubmit={(event) => { if (!confirm(`Delete ${group.name}? Its runners join the default group.`)) event.preventDefault(); }}>
293 <input type="hidden" name="intent" value="delete-group" />
294 <input type="hidden" name="id" value={group.id} />
295 <SubmitButton variant="outline" match={{ intent: "delete-group", id: group.id }} pending="Deleting…">
296 Delete
297 </SubmitButton>
298 </Form>
299 )}
300 </span>
301 )}
302 </div>
303 {editing === group.id && <GroupForm group={group} repositories={repositories} />}
304 </li>
305 ))}
306 </ul>
307 </Card>
What g1t can't do yet, and an open letter to Cloudflare308 {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.309 <Button type="button" variant="outline" onClick={() => setEditing("new")}>
What g1t can't do yet, and an open letter to Cloudflare310 <Plus size={15} />
311 New group
312 </Button>
313 ))}
314 </div>
315 );
316}
317
318function GroupForm({ group, repositories }: { group: RunnerGroup | null; repositories: string[] }) {
319 const [reach, setReach] = useState(group && group.repositories.length > 0 ? "some" : "all");
320 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.321 <Card asChild tone="bg" radius="lg" className="mt-3 space-y-3 p-3">
322 <Form method="post">
323 <input type="hidden" name="intent" value="group" />
324 {group && <input type="hidden" name="id" value={group.id} />}
325 <Field label="Name">
326 <Input name="name" defaultValue={group?.name ?? ""} placeholder="GPU machines" maxLength={64} required={!group} />
327 </Field>
328 <fieldset className="space-y-2 text-sm">
329 <legend className="mb-1 text-xs text-muted">Repositories that may use its runners</legend>
330 <RadioGroup name="reach" value={reach} onValueChange={setReach}>
331 <RadioOption value="all" label="Every repository in the workspace" />
332 <RadioOption value="some" label="Only these" />
333 </RadioGroup>
334 {reach === "some" && (
335 <div className="grid gap-1 pl-6 sm:grid-cols-2">
336 {repositories.map((name) => (
337 <label key={name} className="flex min-h-8 cursor-pointer items-center gap-2 font-mono text-xs">
338 <Checkbox name="repository" value={name} defaultChecked={group?.repositories.includes(name)} />
339 {name}
340 </label>
341 ))}
342 </div>
343 )}
344 </fieldset>
345 <SubmitButton match={{ intent: "group", id: group?.id }} pending="Saving…">
346 Save group
347 </SubmitButton>
348 </Form>
349 </Card>
What g1t can't do yet, and an open letter to Cloudflare350 );
351}
352
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.353export function RunnerSettingsForm({ data, manage, scope }: { data: RunnersData; manage: boolean; scope: "workspace" | "project" }) {
What g1t can't do yet, and an open letter to Cloudflare354 const settings = data.settings;
355 const [agents, setAgents] = useState(settings?.agentsOnSelfHosted ?? false);
356 const [forks, setForks] = useState(settings?.forkPullRequests ?? false);
357 if (!settings) return null;
Merge branch 'worktree-agent-a985c01f43538af01'358 // The intent comes from the button pressed: a hidden one before it would
359 // win the form's first "intent", and Follow the workspace would only save.
What g1t can't do yet, and an open letter to Cloudflare360 return (
361 <Form method="post" className="space-y-4">
362 {scope === "project" && settings.inherited && (
363 <p className="text-sm text-muted">These are the workspace&apos;s settings. Saving here gives this project its own.</p>
364 )}
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.365 <Card asChild className="flex items-start justify-between gap-4 p-4">
366 <label>
367 <span className="min-w-0">
368 <span className="flex items-center gap-2 text-sm font-medium">
369 <Bot size={15} className="text-muted" />
370 Run g1t's work on self-hosted runners
371 </span>
372 <span className="mt-1 block text-sm text-muted">
373 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
374 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
375 Linux runner.
376 </span>
377 <span className="mt-3 block max-w-sm">
378 <Input name="agentLabels" defaultValue={settings.agentLabels.join(", ")} disabled={!manage} aria-label="Labels for agent work" />
379 </span>
What g1t can't do yet, and an open letter to Cloudflare380 </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.381 <Switch name="agents" checked={agents} onCheckedChange={setAgents} disabled={!manage} aria-label="Run g1t's work on self-hosted runners" />
382 </label>
383 </Card>
What g1t can't do yet, and an open letter to Cloudflare384 <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"}`}>
385 <span className="min-w-0">
386 <span className="flex items-center gap-2 text-sm font-medium">
387 <ShieldAlert size={15} className={forks ? "text-danger" : "text-muted"} />
388 Let pull requests from forks use self-hosted runners
389 </span>
390 <span className="mt-1 block text-sm text-muted">
391 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
392 reach, and leave something behind for the next job. Their jobs never get secrets either way.
393 </span>
394 </span>
395 <Switch name="forks" checked={forks} onCheckedChange={setForks} disabled={!manage} />
396 </label>
397 <p className="text-xs text-faint">
398 g1t&apos;s network guardrails are not enforced on your own machines: a job or an agent there reaches whatever the machine can.
399 </p>
400 {manage && (
401 <div className="flex flex-wrap gap-3">
Merge branch 'worktree-agent-a985c01f43538af01'402 <SubmitButton name="intent" value="settings" pending="Saving…">
What g1t can't do yet, and an open letter to Cloudflare403 Save
Merge branch 'worktree-agent-a985c01f43538af01'404 </SubmitButton>
What g1t can't do yet, and an open letter to Cloudflare405 {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.406 <SubmitButton name="intent" value="inherit" variant="outline" pending="Following…">
What g1t can't do yet, and an open letter to Cloudflare407 Follow the workspace
Merge branch 'worktree-agent-a985c01f43538af01'408 </SubmitButton>
What g1t can't do yet, and an open letter to Cloudflare409 )}
410 </div>
411 )}
412 </Form>
413 );
414}
415
416/** Settings, Runners: a workspace's, or a project's own. */
417export function RunnersPanel({
418 data,
419 action,
420 scope,
421 manage,
422}: {
423 data: RunnersData;
424 action: RunnersAction | undefined;
425 scope: "workspace" | "project";
426 manage: boolean;
427}) {
428 const online = data.runners.filter((runner) => runner.status !== "offline").length;
429 return (
430 <div className="max-w-5xl space-y-8">
431 <div className="min-h-6">
Merge branch 'worktree-agent-ad7c6d88d93adc817'432 {action?.notice && <p className="text-sm text-success">{action.notice}</p>}
What g1t can't do yet, and an open letter to Cloudflare433 <ErrorText>{action?.error ?? data.error ?? null}</ErrorText>
434 </div>
435 <Section
436 icon={<ServerCog size={15} className="text-muted" />}
437 title="Runners"
438 about={
439 <>
440 <p>
441 Your own machines, for jobs with <code className="font-mono text-fg">runs-on: self-hosted</code>. Their time is free on every plan.
442 </p>
443 <p className="mt-2">
444 {data.runners.length === 0 ? "None yet." : `${online} of ${data.runners.length} online.`}
445 </p>
446 </>
447 }
448 >
449 {data.runners.length === 0 ? (
450 <EmptyState title="No self-hosted runners">
451 Add one below. It connects out to g1t; nothing needs to reach the machine.
452 </EmptyState>
453 ) : (
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.454 <Card asChild divided>
455 <ul>
456 {data.runners.map((runner) => (
457 <RunnerRow key={runner.id} runner={runner} manage={manage && (scope === "workspace" || runner.repo !== null)} />
458 ))}
459 </ul>
460 </Card>
What g1t can't do yet, and an open letter to Cloudflare461 )}
462 </Section>
463 {manage && (
464 <Section
465 icon={<Plus size={15} className="text-muted" />}
466 title="New runner"
467 about="Download g1t-runner, register it with a token made here, and start it. Linux, macOS and Windows, x64 and arm64."
468 >
469 <NewRunner token={action?.token ?? null} groups={data.groups} repoScoped={scope === "project"} />
470 </Section>
471 )}
472 {scope === "workspace" && (
473 <Section
474 icon={<Boxes size={15} className="text-muted" />}
475 title="Groups"
476 about="Which repositories may use which runners. A runner joins the default group, every repository, unless its token names another."
477 >
Merge branch 'worktree-agent-a985c01f43538af01'478 <Groups groups={data.groups} repositories={data.repositories} manage={manage} action={action} />
What g1t can't do yet, and an open letter to Cloudflare479 </Section>
480 )}
481 <Section
482 icon={<Cpu size={15} className="text-muted" />}
483 title="Where work runs"
484 about={scope === "workspace" ? "For every project in the workspace, unless a project says otherwise." : "For this project."}
485 >
Merge branch 'worktree-agent-a985c01f43538af01'486 {/* 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.487 <RunnerSettingsForm
Merge branch 'worktree-agent-a985c01f43538af01'488 key={data.settings ? `${data.settings.inherited}:${data.settings.agentsOnSelfHosted}:${data.settings.forkPullRequests}:${data.settings.agentLabels.join(",")}` : "none"}
489 data={data}
490 manage={manage}
491 scope={scope}
492 />
What g1t can't do yet, and an open letter to Cloudflare493 </Section>
494 </div>
495 );
496}

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