gearbox-code 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.mjs +456 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,9 @@ Common setup commands:
38
38
  ```bash
39
39
  gearbox auth add <api-key> # auto-detects known key prefixes
40
40
  gearbox auth add <provider> <api-key> # anthropic, openai, google, deepseek, openrouter, groq, xai, mistral...
41
+ gearbox auth add codex # ChatGPT subscription through the Codex CLI
42
+ gearbox auth add codex work # second ChatGPT account, isolated CODEX_HOME
43
+ gearbox auth add claude work # second Claude account, isolated config
41
44
  gearbox auth import # import credentials from env/cloud config
42
45
  gearbox auth providers # list supported providers
43
46
  ```
package/dist/cli.mjs CHANGED
@@ -64452,6 +64452,17 @@ function cleanCliStderr(text2) {
64452
64452
  }
64453
64453
  return cleaned;
64454
64454
  }
64455
+ function cliFailureMessage(binary, stderr, opts = {}) {
64456
+ const err = cleanCliStderr(stderr);
64457
+ const isCodex = binary.includes("codex");
64458
+ if (isCodex && /app_session_terminated|Your session has ended|Failed to refresh token|HTTP error: 401 Unauthorized/i.test(err)) {
64459
+ const account = opts.accountLabel ? ` for ${opts.accountLabel}` : "";
64460
+ const relogin = opts.reloginCommand ? ` Run ${opts.reloginCommand} to sign in again, then /retry.` : " Sign in to that Codex account again, then /retry.";
64461
+ return `Codex session expired${account}.${relogin}`;
64462
+ }
64463
+ const hint = isCodex ? "Codex CLI failed before returning an assistant message. Check the line above, then /retry." : `${binary} failed before returning an assistant message. Check the line above, then /retry.`;
64464
+ return err ? `${hint} ${err}` : hint;
64465
+ }
64455
64466
  async function runCliTask(opts) {
64456
64467
  const { binary, prompt, messages, onEvent, signal } = opts;
64457
64468
  const args = buildCliArgs(binary, prompt, { sessionId: opts.sessionId, autoApprove: opts.autoApprove, modelId: opts.modelId, effort: opts.effort });
@@ -64513,8 +64524,7 @@ async function runCliTask(opts) {
64513
64524
  if (!signal?.aborted) {
64514
64525
  const err = cleanCliStderr(stderr);
64515
64526
  if ((proc.exitCode ?? 0) !== 0) {
64516
- const hint = binary.includes("codex") ? "Codex CLI failed before returning an assistant message. Check the line above, then /retry." : `${binary} failed before returning an assistant message. Check the line above, then /retry.`;
64517
- onEvent({ type: "error", message: err ? `${hint} ${err}` : hint });
64527
+ onEvent({ type: "error", message: cliFailureMessage(binary, stderr, { accountLabel: opts.accountLabel, reloginCommand: opts.reloginCommand }) });
64518
64528
  } else if (!state.text && !sawEvent && err) {
64519
64529
  onEvent({ type: "error", message: `${binary} produced no JSON output: ${err}` });
64520
64530
  } else if (!state.text && !sawEvent) {
@@ -70303,7 +70313,7 @@ var import_react21 = __toESM(require_react(), 1);
70303
70313
  import { createInterface } from "node:readline/promises";
70304
70314
  import { execFileSync as execFileSync4, spawnSync } from "node:child_process";
70305
70315
  import { resolve as resolve11 } from "node:path";
70306
- import { existsSync as existsSync8 } from "node:fs";
70316
+ import { existsSync as existsSync10 } from "node:fs";
70307
70317
 
70308
70318
  // src/ui/App.tsx
70309
70319
  var import_react26 = __toESM(require_react(), 1);
@@ -132179,6 +132189,70 @@ async function runShellStream(command, opts = {}) {
132179
132189
 
132180
132190
  // src/tools.ts
132181
132191
  init_proc();
132192
+
132193
+ // src/fetch.ts
132194
+ var MAX_FETCH_CHARS = 80000;
132195
+ var MAX_RETURN_CHARS = 20000;
132196
+ var ENTITY = {
132197
+ amp: "&",
132198
+ lt: "<",
132199
+ gt: ">",
132200
+ quot: '"',
132201
+ apos: "'",
132202
+ nbsp: " "
132203
+ };
132204
+ function urlsInText(text2, limit = 2) {
132205
+ const out = [];
132206
+ const re = /\bhttps?:\/\/[^\s<>"')\]]+/gi;
132207
+ for (const m2 of text2.matchAll(re)) {
132208
+ const url2 = m2[0].replace(/[.,;:!?]+$/, "");
132209
+ if (!out.includes(url2))
132210
+ out.push(url2);
132211
+ if (out.length >= limit)
132212
+ break;
132213
+ }
132214
+ return out;
132215
+ }
132216
+ function decodeEntities(text2) {
132217
+ return text2.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (_, raw) => {
132218
+ const key = raw.toLowerCase();
132219
+ if (key[0] === "#") {
132220
+ const n = key[1] === "x" ? Number.parseInt(key.slice(2), 16) : Number.parseInt(key.slice(1), 10);
132221
+ return Number.isFinite(n) ? String.fromCodePoint(n) : "";
132222
+ }
132223
+ return ENTITY[key] ?? "";
132224
+ });
132225
+ }
132226
+ function stripHtml(html2) {
132227
+ return decodeEntities(html2.replace(/<script\b[\s\S]*?<\/script>/gi, " ").replace(/<style\b[\s\S]*?<\/style>/gi, " ").replace(/<noscript\b[\s\S]*?<\/noscript>/gi, " ").replace(/<\/(p|div|section|article|header|footer|li|tr|h[1-6])>/gi, `
132228
+ `).replace(/<br\s*\/?>/gi, `
132229
+ `).replace(/<[^>]+>/g, " ").replace(/[ \t]+\n/g, `
132230
+ `).replace(/\n{3,}/g, `
132231
+
132232
+ `).replace(/[ \t]{2,}/g, " ").trim());
132233
+ }
132234
+ async function fetchUrlText(url2) {
132235
+ const u = new URL(url2);
132236
+ if (u.protocol !== "http:" && u.protocol !== "https:")
132237
+ throw new Error("only http(s) URLs are supported");
132238
+ const res = await fetch(u, {
132239
+ headers: {
132240
+ "user-agent": "Gearbox/0.1 (+https://github.com/AnayGarodia/gearbox)",
132241
+ accept: "text/html,text/plain,application/json;q=0.8,*/*;q=0.2"
132242
+ }
132243
+ });
132244
+ if (!res.ok)
132245
+ throw new Error(`fetch failed: HTTP ${res.status}`);
132246
+ const raw = (await res.text()).slice(0, MAX_FETCH_CHARS);
132247
+ const contentType = res.headers.get("content-type") ?? "";
132248
+ const title = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.replace(/\s+/g, " ").trim();
132249
+ const text2 = (/html/i.test(contentType) || /<html|<body|<p\b|<div\b/i.test(raw) ? stripHtml(raw) : raw.trim()).slice(0, MAX_RETURN_CHARS);
132250
+ if (!text2)
132251
+ throw new Error("fetched URL had no readable text");
132252
+ return { url: u.toString(), title: title ? decodeEntities(title) : undefined, text: text2, chars: text2.length };
132253
+ }
132254
+
132255
+ // src/tools.ts
132182
132256
  var ROOT = process.cwd();
132183
132257
  var CAP2 = 60000;
132184
132258
  var DENIED = "Permission denied by the user — they declined this action.";
@@ -132192,6 +132266,44 @@ function safe(path) {
132192
132266
  }
132193
132267
  var clip2 = (s2) => s2.length > CAP2 ? s2.slice(0, CAP2) + `
132194
132268
  … [clipped ${s2.length - CAP2} chars]` : s2;
132269
+ function countOccurrences(text2, find2) {
132270
+ if (!find2)
132271
+ return 0;
132272
+ let count = 0;
132273
+ let at3 = 0;
132274
+ while ((at3 = text2.indexOf(find2, at3)) >= 0) {
132275
+ count++;
132276
+ at3 += find2.length;
132277
+ }
132278
+ return count;
132279
+ }
132280
+ function replaceOccurrence(text2, find2, replace2, occurrence) {
132281
+ let at3 = -1;
132282
+ let from = 0;
132283
+ for (let i2 = 0;i2 < occurrence; i2++) {
132284
+ at3 = text2.indexOf(find2, from);
132285
+ if (at3 < 0)
132286
+ return text2;
132287
+ from = at3 + find2.length;
132288
+ }
132289
+ return text2.slice(0, at3) + replace2 + text2.slice(at3 + find2.length);
132290
+ }
132291
+ function notFoundHint(path, before2, find2) {
132292
+ const needle = find2.trim().split(/\s+/).filter((w) => w.length >= 3)[0]?.toLowerCase();
132293
+ if (!needle)
132294
+ return `text not found in ${path}`;
132295
+ const lines = before2.split(`
132296
+ `);
132297
+ const hit = lines.findIndex((l) => l.toLowerCase().includes(needle));
132298
+ if (hit < 0)
132299
+ return `text not found in ${path}`;
132300
+ const start = Math.max(0, hit - 2);
132301
+ const end = Math.min(lines.length, hit + 3);
132302
+ const snippet = lines.slice(start, end).map((l, i2) => `${start + i2 + 1}: ${l}`).join(`
132303
+ `);
132304
+ return `text not found in ${path}. Nearby match for "${needle}":
132305
+ ${snippet}`;
132306
+ }
132195
132307
  function createTools(onEvent) {
132196
132308
  return {
132197
132309
  read_file: tool5({
@@ -132214,19 +132326,38 @@ function createTools(onEvent) {
132214
132326
  }
132215
132327
  }),
132216
132328
  edit_file: tool5({
132217
- description: "Replace the first exact occurrence of `find` with `replace` in a file.",
132218
- inputSchema: exports_external2.object({ path: exports_external2.string(), find: exports_external2.string(), replace: exports_external2.string() }),
132219
- execute: async ({ path, find: find2, replace: replace2 }) => {
132329
+ description: "Edit a file by exact text replacement. Use occurrence for a specific match, or replaceAll for every exact match.",
132330
+ inputSchema: exports_external2.object({
132331
+ path: exports_external2.string(),
132332
+ find: exports_external2.string().min(1),
132333
+ replace: exports_external2.string(),
132334
+ occurrence: exports_external2.number().int().positive().default(1).describe("1-based match to replace when replaceAll is false"),
132335
+ replaceAll: exports_external2.boolean().default(false).describe("replace every exact occurrence")
132336
+ }),
132337
+ execute: async ({ path, find: find2, replace: replace2, occurrence, replaceAll }) => {
132220
132338
  const abs = safe(path);
132221
132339
  const before2 = await readFile(abs, "utf8");
132222
- if (!before2.includes(find2))
132223
- throw new Error(`text not found in ${path}`);
132340
+ const matches2 = countOccurrences(before2, find2);
132341
+ if (matches2 === 0)
132342
+ throw new Error(notFoundHint(path, before2, find2));
132343
+ if (!replaceAll && occurrence > matches2)
132344
+ throw new Error(`only found ${matches2} occurrence${matches2 === 1 ? "" : "s"} in ${path}; requested occurrence ${occurrence}`);
132224
132345
  if (!await requestPermission({ kind: "edit", title: "Edit a file", detail: path }))
132225
132346
  throw new Error(DENIED);
132226
- const after2 = before2.replace(find2, replace2);
132347
+ const after2 = replaceAll ? before2.split(find2).join(replace2) : replaceOccurrence(before2, find2, replace2, occurrence);
132227
132348
  await writeFile2(abs, after2, "utf8");
132228
132349
  const diff2 = computeDiff(before2, after2);
132229
- return { summary: `edited ${path} (${diffStat(diff2)})`, diff: diff2 };
132350
+ const changed = replaceAll ? matches2 : 1;
132351
+ return { summary: `edited ${path} · ${changed} replacement${changed === 1 ? "" : "s"} (${diffStat(diff2)})`, diff: diff2 };
132352
+ }
132353
+ }),
132354
+ fetch_url: tool5({
132355
+ description: "Fetch a public http(s) URL and return readable text. Use this for docs, release notes, issue pages, or pasted links.",
132356
+ inputSchema: exports_external2.object({ url: exports_external2.string().url() }),
132357
+ execute: async ({ url: url2 }) => {
132358
+ const page = await fetchUrlText(url2);
132359
+ return clip2([`URL: ${page.url}`, page.title ? `Title: ${page.title}` : "", "", page.text].filter(Boolean).join(`
132360
+ `));
132230
132361
  }
132231
132362
  }),
132232
132363
  search: tool5({
@@ -132332,7 +132463,8 @@ var readOnlyTools = {
132332
132463
  read_file: tools.read_file,
132333
132464
  list_dir: tools.list_dir,
132334
132465
  search: tools.search,
132335
- glob: tools.glob
132466
+ glob: tools.glob,
132467
+ fetch_url: tools.fetch_url
132336
132468
  };
132337
132469
 
132338
132470
  // node_modules/js-tiktoken/dist/chunk-VL2OQCWN.js
@@ -132789,6 +132921,42 @@ function retrieveFiles(query, cwd2 = process.cwd(), k = 6, budget = 8000, modelI
132789
132921
  return out;
132790
132922
  }
132791
132923
 
132924
+ // src/context/git.ts
132925
+ init_proc();
132926
+ function git(args, cwd2) {
132927
+ const r2 = spawnSyncProc(["git", ...args], { cwd: cwd2, stdout: "pipe", stderr: "ignore" });
132928
+ return r2.exitCode === 0 ? r2.stdout.toString().trim() : "";
132929
+ }
132930
+ function gitContext(cwd2 = process.cwd()) {
132931
+ if (!git(["rev-parse", "--is-inside-work-tree"], cwd2))
132932
+ return "";
132933
+ const branch = git(["branch", "--show-current"], cwd2) || git(["rev-parse", "--short", "HEAD"], cwd2);
132934
+ const status = git(["status", "--short"], cwd2);
132935
+ const staged = git(["diff", "--cached", "--stat"], cwd2);
132936
+ const unstaged = git(["diff", "--stat"], cwd2);
132937
+ const commits = git(["log", "--oneline", "-5"], cwd2);
132938
+ const parts = [];
132939
+ if (branch)
132940
+ parts.push(`branch: ${branch}`);
132941
+ if (status)
132942
+ parts.push(`dirty files:
132943
+ ${status}`);
132944
+ else
132945
+ parts.push("working tree: clean");
132946
+ if (staged)
132947
+ parts.push(`staged diff stat:
132948
+ ${staged}`);
132949
+ if (unstaged)
132950
+ parts.push(`unstaged diff stat:
132951
+ ${unstaged}`);
132952
+ if (commits)
132953
+ parts.push(`recent commits:
132954
+ ${commits}`);
132955
+ return parts.join(`
132956
+
132957
+ `);
132958
+ }
132959
+
132792
132960
  // src/context/builder.ts
132793
132961
  var BASE_SYSTEM = `You are Gearbox, a precise terminal coding agent.
132794
132962
  Work in small, verifiable steps. Use the tools to read before you write, and
@@ -132900,6 +133068,14 @@ function buildContext(opts) {
132900
133068
  ${memory}`;
132901
133069
  sections.push({ name: "memory", tokens: countTokens(memory, modelId) });
132902
133070
  }
133071
+ const git2 = safe2(() => gitContext(cwd2), "");
133072
+ if (git2) {
133073
+ system += `
133074
+
133075
+ # GIT CONTEXT (current repository state; do not overwrite unrelated user changes)
133076
+ ${git2}`;
133077
+ sections.push({ name: "git", tokens: countTokens(git2, modelId) });
133078
+ }
132903
133079
  const mapBudget = Math.min(4000, Math.floor(inputBudget * 0.05));
132904
133080
  const map3 = safe2(() => repoMap(cwd2, mapBudget), "");
132905
133081
  if (map3) {
@@ -133369,6 +133545,171 @@ ${summary}` },
133369
133545
  return { messages, summarizedTurns: old.length, before: before2, after: after2 };
133370
133546
  }
133371
133547
 
133548
+ // src/init.ts
133549
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
133550
+ import { join as join12 } from "node:path";
133551
+
133552
+ // src/verify.ts
133553
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "node:fs";
133554
+ import { join as join11 } from "node:path";
133555
+ function readJson(path) {
133556
+ try {
133557
+ return JSON.parse(readFileSync12(path, "utf8"));
133558
+ } catch {
133559
+ return null;
133560
+ }
133561
+ }
133562
+ function packageManager(cwd2) {
133563
+ if (existsSync7(join11(cwd2, "bun.lock")) || existsSync7(join11(cwd2, "bun.lockb")))
133564
+ return "bun";
133565
+ if (existsSync7(join11(cwd2, "pnpm-lock.yaml")))
133566
+ return "pnpm";
133567
+ if (existsSync7(join11(cwd2, "yarn.lock")))
133568
+ return "yarn";
133569
+ return "npm";
133570
+ }
133571
+ function packageCommands(cwd2) {
133572
+ const pkg = readJson(join11(cwd2, "package.json"));
133573
+ const scripts = pkg?.scripts ?? {};
133574
+ if (!scripts || typeof scripts !== "object")
133575
+ return [];
133576
+ const pm = packageManager(cwd2);
133577
+ const run = (name31) => pm === "npm" ? `npm run ${name31}` : `${pm} run ${name31}`;
133578
+ const cmds = [];
133579
+ if (scripts.typecheck)
133580
+ cmds.push({ command: run("typecheck"), reason: "typecheck script" });
133581
+ if (scripts.test)
133582
+ cmds.push({ command: pm === "npm" ? "npm test" : pm === "bun" ? "bun test" : `${pm} test`, reason: "test script" });
133583
+ if (scripts.build)
133584
+ cmds.push({ command: run("build"), reason: "build script" });
133585
+ return cmds;
133586
+ }
133587
+ function detectVerificationCommands(cwd2 = process.cwd(), changedFiles = []) {
133588
+ const cmds = packageCommands(cwd2);
133589
+ const hasPython = changedFiles.some((f3) => /\.py$/.test(f3)) || existsSync7(join11(cwd2, "pyproject.toml")) || existsSync7(join11(cwd2, "pytest.ini"));
133590
+ const hasRust = changedFiles.some((f3) => /\.rs$/.test(f3)) || existsSync7(join11(cwd2, "Cargo.toml"));
133591
+ const hasGo = changedFiles.some((f3) => /\.go$/.test(f3)) || existsSync7(join11(cwd2, "go.mod"));
133592
+ if (hasPython && !cmds.some((c) => /\bpytest\b/.test(c.command)))
133593
+ cmds.push({ command: "pytest", reason: "python project" });
133594
+ if (hasRust && !cmds.some((c) => /\bcargo\s+test\b/.test(c.command)))
133595
+ cmds.push({ command: "cargo test", reason: "rust project" });
133596
+ if (hasGo && !cmds.some((c) => /\bgo\s+test\b/.test(c.command)))
133597
+ cmds.push({ command: "go test ./...", reason: "go project" });
133598
+ return cmds.slice(0, 3);
133599
+ }
133600
+ function summarize(output) {
133601
+ const lines = output.split(`
133602
+ `).map((l) => l.trim()).filter(Boolean);
133603
+ const fail = lines.find((l) => /\b(error|failed|failures?|exception|panic)\b/i.test(l));
133604
+ return (fail ?? lines[0] ?? "(no output)").slice(0, 160);
133605
+ }
133606
+ async function runVerification(commands, opts) {
133607
+ const results = [];
133608
+ for (const c of commands) {
133609
+ opts.onEvent({ type: "phase", label: "verifying", detail: `${c.command} · ${c.reason}`, state: "running" });
133610
+ const r2 = await runShellStream(c.command, { signal: opts.signal, timeoutMs: opts.timeoutMs ?? 120000 });
133611
+ results.push(r2);
133612
+ opts.onEvent({ type: "verification", command: c.command, ok: r2.ok, summary: r2.ok ? "passed" : summarize(r2.output) });
133613
+ opts.onEvent({ type: "phase", label: "verification", detail: c.command, state: r2.ok ? "ok" : "err" });
133614
+ if (!r2.ok)
133615
+ break;
133616
+ }
133617
+ return results;
133618
+ }
133619
+
133620
+ // src/init.ts
133621
+ function readJson2(path) {
133622
+ try {
133623
+ return JSON.parse(readFileSync13(path, "utf8"));
133624
+ } catch {
133625
+ return null;
133626
+ }
133627
+ }
133628
+ function rootEntries(cwd2) {
133629
+ try {
133630
+ return readdirSync4(cwd2, { withFileTypes: true }).filter((e2) => ![".git", "node_modules", "dist", "build", ".next", "coverage"].includes(e2.name)).map((e2) => e2.name + (e2.isDirectory() ? "/" : "")).sort().slice(0, 80);
133631
+ } catch {
133632
+ return [];
133633
+ }
133634
+ }
133635
+ function detectStack(cwd2) {
133636
+ const out = [];
133637
+ const pkg = readJson2(join12(cwd2, "package.json"));
133638
+ if (pkg) {
133639
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
133640
+ out.push("JavaScript/TypeScript");
133641
+ if (deps.react || deps.ink)
133642
+ out.push(deps.ink ? "Ink terminal UI" : "React");
133643
+ if (existsSync8(join12(cwd2, "bun.lock")) || existsSync8(join12(cwd2, "bun.lockb")))
133644
+ out.push("Bun");
133645
+ }
133646
+ if (existsSync8(join12(cwd2, "pyproject.toml")))
133647
+ out.push("Python");
133648
+ if (existsSync8(join12(cwd2, "Cargo.toml")))
133649
+ out.push("Rust");
133650
+ if (existsSync8(join12(cwd2, "go.mod")))
133651
+ out.push("Go");
133652
+ return [...new Set(out)];
133653
+ }
133654
+ function packageScripts(cwd2) {
133655
+ const pkg = readJson2(join12(cwd2, "package.json"));
133656
+ const scripts = pkg?.scripts ?? {};
133657
+ return Object.keys(scripts).sort().map((k) => `${k}: ${scripts[k]}`).slice(0, 20);
133658
+ }
133659
+ function existingDocs(cwd2) {
133660
+ return ["README.md", "DESIGN.md", "ROADMAP.md", "VISION.md", "AGENTS.md", "CLAUDE.md"].filter((name31) => existsSync8(join12(cwd2, name31)));
133661
+ }
133662
+ function buildProjectGuide(cwd2 = process.cwd()) {
133663
+ const name31 = readJson2(join12(cwd2, "package.json"))?.name ?? cwd2.split(/[\\/]/).filter(Boolean).at(-1) ?? "project";
133664
+ const stack = detectStack(cwd2);
133665
+ const checks3 = detectVerificationCommands(cwd2).map((c) => c.command);
133666
+ const scripts = packageScripts(cwd2);
133667
+ const entries = rootEntries(cwd2);
133668
+ const docs = existingDocs(cwd2);
133669
+ return `# ${name31} - Gearbox Guide
133670
+
133671
+ ## What This Project Is
133672
+
133673
+ This file was generated from the repository structure so Gearbox has project context before editing.
133674
+ ${stack.length ? `Detected stack: ${stack.join(", ")}.` : "Detected stack: unknown from root files."}
133675
+
133676
+ ## Run And Verify
133677
+
133678
+ ${checks3.length ? checks3.map((c) => `- \`${c}\``).join(`
133679
+ `) : "- No standard verification command was detected. Add one here when known."}
133680
+
133681
+ ## Layout
133682
+
133683
+ ${entries.length ? entries.map((e2) => `- \`${e2}\``).join(`
133684
+ `) : "- Root layout could not be read."}
133685
+
133686
+ ${scripts.length ? `## Package Scripts
133687
+
133688
+ ${scripts.map((s2) => `- \`${s2}\``).join(`
133689
+ `)}
133690
+
133691
+ ` : ""}## Existing Project Docs
133692
+
133693
+ ${docs.length ? docs.map((d) => `- \`${d}\``).join(`
133694
+ `) : "- No common docs detected."}
133695
+
133696
+ ## Agent Conventions
133697
+
133698
+ - Read relevant files before editing.
133699
+ - Keep changes scoped to the user request.
133700
+ - Do not overwrite unrelated dirty work.
133701
+ - Run the verification commands above after edits when practical.
133702
+ `;
133703
+ }
133704
+ function writeProjectGuide(cwd2 = process.cwd()) {
133705
+ const path = join12(cwd2, "GEARBOX.md");
133706
+ const before2 = existsSync8(path) ? readFileSync13(path, "utf8") : "";
133707
+ const after2 = buildProjectGuide(cwd2);
133708
+ writeFileSync7(path, after2, "utf8");
133709
+ const diff2 = computeDiff(before2, after2);
133710
+ return { path, summary: `wrote GEARBOX.md (${diffStat(diff2)})`, diff: diff2 };
133711
+ }
133712
+
133372
133713
  // src/ui/clipboard.ts
133373
133714
  init_proc();
133374
133715
  function osc52(text2) {
@@ -133572,7 +133913,7 @@ function gitBranch() {
133572
133913
  init_proc();
133573
133914
  var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
133574
133915
  import { basename as basename2, extname } from "node:path";
133575
- import { existsSync as existsSync7, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
133916
+ import { existsSync as existsSync9, readFileSync as readFileSync14, statSync as statSync4 } from "node:fs";
133576
133917
  import { writeFile as fsWriteFile } from "node:fs/promises";
133577
133918
  import { spawnSync as nodeSpawnSync2 } from "node:child_process";
133578
133919
  var accountResolver = new AccountResolver;
@@ -133676,12 +134017,12 @@ function previewLang(path) {
133676
134017
  }
133677
134018
  function filePreview(path) {
133678
134019
  try {
133679
- if (!path || !existsSync7(path))
134020
+ if (!path || !existsSync9(path))
133680
134021
  return null;
133681
134022
  const st = statSync4(path);
133682
134023
  if (!st.isFile() || st.size > 400000)
133683
134024
  return null;
133684
- const raw = readFileSync12(path, "utf8").replace(/\r\n?/g, `
134025
+ const raw = readFileSync14(path, "utf8").replace(/\r\n?/g, `
133685
134026
  `);
133686
134027
  const lines = raw.split(`
133687
134028
  `);
@@ -134618,7 +134959,23 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
134618
134959
  const cliChoices = cliModelChoices(cli.binary);
134619
134960
  const cliChoice = cliChoices.find((m2) => m2.id === activeCliModelRef.current) ?? cliChoices[0];
134620
134961
  const cliEffort = cliChoice ? normalizeEffort(effortRef.current, cliChoice.efforts ?? []) ?? undefined : undefined;
134621
- const r3 = await runCliTask({ binary: cli.binary, prompt, messages, onEvent, signal, sessionId: cliSessionRef.current, autoApprove: isYolo(), profile: cli.profile, modelId: activeCliModelRef.current, effort: cliEffort });
134962
+ const activeAccount = getAccount(cli.id);
134963
+ const activeName = activeAccount ? accountName(activeAccount).match(/\((.*)\)/)?.[1] : undefined;
134964
+ const reloginCommand = cli.binary.includes("codex") ? `/account add codex${activeName ? ` ${activeName}` : ""}` : `/account add claude${activeName ? ` ${activeName}` : ""}`;
134965
+ const r3 = await runCliTask({
134966
+ binary: cli.binary,
134967
+ prompt,
134968
+ messages,
134969
+ onEvent,
134970
+ signal,
134971
+ sessionId: cliSessionRef.current,
134972
+ autoApprove: isYolo(),
134973
+ profile: cli.profile,
134974
+ modelId: activeCliModelRef.current,
134975
+ effort: cliEffort,
134976
+ accountLabel: activeAccount ? accountLabel(activeAccount) : cli.id,
134977
+ reloginCommand
134978
+ });
134622
134979
  cliSessionRef.current = r3.sessionId ?? cliSessionRef.current;
134623
134980
  cliMetaRef.current = { costUSD: r3.costUSD, rates: r3.rates };
134624
134981
  return { messages: r3.messages, usage: r3.usage };
@@ -134728,9 +135085,32 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
134728
135085
  echo(prompt);
134729
135086
  lastPromptRef.current = prompt;
134730
135087
  setVerb(nextVerb());
134731
- const { text: modelPrompt, attached } = expandMentions(prompt);
135088
+ let { text: modelPrompt, attached } = expandMentions(prompt);
134732
135089
  if (attached.length)
134733
135090
  notice(`attached ${attached.length} file${attached.length > 1 ? "s" : ""}: ${attached.join(", ")}`);
135091
+ const urls = urlsInText(modelPrompt);
135092
+ if (urls.length) {
135093
+ const fetched = [];
135094
+ for (const url2 of urls) {
135095
+ try {
135096
+ const page = await fetchUrlText(url2);
135097
+ fetched.push(`=== ${page.url}${page.title ? ` · ${page.title}` : ""} ===
135098
+ ${page.text}`);
135099
+ } catch (e2) {
135100
+ notice(`couldn't fetch ${url2}: ${(e2?.message ?? String(e2)).split(`
135101
+ `)[0]}`);
135102
+ }
135103
+ }
135104
+ if (fetched.length) {
135105
+ notice(`fetched ${fetched.length} URL${fetched.length === 1 ? "" : "s"} for context`);
135106
+ modelPrompt += `
135107
+
135108
+ # FETCHED URL CONTEXT
135109
+ ${fetched.join(`
135110
+
135111
+ `)}`;
135112
+ }
135113
+ }
134734
135114
  setBusy(true);
134735
135115
  setSuggestion(null);
134736
135116
  const turnStart = Date.now();
@@ -134889,6 +135269,16 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
134889
135269
  try {
134890
135270
  const r2 = await (runner ?? defaultRunner)({ prompt: modelPrompt, messages: msgRef.current, onEvent, selector: selectorRef.current, signal: ac.signal });
134891
135271
  msgRef.current = r2.messages;
135272
+ if (!hadError && !ac.signal.aborted && changedFiles.size && checks3.length === 0) {
135273
+ const commands = detectVerificationCommands(process.cwd(), [...changedFiles]);
135274
+ if (commands.length) {
135275
+ const results = await runVerification(commands, { onEvent, signal: ac.signal });
135276
+ if (results.some((res) => !res.ok))
135277
+ hadError = true;
135278
+ } else {
135279
+ onEvent({ type: "phase", label: "verification skipped", detail: "no test/build/typecheck command detected", state: "err" });
135280
+ }
135281
+ }
134892
135282
  let modelId = activeCliRef.current?.id ?? routedRef.current?.model.id;
134893
135283
  if (!modelId) {
134894
135284
  try {
@@ -135593,7 +135983,31 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
135593
135983
  notice("busy — try /init again once the current turn finishes");
135594
135984
  return;
135595
135985
  }
135596
- runTurn("Initialize project memory: survey this repository (use the repo map and read the key entry points, config, and docs) and write a concise GEARBOX.md at the repo root covering what the project is, how to build/test/run it, the layout, and any conventions a new contributor must know. Keep it tight and accurate.");
135986
+ echo(text2);
135987
+ setBusy(true);
135988
+ setSuggestion(null);
135989
+ (async () => {
135990
+ const id = idRef.current++;
135991
+ try {
135992
+ push({ kind: "tool", id, callId: `init:${id}`, name: "write_file", arg: "GEARBOX.md", status: "running", summary: "", startedAt: Date.now() });
135993
+ const res = writeProjectGuide(process.cwd());
135994
+ setItems((prev) => prev.map((i2) => i2.id === id && i2.kind === "tool" ? { ...i2, status: "ok", summary: res.summary, diff: res.diff, endedAt: Date.now() } : i2));
135995
+ const commands = detectVerificationCommands(process.cwd(), ["GEARBOX.md"]);
135996
+ if (commands.length)
135997
+ await runVerification(commands.slice(0, 1), { onEvent: (e2) => {
135998
+ if (e2.type === "verification")
135999
+ push({ kind: "verification", id: idRef.current++, command: e2.command, ok: e2.ok, summary: e2.summary });
136000
+ else if (e2.type === "phase")
136001
+ push({ kind: "phase", id: idRef.current++, label: e2.label, detail: e2.detail, state: e2.state ?? "running" });
136002
+ } });
136003
+ notice("initialized GEARBOX.md");
136004
+ persist();
136005
+ } catch (e2) {
136006
+ setItems((prev) => prev.map((i2) => i2.id === id && i2.kind === "tool" ? { ...i2, status: "err", summary: e2?.message ?? String(e2), endedAt: Date.now() } : i2));
136007
+ } finally {
136008
+ setBusy(false);
136009
+ }
136010
+ })();
135597
136011
  return;
135598
136012
  default: {
135599
136013
  echo(text2);
@@ -135871,7 +136285,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
135871
136285
  if (!busyRef.current && input.length > 3 && !input.includes(`
135872
136286
  `)) {
135873
136287
  const p = sanitizeInputText(input).trim().replace(/^'|'$/g, "").replace(/\\ /g, " ");
135874
- if (/[/\\.]/.test(p) && p.length < 1024 && existsSync7(p)) {
136288
+ if (/[/\\.]/.test(p) && p.length < 1024 && existsSync9(p)) {
135875
136289
  const e2 = editRef.current;
135876
136290
  const ins = `@${p} `;
135877
136291
  setEdit({ value: e2.value.slice(0, e2.cursor) + ins + e2.value.slice(e2.cursor), cursor: e2.cursor + ins.length });
@@ -136281,7 +136695,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
136281
136695
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
136282
136696
  process.env.LANG = process.env.LANG || "en_US.UTF-8";
136283
136697
  process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
136284
- var VERSION16 = "0.1.9";
136698
+ var VERSION16 = "0.1.11";
136285
136699
  var args = process.argv.slice(2);
136286
136700
  var supportsAnsi = process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb" && (process.stdout.isTTY || process.env.FORCE_COLOR === "1");
136287
136701
  var ansi = (code) => supportsAnsi ? `\x1B[${code}m` : "";
@@ -136499,7 +136913,7 @@ async function readStdin() {
136499
136913
  }
136500
136914
  if (args[0] === "upgrade" || args[0] === "update") {
136501
136915
  const root2 = resolve11(import.meta.dir, "..");
136502
- if (!existsSync8(resolve11(root2, ".git"))) {
136916
+ if (!existsSync10(resolve11(root2, ".git"))) {
136503
136917
  console.log("This build can't self-update (not a git checkout).");
136504
136918
  console.log("Update by pulling the repo and reinstalling: git pull && bun install");
136505
136919
  process.exit(0);
@@ -136539,6 +136953,8 @@ Set up at least one provider first:
136539
136953
  gearbox onboard
136540
136954
  gearbox auth add <api-key>
136541
136955
  gearbox auth add <provider> <api-key>
136956
+ gearbox auth add codex [name]
136957
+ gearbox auth add claude [name]
136542
136958
  gearbox auth import
136543
136959
 
136544
136960
  Models: ${MODELS.map((m2) => m2.label).join(", ")}
@@ -136556,7 +136972,8 @@ if (args[0] === "onboard" || args[0] === "setup") {
136556
136972
  if (args[0] === "auth") {
136557
136973
  const { listAccounts: listAccounts2, loadAccounts: loadAccounts3, removeAccount: removeAccount2 } = await Promise.resolve().then(() => (init_store(), exports_store));
136558
136974
  const { importableEnvCreds: importableEnvCreds2, importEnvCred: importEnvCred2, importableCloudCreds: importableCloudCreds2, importCloudCred: importCloudCred2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
136559
- const { addApiKeyAccount: addApiKeyAccount2, addByPastedKey: addByPastedKey2, testAccount: testAccount2, addableProviders: addableProviders2 } = await Promise.resolve().then(() => (init_onboard(), exports_onboard));
136975
+ const { addApiKeyAccount: addApiKeyAccount2, addByPastedKey: addByPastedKey2, testAccount: testAccount2, addableProviders: addableProviders2, addCliAccount: addCliAccount2, cliAuthStatus: cliAuthStatus2, cliLoginArgs: cliLoginArgs2 } = await Promise.resolve().then(() => (init_onboard(), exports_onboard));
136976
+ const { subscriptionEnv: subscriptionEnv2 } = await Promise.resolve().then(() => (init_cli_backend(), exports_cli_backend));
136560
136977
  const { detectProviderByKey: detectProviderByKey2 } = await Promise.resolve().then(() => (init_catalog(), exports_catalog));
136561
136978
  const sub = args[1];
136562
136979
  const rest2 = args.slice(2);
@@ -136580,11 +136997,25 @@ Importable from your env (gearbox auth import): ${imp.map((c) => c.envVar).join(
136580
136997
  const names = [...keys2.map((c) => c.provider), ...cloud.map((c) => c.provider)];
136581
136998
  console.log(names.length ? `Imported ${names.length}: ${names.join(", ")}` : "Nothing to import.");
136582
136999
  } else if (sub === "add") {
136583
- const res = rest2[0] && !rest2[1] && detectProviderByKey2(rest2[0]) ? await addByPastedKey2(rest2[0]) : rest2[0] && rest2[1] ? await addApiKeyAccount2(rest2[0], rest2[1]) : { ok: false, message: "usage: gearbox auth add <key> | gearbox auth add <provider> <key>" };
137000
+ const head2 = (rest2[0] ?? "").toLowerCase();
137001
+ const cliProvider = head2 === "codex" || head2 === "chatgpt" ? "codex-cli" : head2 === "claude" ? "claude-cli" : "";
137002
+ const res = cliProvider ? addCliAccount2(cliProvider, rest2.slice(1).join(" ").trim() || undefined) : rest2[0] && !rest2[1] && detectProviderByKey2(rest2[0]) ? await addByPastedKey2(rest2[0]) : rest2[0] && rest2[1] ? await addApiKeyAccount2(rest2[0], rest2[1]) : { ok: false, message: "usage: gearbox auth add <key> | gearbox auth add <provider> <key> | gearbox auth add codex [name]" };
136584
137003
  console.log(res.message);
136585
137004
  if (res.ok && res.account) {
136586
- const t2 = await testAccount2(res.account);
136587
- console.log(t2.ok ? " test: ✓ " + t2.message : " test: ✗ " + t2.message + " (stored anyway)");
137005
+ if (res.account.exec === "cli" && res.account.auth.kind === "cli") {
137006
+ const bin = res.account.auth.binary;
137007
+ const profile = res.account.auth.loginProfile;
137008
+ let st = await cliAuthStatus2(bin, profile);
137009
+ if (!st.loggedIn) {
137010
+ console.log(` sign-in: starting ${bin} ${cliLoginArgs2(bin).join(" ")}`);
137011
+ spawnSync(bin, cliLoginArgs2(bin), { stdio: "inherit", env: subscriptionEnv2(bin, profile) });
137012
+ st = await cliAuthStatus2(bin, profile);
137013
+ }
137014
+ console.log(st.loggedIn ? ` sign-in: ✓ ${st.detail ?? "ready"}` : ` sign-in: ✗ not signed in${st.detail ? ` (${st.detail})` : ""}`);
137015
+ } else {
137016
+ const t2 = await testAccount2(res.account);
137017
+ console.log(t2.ok ? " test: ✓ " + t2.message : " test: ✗ " + t2.message + " (stored anyway)");
137018
+ }
136588
137019
  }
136589
137020
  } else if (sub === "test" && rest2[0]) {
136590
137021
  const a = listAccounts2().find((x2) => x2.id === rest2[0]);
@@ -136596,7 +137027,7 @@ Importable from your env (gearbox auth import): ${imp.map((c) => c.envVar).join(
136596
137027
  for (const p of addableProviders2())
136597
137028
  console.log(`${p.id.padEnd(16)} ${p.label} (${p.group})`);
136598
137029
  } else {
136599
- console.log("gearbox auth [list|import|add <key>|add <provider> <key>|test <id>|rm <id>|providers]");
137030
+ console.log("gearbox auth [list|import|add <key>|add <provider> <key>|add codex [name]|add claude [name]|test <id>|rm <id>|providers]");
136600
137031
  }
136601
137032
  process.exit(0);
136602
137033
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gearbox-code",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "A beautiful multi-provider coding harness for the terminal. (Intelligent model routing lands on top of this soon.)",
5
5
  "type": "module",
6
6
  "license": "MIT",