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

Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP1#!/usr/bin/env node
2// Releases of the g1t CLI (crates/g1t): built for every platform,
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.3// checksummed, signed, and published to the g1t-downloads R2 bucket that
4// g1t.sh serves at /downloads/cli/ (apps/web/app/routes/downloads-runner.ts).
5// g1t.sh/install and g1t.sh/install.ps1 (apps/web/app/content/install/)
6// install the newest one.
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP7//
8// node scripts/cli-release.mjs build # every platform, in the cargo-zigbuild image (Docker)
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.9// node scripts/cli-release.mjs manifest # SHA256SUMS, manifest.json and latest.json from what was built
10// node scripts/cli-release.mjs sign # latest.json.sig
11// node scripts/cli-release.mjs verify # checks the signature
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP12// node scripts/cli-release.mjs publish [--dry-run]
13//
14// At cli/<version>/ in the bucket: g1t-linux-x64, -linux-arm64,
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.15// -macos-x64, -macos-arm64, -windows-x64.exe, SHA256SUMS, manifest.json and
16// latest.json.sig; at cli/: latest.json, the newest release's manifest, and
17// latest.json.sig, its Ed25519 signature in base64. SHA256SUMS is what the
18// install scripts check a download against; the signature is what a CLI
19// that updates itself will check, with the same release key as the
20// runner's (scripts/runner-release.mjs keygen):
21//
22// RUNNER_RELEASE_KEY the private key: a g1t Actions secret
23// G1T_RUNNER_RELEASE_KEY the public key, for `verify`
24//
25// The macOS builds link Apple's Security framework (the keychain), whose
26// SDK the rust-cross/cargo-zigbuild image carries, so `build` runs there.
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP27
28import { spawnSync } from "node:child_process";
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.29import { createHash, createPrivateKey } from "node:crypto";
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP30import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
Merge branch 'worktree-agent-a3b9b1f730f55b5bf'31import { homedir } from "node:os";
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP32import { dirname, join } from "node:path";
33import { fileURLToPath } from "node:url";
34
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.35import { signBytes, verifyBytes } from "./runner-release.mjs";
36
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP37const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
38const BUCKET = "g1t-downloads";
Merge branch 'worktree-agent-a3b9b1f730f55b5bf'39// Pinned by digest, so a release builds with the toolchain (Rust 1.93.0,
40// zig 0.16.0) its cache was built with, and `latest` moving never changes a
41// release under it. To move on: `docker pull ghcr.io/rust-cross/cargo-zigbuild:latest`
42// and take the digest from `docker image inspect --format '{{json .RepoDigests}}'`.
43// The Caches workflow keys the build's cache by it (ci-cache.mjs keys:
44// zigbuild), so a new digest starts a new entry.
45export const BUILDER = "ghcr.io/rust-cross/cargo-zigbuild@sha256:d8313491ec5798de0633fdc1c5753761bff79967bea69076020dc78121b2cca8";
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP46export const TARGETS = {
47 "linux-x64": { triple: "x86_64-unknown-linux-musl", file: "g1t-linux-x64" },
48 "linux-arm64": { triple: "aarch64-unknown-linux-musl", file: "g1t-linux-arm64" },
49 "macos-x64": { triple: "x86_64-apple-darwin", file: "g1t-macos-x64" },
50 "macos-arm64": { triple: "aarch64-apple-darwin", file: "g1t-macos-arm64" },
51 "windows-x64": { triple: "x86_64-pc-windows-gnu", file: "g1t-windows-x64.exe", exe: true },
52};
53
54export function version() {
55 const found = /^version\s*=\s*"([^"]+)"/m.exec(readFileSync(join(ROOT, "crates/g1t/Cargo.toml"), "utf8"));
56 if (!found) throw new Error("crates/g1t/Cargo.toml has no version");
57 return found[1];
58}
59
60const outDir = (v = version()) => join(ROOT, "target", "cli-release", v);
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.61export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP62
63function run(command, args, options = {}) {
64 const done = spawnSync(command, args, { stdio: "inherit", cwd: ROOT, ...options });
65 if (done.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`);
66}
67
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.68/**
69 * The release's manifest, from the binaries in `dir`: each platform's file
70 * with its SHA-256. Writes SHA256SUMS, manifest.json and latest.json there.
71 */
72export function manifest(dir, v, now = new Date()) {
73 const files = {};
74 for (const [platform, target] of Object.entries(TARGETS)) {
75 const path = join(dir, target.file);
76 if (!existsSync(path)) continue;
77 files[platform] = { name: target.file, sha256: sha256(readFileSync(path)) };
78 }
79 if (Object.keys(files).length === 0) throw new Error(`no binaries in ${dir}: node scripts/cli-release.mjs build`);
80 const body = { version: v, published_at: now.toISOString(), files };
81 const text = `${JSON.stringify(body, null, 2)}\n`;
82 writeFileSync(join(dir, "manifest.json"), text);
83 writeFileSync(join(dir, "latest.json"), text);
84 writeFileSync(join(dir, "SHA256SUMS"), Object.values(files).map((f) => `${f.sha256} ${f.name}`).join("\n") + "\n");
85 return body;
86}
87
Merge branch 'worktree-agent-a3b9b1f730f55b5bf'88/**
89 * The builder container's flags. Its target is target/zig in the
90 * checkout, which the workflows cache (cli-release-…). In a workflow job,
91 * Cargo's registry is the job's own ~/.cargo/registry, which the crates
92 * entry (cargo-crates-…) restores; elsewhere a Docker volume keeps it.
93 */
94export function builderArgs({ root = ROOT, env = process.env, home = homedir() } = {}) {
95 const registry = env.GITHUB_ACTIONS === "true" ? `${join(env.CARGO_HOME || join(home, ".cargo"), "registry").replaceAll("\\", "/")}` : "g1t-cargo-registry";
96 return [
97 "-e", "CARGO_TARGET_DIR=/src/target/zig",
98 "-v", `${root.replaceAll("\\", "/")}:/src`,
99 "-v", `${registry}:/usr/local/cargo/registry`,
100 "-w", "/src", BUILDER,
101 ];
102}
103
104/**
105 * What the container runs. It runs as root, so on a host with users what
106 * it wrote is handed back to the one running this, for the cache to save
107 * and prune.
108 */
109export function builderScript(triples, owner = process.getuid ? `${process.getuid()}:${process.getgid()}` : null) {
110 const build = `set -e; for t in ${triples}; do rustup target add $t >/dev/null 2>&1; cargo zigbuild --release --locked --package g1t --target $t; done`;
111 return owner ? `trap 'chown -R ${owner} /src/target/zig /usr/local/cargo/registry' EXIT; ${build}` : build;
112}
113
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP114function build() {
115 const v = version();
116 const dir = outDir(v);
117 mkdirSync(dir, { recursive: true });
118 const triples = Object.values(TARGETS).map((t) => t.triple).join(" ");
119 // One container builds every platform; its target folder is kept apart
120 // from the host's so the two never mix.
Merge branch 'worktree-agent-a3b9b1f730f55b5bf'121 run("docker", ["run", "--rm", ...builderArgs(), "sh", "-c", builderScript(triples)], { env: { ...process.env, MSYS_NO_PATHCONV: "1" } });
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.122 for (const target of Object.values(TARGETS)) {
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP123 const built = join(ROOT, "target", "zig", target.triple, "release", target.exe ? "g1t.exe" : "g1t");
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.124 writeFileSync(join(dir, target.file), readFileSync(built));
125 }
126 const m = manifest(dir, v);
127 console.log(`built ${Object.keys(m.files).length} binaries of g1t ${v} into ${dir}`);
128}
129
130function privateKey(text = process.env.RUNNER_RELEASE_KEY) {
131 if (!text) throw new Error("RUNNER_RELEASE_KEY is not set");
132 return createPrivateKey({ key: Buffer.from(text, "base64"), format: "der", type: "pkcs8" });
133}
134
135function signRelease() {
136 const dir = outDir();
137 const bytes = readFileSync(join(dir, "latest.json"));
138 writeFileSync(join(dir, "latest.json.sig"), signBytes(bytes, privateKey()));
139 console.log(`signed g1t ${version()}`);
140}
141
142function verifyRelease() {
143 const dir = outDir();
144 const publicRaw = process.env.G1T_RUNNER_RELEASE_KEY;
145 if (!publicRaw) throw new Error("G1T_RUNNER_RELEASE_KEY is not set");
146 const ok = verifyBytes(readFileSync(join(dir, "latest.json")), readFileSync(join(dir, "latest.json.sig"), "utf8"), publicRaw);
147 if (!ok) throw new Error("latest.json's signature is not the release key's");
148 const m = JSON.parse(readFileSync(join(dir, "latest.json"), "utf8"));
149 for (const file of Object.values(m.files)) {
150 if (sha256(readFileSync(join(dir, file.name))) !== file.sha256) throw new Error(`${file.name} is not the file the manifest names`);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP151 }
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.152 console.log(`g1t ${m.version}: the signature and every file check out`);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP153}
154
155function publish(dryRun) {
156 const v = version();
157 const dir = outDir(v);
158 if (!existsSync(join(dir, "manifest.json"))) throw new Error(`nothing built for ${v}: node scripts/cli-release.mjs build`);
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.159 const signed = existsSync(join(dir, "latest.json.sig"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP160 const put = (key, file, type) => {
161 if (dryRun) return console.log(`would put ${key}`);
162 run("npx", ["wrangler", "r2", "object", "put", `${BUCKET}/${key}`, "--file", file, "--remote", "--content-type", type], {
163 shell: process.platform === "win32",
164 // Away from the repository's .env, which may hold a token meant for
165 // something else (as scripts/deploy does).
166 cwd: join(ROOT, "apps/web"),
167 });
168 };
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.169 const m = JSON.parse(readFileSync(join(dir, "manifest.json"), "utf8"));
170 for (const file of Object.values(m.files)) put(`cli/${v}/${file.name}`, join(dir, file.name), "application/octet-stream");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP171 put(`cli/${v}/manifest.json`, join(dir, "manifest.json"), "application/json");
172 put(`cli/${v}/SHA256SUMS`, join(dir, "SHA256SUMS"), "text/plain");
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.173 if (signed) put(`cli/${v}/latest.json.sig`, join(dir, "latest.json.sig"), "text/plain");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP174 // Last, so `latest` never names a version whose files are not up yet.
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.175 if (signed) put("cli/latest.json.sig", join(dir, "latest.json.sig"), "text/plain");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP176 put("cli/latest.json", join(dir, "latest.json"), "application/json");
177}
178
179if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/cli-release.mjs")) {
180 const [command, ...rest] = process.argv.slice(2);
181 if (command === "build") build();
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.182 else if (command === "manifest") manifest(outDir(), version());
183 else if (command === "sign") signRelease();
184 else if (command === "verify") verifyRelease();
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP185 else if (command === "publish") publish(rest.includes("--dry-run"));
186 else {
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.187 console.error("usage: node scripts/cli-release.mjs build|manifest|sign|verify|publish [--dry-run]");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP188 process.exit(2);
189 }
190}

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