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
373 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 sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
2 * Cloudflare's API, behind the calls deployments need: open an upload of
3 * an app's files, put the app in the dispatch namespace, take it down, and
4 * count what each app used.
5 *
6 * The token is the service's own, scoped to Workers scripts and analytics on
7 * g1t's account. It never leaves this Worker: a sandbox only ever gets an
8 * upload session's key, which can upload one manifest's files and nothing
9 * else.
10 */
11
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.12import { TAIL_CONSUMERS } from "./telemetry";
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.13import { APP_CLASS, APP_WORKER, REGISTRY_HOST, applicationName, applicationRequest, pushLoginRequest, rolloutRequest } from "./containers";
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor14import { redirectScript } from "./redirect";
15
16const API = "https://api.cloudflare.com/client/v4";
17
18export type Manifest = Record<string, { hash: string; size: number }>;
19
20/** A module of a Worker, as the sandbox sends it. */
21export type Module = { name: string; contentBase64: string; contentType: string };
22
23/** What the sandbox built: the Worker's code and settings. */
24export type BuiltWorker = {
25 mainModule?: string;
26 modules?: Module[];
27 compatibilityDate?: string;
28 compatibilityFlags?: string[];
29 vars?: Record<string, unknown>;
30 assetsBinding?: string | null;
31 htmlHandling?: string | null;
32 notFoundHandling?: string | null;
33 _headers?: string;
34 _redirects?: string;
35};
36
37/** Serves the site's files, for an app that brings no code of its own. */
38const ASSETS_ONLY = `export default { fetch(request, env) { return env.ASSETS.fetch(request); } };\n`;
39
40/** What an app answers between being taken down and being gone. */
41const PAUSED = `export default {
42 fetch() {
43 return new Response("<!doctype html><meta charset=utf-8><meta name=robots content=noindex><title>Paused</title><body style='font:16px system-ui;background:#121214;color:#ececf1;display:grid;place-items:center;min-height:100vh;margin:0;padding:0 16px'><main style='max-width:32rem'><h1>This app is paused</h1><p style='color:#9a9aa6'>The workspace it belongs to reached its usage limit on g1t. It comes back by itself once the workspace is under it again.</p></main>", { status: 402, headers: { "content-type": "text/html; charset=utf-8", "x-robots-tag": "noindex", "cache-control": "no-store" } });
44 },
45};`;
46
47const TAKEN_DOWN = `export default {
48 fetch() {
49 return new Response("<!doctype html><meta charset=utf-8><meta name=robots content=noindex><title>Not up</title><body style='font:16px system-ui;background:#121214;color:#ececf1;display:grid;place-items:center;min-height:100vh;margin:0'><main><h1>This app is not up</h1><p style='color:#9a9aa6'>It was taken down. Deploying it again brings it back.</p></main>", { status: 404, headers: { "content-type": "text/html; charset=utf-8", "x-robots-tag": "noindex", "cache-control": "no-store" } });
50 },
51};
52`;
53
54const HTML_HANDLING =["auto-trailing-slash", "force-trailing-slash", "drop-trailing-slash", "none"];
55const NOT_FOUND_HANDLING = ["single-page-application", "404-page", "none"];
56
57export class Cloudflare {
58 constructor(
59 private readonly token: string,
60 private readonly account: string,
61 readonly namespace: string,
62 ) {}
63
64 private async call<T>(method: string, path: string, body?: BodyInit, contentType?: string): Promise<T> {
65 const headers: Record<string, string> = { authorization: `Bearer ${this.token}` };
66 if (contentType) headers["content-type"] = contentType;
67 const response = await fetch(`${API}${path}`, { method, headers, body });
68 const answer = (await response.json().catch(() => null)) as {
69 success?: boolean;
70 result?: T;
71 errors?: { code: number; message: string }[];
72 } | null;
73 if (!response.ok || !answer?.success) {
74 const why = answer?.errors?.map((error) => `${error.message} (${error.code})`).join("; ");
75 throw new Error(`Cloudflare answered ${response.status}: ${why || "no reason given"}`);
76 }
77 return answer.result as T;
78 }
79
80 private scriptPath(script: string): string {
81 return `/accounts/${this.account}/workers/dispatch/namespaces/${this.namespace}/scripts/${encodeURIComponent(script)}`;
82 }
83
84 /** Where a sandbox sends the files an upload session asks for. */
85 get uploadUrl(): string {
86 return `${API}/accounts/${this.account}/workers/assets/upload?base64=true`;
87 }
88
89 /**
90 * Opens an upload of exactly these files. Cloudflare answers with a key
91 * that can upload only them, and the files it does not already have, in
92 * buckets; with no buckets, the key itself completes the upload.
93 */
94 async openUpload(script: string, manifest: Manifest): Promise<{ jwt: string; buckets: string[][] }> {
95 const result = await this.call<{ jwt: string; buckets?: string[][] }>(
96 "POST",
97 `${this.scriptPath(script)}/assets-upload-session`,
98 JSON.stringify({ manifest }),
99 "application/json",
100 );
101 return { jwt: result.jwt, buckets: result.buckets ?? [] };
102 }
103
104 /** Puts an app in the namespace, replacing what was there. */
105 async putScript(
106 script: string,
107 worker: BuiltWorker,
108 completionJwt: string | null,
109 tags: string[],
110 /** The repository's entries for running apps, over the project's own `vars`. */
111 runtime: { secrets: Record<string, string>; variables: Record<string, string> } = { secrets: {}, variables: {} },
112 ): Promise<void> {
113 const form = new FormData();
114 const modules = worker.modules?.length ? worker.modules : null;
115 const assetsBinding = worker.assetsBinding || "ASSETS";
116 const vars: Record<string, unknown> = { ...(worker.vars ?? {}), ...runtime.variables };
117 for (const name of Object.keys(runtime.secrets)) delete vars[name];
118 const bindings: object[] = Object.entries(vars).map(([name, value]) =>
119 typeof value === "string"
120 ? { type: "plain_text", name, text: value }
121 : { type: "json", name, json: value },
122 );
123 for (const [name, text] of Object.entries(runtime.secrets)) bindings.push({ type: "secret_text", name, text });
124 if (completionJwt) bindings.push({ type: "assets", name: assetsBinding });
125 const assetsConfig: Record<string, string> = {};
126 if (worker.htmlHandling && HTML_HANDLING.includes(worker.htmlHandling)) {
127 assetsConfig.html_handling = worker.htmlHandling;
128 }
129 if (worker.notFoundHandling && NOT_FOUND_HANDLING.includes(worker.notFoundHandling)) {
130 assetsConfig.not_found_handling = worker.notFoundHandling;
131 }
132 if (worker._headers) assetsConfig._headers = worker._headers;
133 if (worker._redirects) assetsConfig._redirects = worker._redirects;
134 const mainModule = modules ? worker.mainModule ?? modules[0].name : "index.js";
135 form.append(
136 "metadata",
137 JSON.stringify({
138 main_module: mainModule,
139 compatibility_date: worker.compatibilityDate ?? "2026-09-26",
140 compatibility_flags: worker.compatibilityFlags ?? [],
141 bindings,
142 tags,
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.143 // Each request it answers, with its logs, goes to observability (src/telemetry.ts).
144 tail_consumers: TAIL_CONSUMERS,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor145 ...(completionJwt ? { assets: { jwt: completionJwt, config: assetsConfig } } : {}),
146 }),
147 );
148 if (modules) {
149 for (const module of modules) {
150 const bytes = Uint8Array.from(atob(module.contentBase64), (c) => c.charCodeAt(0));
151 form.append(module.name, new File([bytes], module.name, { type: module.contentType }));
152 }
153 } else {
154 if (!completionJwt) throw new Error("The build produced neither code nor files to serve.");
155 form.append(
156 "index.js",
157 new File([ASSETS_ONLY], "index.js", { type: "application/javascript+module" }),
158 );
159 }
160 await this.call("PUT", this.scriptPath(script), form);
161 }
162
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.163 // ---- Container apps (containers.ts) ---------------------------------
164
165 /**
166 * A login to Cloudflare's registry for this account: push only (never
167 * pull, so a build cannot read another app's image), for minutes.
168 */
169 async registryLogin(permissions: ("push" | "pull")[] = ["push"]): Promise<{ username: string; password: string }> {
170 const body = permissions.includes("pull") ? { permissions, expiration_minutes: 5 } : pushLoginRequest();
171 const result = await this.call<{ username: string; password?: string }>(
172 "POST",
173 `/accounts/${this.account}/containers/registries/${REGISTRY_HOST}/credentials`,
174 JSON.stringify(body),
175 "application/json",
176 );
177 if (!result.password) throw new Error("Cloudflare's registry gave no password.");
178 return { username: result.username, password: result.password };
179 }
180
181 /**
182 * Whether `repository` holds the image `digest`: asked of the registry
183 * itself, with a pull login of g1t's own, through the token exchange
184 * registries use (`WWW-Authenticate: Bearer`).
185 */
186 async hasImage(repository: string, digest: string): Promise<boolean> {
187 const login = await this.registryLogin(["pull"]);
188 const basic = `Basic ${btoa(`${login.username}:${login.password}`)}`;
189 const url = `https://${REGISTRY_HOST}/v2/${repository}/manifests/${digest}`;
190 const accept = "application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json";
191 let response = await fetch(url, { method: "HEAD", headers: { authorization: basic, accept } });
192 const challenge = response.headers.get("www-authenticate") ?? "";
193 if (response.status === 401 && /^bearer /i.test(challenge)) {
194 const params = Object.fromEntries([...challenge.matchAll(/(\w+)="([^"]*)"/g)].map((m) => [m[1], m[2]]));
195 const token = new URL(params.realm);
196 if (params.service) token.searchParams.set("service", params.service);
197 token.searchParams.set("scope", params.scope ?? `repository:${repository}:pull`);
198 const answer = (await (await fetch(token, { headers: { authorization: basic } })).json().catch(() => null)) as {
199 token?: string;
200 access_token?: string;
201 } | null;
202 const bearer = answer?.token ?? answer?.access_token;
203 if (!bearer) return false;
204 response = await fetch(url, { method: "HEAD", headers: { authorization: `Bearer ${bearer}`, accept } });
205 }
206 return response.ok;
207 }
208
209 /** Puts a container app's Worker (`APP_WORKER`, g1t's own) in the namespace, with `metadata`. */
210 async putContainerWorker(script: string, metadata: Record<string, unknown>): Promise<void> {
211 const form = new FormData();
212 form.append("metadata", JSON.stringify(metadata));
213 form.append("index.js", new File([APP_WORKER], "index.js", { type: "application/javascript+module" }));
214 await this.call("PUT", this.scriptPath(script), form);
215 }
216
217 /** The Durable Object namespace of a container app's Worker: what its Container application is attached to. */
218 async appNamespace(script: string): Promise<string> {
219 for (let page = 1; page <= 20; page++) {
220 const namespaces = await this.call<{ id: string; class: string; script: string; dispatch_namespace?: string }[]>(
221 "GET",
222 `/accounts/${this.account}/workers/durable_objects/namespaces?per_page=1000&page=${page}`,
223 );
224 const found = namespaces.find((n) => n.class === APP_CLASS && n.script === script && n.dispatch_namespace === this.namespace);
225 if (found) return found.id;
226 if (namespaces.length < 1000) break;
227 }
228 throw new Error(`The app's Durable Object namespace was not found for ${script}.`);
229 }
230
231 /** The Container application named `name`, if there is one. */
232 async containerApplication(name: string): Promise<{ id: string } | null> {
233 const apps = await this.call<{ id: string; name: string }[]>("GET", `/accounts/${this.account}/containers/applications`);
234 return apps.find((app) => app.name === name) ?? null;
235 }
236
237 /**
238 * Runs `image` at `instanceType` for the app `script`: its Container
239 * application is made the first time, and rolled out to after.
240 */
241 async runContainer(script: string, image: string, instanceType: string, description: string): Promise<string> {
242 const name = applicationName(script);
243 const existing = await this.containerApplication(name);
244 if (existing) {
245 await this.call(
246 "POST",
247 `/accounts/${this.account}/containers/applications/${existing.id}/rollouts`,
248 JSON.stringify(rolloutRequest(image, instanceType, description)),
249 "application/json",
250 );
251 return existing.id;
252 }
253 const namespaceId = await this.appNamespace(script);
254 const created = await this.call<{ id: string }>(
255 "POST",
256 `/accounts/${this.account}/containers/applications`,
257 JSON.stringify(applicationRequest(name, image, instanceType, namespaceId)),
258 "application/json",
259 );
260 return created.id;
261 }
262
263 /** Deletes the app's Container application, and its instances with it. Already gone is fine. */
264 async deleteContainer(script: string, id?: string | null): Promise<void> {
265 const found = id ? { id } : await this.containerApplication(applicationName(script)).catch(() => null);
266 if (!found) return;
267 try {
268 await this.call("DELETE", `/accounts/${this.account}/containers/applications/${found.id}`);
269 } catch (error) {
270 if (!/404|not found/i.test(String(error))) throw error;
271 }
272 }
273
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor274 /** Every app in the namespace, with when it was last changed. */
275 async listScripts(): Promise<{ id: string; modified_on: string }[]> {
276 return this.call<{ id: string; modified_on: string }[]>(
277 "GET",
278 `/accounts/${this.account}/workers/dispatch/namespaces/${this.namespace}/scripts`,
279 );
280 }
281
282 /**
283 * Takes an app down. Already gone is fine.
284 *
285 * A deleted app can keep answering for a while where Cloudflare still has
286 * it warm. So it is first replaced by a notice that it is down, which
287 * reaches the edge as fast as any deploy, and deleted after.
288 */
289 async deleteScript(script: string): Promise<void> {
290 await this.placeholder(script, TAKEN_DOWN, "taken-down").catch(() => undefined);
291 try {
292 await this.call("DELETE", `${this.scriptPath(script)}?force=true`);
293 } catch (error) {
294 if (!/404|not found|10007/i.test(String(error))) throw error;
295 }
296 }
297
298 /**
299 * Replaces an app with a notice that it is paused: its workspace reached
300 * its limit. The notice costs next to nothing to answer with, and the app
301 * comes back by being deployed again.
302 */
303 async pauseScript(script: string): Promise<void> {
304 await this.placeholder(script, PAUSED, "paused");
305 }
306
307 /**
308 * Replaces an app with a redirect to `targetHost`, path and query kept:
309 * what an app's old address answers after its workspace is renamed.
310 */
311 async redirectScript(script: string, targetHost: string): Promise<void> {
312 await this.placeholder(script, redirectScript(targetHost), "redirect");
313 }
314
315 private async placeholder(script: string, code: string, tag: string): Promise<void> {
316 const form = new FormData();
317 form.append(
318 "metadata",
319 JSON.stringify({ main_module: "index.js", compatibility_date: "2026-09-26", bindings: [], tags: [tag] }),
320 );
321 form.append("index.js", new File([code], "index.js", { type: "application/javascript+module" }));
322 await this.call("PUT", this.scriptPath(script), form);
323 }
324
325 /**
326 * Requests and CPU time per app over a period, from Workers analytics.
327 * Apps with no traffic are absent.
328 */
329 async usage(
330 scripts: string[],
331 since: string,
332 until: string,
333 ): Promise<Map<string, { requests: number; cpuMs: number }>> {
334 const totals = new Map<string, { requests: number; cpuMs: number }>();
335 if (scripts.length === 0) return totals;
336 const query = (withCpu: boolean) => `query ($account: string!, $since: Time!, $until: Time!, $scripts: [string!]) {
337 viewer { accounts(filter: { accountTag: $account }) {
338 workersInvocationsAdaptive(limit: 10000, filter: { datetime_geq: $since, datetime_lt: $until, scriptName_in: $scripts }) {
339 sum { requests${withCpu ? " cpuTimeUs" : ""} }
340 dimensions { scriptName }
341 }
342 } }
343 }`;
344 type Row = { sum: { requests: number; cpuTimeUs?: number }; dimensions: { scriptName: string } };
345 const ask = async (withCpu: boolean) => {
346 const response = await fetch(`${API}/graphql`, {
347 method: "POST",
348 headers: { authorization: `Bearer ${this.token}`, "content-type": "application/json" },
349 body: JSON.stringify({
350 query: query(withCpu),
351 variables: { account: this.account, since, until, scripts },
352 }),
353 });
354 return (await response.json()) as {
355 data?: { viewer: { accounts: { workersInvocationsAdaptive: Row[] }[] } };
356 errors?: { message: string }[] | null;
357 };
358 };
359 let answer = await ask(true);
360 // CPU time is counted where analytics offers it; requests always.
361 if (answer.errors?.length) answer = await ask(false);
362 if (answer.errors?.length || !answer.data) {
363 throw new Error(`Workers analytics refused: ${answer.errors?.map((e) => e.message).join("; ")}`);
364 }
365 for (const row of answer.data.viewer.accounts[0]?.workersInvocationsAdaptive ?? []) {
366 const seen = totals.get(row.dimensions.scriptName) ?? { requests: 0, cpuMs: 0 };
367 seen.requests += row.sum.requests;
368 seen.cpuMs += Math.ceil((row.sum.cpuTimeUs ?? 0) / 1000);
369 totals.set(row.dimensions.scriptName, seen);
370 }
371 return totals;
372 }
373}

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