gearbox-code 0.1.10 → 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 (2) hide show
  1. package/dist/cli.mjs +405 -17
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -70313,7 +70313,7 @@ var import_react21 = __toESM(require_react(), 1);
70313
70313
  import { createInterface } from "node:readline/promises";
70314
70314
  import { execFileSync as execFileSync4, spawnSync } from "node:child_process";
70315
70315
  import { resolve as resolve11 } from "node:path";
70316
- import { existsSync as existsSync8 } from "node:fs";
70316
+ import { existsSync as existsSync10 } from "node:fs";
70317
70317
 
70318
70318
  // src/ui/App.tsx
70319
70319
  var import_react26 = __toESM(require_react(), 1);
@@ -132189,6 +132189,70 @@ async function runShellStream(command, opts = {}) {
132189
132189
 
132190
132190
  // src/tools.ts
132191
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
132192
132256
  var ROOT = process.cwd();
132193
132257
  var CAP2 = 60000;
132194
132258
  var DENIED = "Permission denied by the user — they declined this action.";
@@ -132202,6 +132266,44 @@ function safe(path) {
132202
132266
  }
132203
132267
  var clip2 = (s2) => s2.length > CAP2 ? s2.slice(0, CAP2) + `
132204
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
+ }
132205
132307
  function createTools(onEvent) {
132206
132308
  return {
132207
132309
  read_file: tool5({
@@ -132224,19 +132326,38 @@ function createTools(onEvent) {
132224
132326
  }
132225
132327
  }),
132226
132328
  edit_file: tool5({
132227
- description: "Replace the first exact occurrence of `find` with `replace` in a file.",
132228
- inputSchema: exports_external2.object({ path: exports_external2.string(), find: exports_external2.string(), replace: exports_external2.string() }),
132229
- 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 }) => {
132230
132338
  const abs = safe(path);
132231
132339
  const before2 = await readFile(abs, "utf8");
132232
- if (!before2.includes(find2))
132233
- 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}`);
132234
132345
  if (!await requestPermission({ kind: "edit", title: "Edit a file", detail: path }))
132235
132346
  throw new Error(DENIED);
132236
- const after2 = before2.replace(find2, replace2);
132347
+ const after2 = replaceAll ? before2.split(find2).join(replace2) : replaceOccurrence(before2, find2, replace2, occurrence);
132237
132348
  await writeFile2(abs, after2, "utf8");
132238
132349
  const diff2 = computeDiff(before2, after2);
132239
- 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
+ `));
132240
132361
  }
132241
132362
  }),
132242
132363
  search: tool5({
@@ -132342,7 +132463,8 @@ var readOnlyTools = {
132342
132463
  read_file: tools.read_file,
132343
132464
  list_dir: tools.list_dir,
132344
132465
  search: tools.search,
132345
- glob: tools.glob
132466
+ glob: tools.glob,
132467
+ fetch_url: tools.fetch_url
132346
132468
  };
132347
132469
 
132348
132470
  // node_modules/js-tiktoken/dist/chunk-VL2OQCWN.js
@@ -132799,6 +132921,42 @@ function retrieveFiles(query, cwd2 = process.cwd(), k = 6, budget = 8000, modelI
132799
132921
  return out;
132800
132922
  }
132801
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
+
132802
132960
  // src/context/builder.ts
132803
132961
  var BASE_SYSTEM = `You are Gearbox, a precise terminal coding agent.
132804
132962
  Work in small, verifiable steps. Use the tools to read before you write, and
@@ -132910,6 +133068,14 @@ function buildContext(opts) {
132910
133068
  ${memory}`;
132911
133069
  sections.push({ name: "memory", tokens: countTokens(memory, modelId) });
132912
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
+ }
132913
133079
  const mapBudget = Math.min(4000, Math.floor(inputBudget * 0.05));
132914
133080
  const map3 = safe2(() => repoMap(cwd2, mapBudget), "");
132915
133081
  if (map3) {
@@ -133379,6 +133545,171 @@ ${summary}` },
133379
133545
  return { messages, summarizedTurns: old.length, before: before2, after: after2 };
133380
133546
  }
133381
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
+
133382
133713
  // src/ui/clipboard.ts
133383
133714
  init_proc();
133384
133715
  function osc52(text2) {
@@ -133582,7 +133913,7 @@ function gitBranch() {
133582
133913
  init_proc();
133583
133914
  var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
133584
133915
  import { basename as basename2, extname } from "node:path";
133585
- 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";
133586
133917
  import { writeFile as fsWriteFile } from "node:fs/promises";
133587
133918
  import { spawnSync as nodeSpawnSync2 } from "node:child_process";
133588
133919
  var accountResolver = new AccountResolver;
@@ -133686,12 +134017,12 @@ function previewLang(path) {
133686
134017
  }
133687
134018
  function filePreview(path) {
133688
134019
  try {
133689
- if (!path || !existsSync7(path))
134020
+ if (!path || !existsSync9(path))
133690
134021
  return null;
133691
134022
  const st = statSync4(path);
133692
134023
  if (!st.isFile() || st.size > 400000)
133693
134024
  return null;
133694
- const raw = readFileSync12(path, "utf8").replace(/\r\n?/g, `
134025
+ const raw = readFileSync14(path, "utf8").replace(/\r\n?/g, `
133695
134026
  `);
133696
134027
  const lines = raw.split(`
133697
134028
  `);
@@ -134754,9 +135085,32 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
134754
135085
  echo(prompt);
134755
135086
  lastPromptRef.current = prompt;
134756
135087
  setVerb(nextVerb());
134757
- const { text: modelPrompt, attached } = expandMentions(prompt);
135088
+ let { text: modelPrompt, attached } = expandMentions(prompt);
134758
135089
  if (attached.length)
134759
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
+ }
134760
135114
  setBusy(true);
134761
135115
  setSuggestion(null);
134762
135116
  const turnStart = Date.now();
@@ -134915,6 +135269,16 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
134915
135269
  try {
134916
135270
  const r2 = await (runner ?? defaultRunner)({ prompt: modelPrompt, messages: msgRef.current, onEvent, selector: selectorRef.current, signal: ac.signal });
134917
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
+ }
134918
135282
  let modelId = activeCliRef.current?.id ?? routedRef.current?.model.id;
134919
135283
  if (!modelId) {
134920
135284
  try {
@@ -135619,7 +135983,31 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
135619
135983
  notice("busy — try /init again once the current turn finishes");
135620
135984
  return;
135621
135985
  }
135622
- 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
+ })();
135623
136011
  return;
135624
136012
  default: {
135625
136013
  echo(text2);
@@ -135897,7 +136285,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
135897
136285
  if (!busyRef.current && input.length > 3 && !input.includes(`
135898
136286
  `)) {
135899
136287
  const p = sanitizeInputText(input).trim().replace(/^'|'$/g, "").replace(/\\ /g, " ");
135900
- if (/[/\\.]/.test(p) && p.length < 1024 && existsSync7(p)) {
136288
+ if (/[/\\.]/.test(p) && p.length < 1024 && existsSync9(p)) {
135901
136289
  const e2 = editRef.current;
135902
136290
  const ins = `@${p} `;
135903
136291
  setEdit({ value: e2.value.slice(0, e2.cursor) + ins + e2.value.slice(e2.cursor), cursor: e2.cursor + ins.length });
@@ -136307,7 +136695,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
136307
136695
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
136308
136696
  process.env.LANG = process.env.LANG || "en_US.UTF-8";
136309
136697
  process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
136310
- var VERSION16 = "0.1.10";
136698
+ var VERSION16 = "0.1.11";
136311
136699
  var args = process.argv.slice(2);
136312
136700
  var supportsAnsi = process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb" && (process.stdout.isTTY || process.env.FORCE_COLOR === "1");
136313
136701
  var ansi = (code) => supportsAnsi ? `\x1B[${code}m` : "";
@@ -136525,7 +136913,7 @@ async function readStdin() {
136525
136913
  }
136526
136914
  if (args[0] === "upgrade" || args[0] === "update") {
136527
136915
  const root2 = resolve11(import.meta.dir, "..");
136528
- if (!existsSync8(resolve11(root2, ".git"))) {
136916
+ if (!existsSync10(resolve11(root2, ".git"))) {
136529
136917
  console.log("This build can't self-update (not a git checkout).");
136530
136918
  console.log("Update by pulling the repo and reinstalling: git pull && bun install");
136531
136919
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gearbox-code",
3
- "version": "0.1.10",
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",