gearbox-code 0.1.7 → 0.1.9

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.
package/README.md CHANGED
@@ -1,117 +1,93 @@
1
- # gearbox
1
+ # gearbox
2
2
 
3
- A beautiful, simple coding harness for the terminal. It reads, edits, and runs your code through one clean agent loop, talking to any provider (Anthropic, OpenAI, Google, DeepSeek).
3
+ ## Install
4
4
 
5
- > **What it does:** the point of Gearbox is *intelligent per-task model routing* — automatically using the right model for each task across every provider and account you pay for, cheaply and transparently. Basic routing is live: it classifies each task, filters candidates by quality bar, and picks the cheapest one that fits. The richer engine (shadow-eval, credit/limit scoring, confidence display) layers on top. See [`DESIGN.md`](./DESIGN.md).
5
+ macOS, Linux, WSL:
6
6
 
7
+ ```bash
8
+ curl -fsSL https://unpkg.com/gearbox-code@latest/install.sh | bash
7
9
  ```
8
- ⚙ gearbox
9
- coding harness · sonnet-4.6
10
- ──────────────────────────────────────────────────────────
11
-
12
- › add a --json flag to the CLI and cover it with a test
13
10
 
14
- ⏺ I'll see how args are parsed, add the flag, then test it.
15
-
16
- ✓ read_file src/cli.tsx
17
- renders the Ink app · 18 lines
18
- ✓ edit_file src/cli.tsx
19
- ✓ run_shell bun test
20
- 9 pass · 0 fail
21
-
22
- ⏺ Done — flag added with a passing test.
11
+ Windows PowerShell:
23
12
 
24
- ╭──────────────────────────────────────────────────────────╮
25
- │ › ask gearbox to build or fix something │
26
- ╰──────────────────────────────────────────────────────────╯
27
- gearbox · sonnet-4.6 · 18,432 tok · ⏎ send ctrl+c quit
13
+ ```powershell
14
+ irm https://unpkg.com/gearbox-code@latest/install.ps1 | iex
28
15
  ```
29
16
 
30
- ## Run
17
+ These installers do not use `sudo`, admin privileges, or `npm install -g`.
18
+ They install Gearbox into a user-owned directory, create the `gearbox` command,
19
+ then start onboarding before the coding app opens.
31
20
 
32
- ```bash
33
- bun install
34
- gearbox auth add <api-key> # paste-detects common providers when possible
35
- # or: gearbox auth add <provider> <api-key>
36
- # or: gearbox auth import # import keys from env/cloud credentials
37
- bun start # or: bun run src/cli.tsx
38
- bun start -- --model gemini-flash # pick a model
39
- ```
40
-
41
- No provider configured? Gearbox opens a setup screen and will not run a fake model. Preview the look without running anything:
21
+ Run without installing:
42
22
 
43
23
  ```bash
44
- bun run scripts/preview.tsx
24
+ npx gearbox-code@latest
45
25
  ```
46
26
 
47
- ## Install
27
+ ## First Run
48
28
 
49
- macOS, Linux, WSL:
29
+ Gearbox needs one provider account before it opens the coding app. The installer
30
+ runs setup automatically. You can also run it yourself:
50
31
 
51
32
  ```bash
52
- curl -fsSL https://unpkg.com/gearbox-code@latest/install.sh | bash
33
+ gearbox onboard
53
34
  ```
54
35
 
55
- Windows PowerShell:
36
+ Common setup commands:
56
37
 
57
- ```powershell
58
- irm https://unpkg.com/gearbox-code@latest/install.ps1 | iex
38
+ ```bash
39
+ gearbox auth add <api-key> # auto-detects known key prefixes
40
+ gearbox auth add <provider> <api-key> # anthropic, openai, google, deepseek, openrouter, groq, xai, mistral...
41
+ gearbox auth import # import credentials from env/cloud config
42
+ gearbox auth providers # list supported providers
59
43
  ```
60
44
 
61
- The installers are served from the published npm package, download the latest
62
- `gearbox-code` tarball, and create a user-owned `gearbox` command. They avoid
63
- `sudo`, admin privileges, and `npm install -g`.
64
-
65
- Then:
45
+ After setup:
66
46
 
67
47
  ```bash
68
- gearbox auth add <api-key> # each person uses their own provider account
69
- cd ~/any/project && gearbox # the current directory is the workspace
48
+ cd ~/your-project
49
+ gearbox
70
50
  ```
71
51
 
72
- If `~/.local/bin` is not on your PATH, the installer prints the exact line to
73
- add to your shell config.
52
+ No account configured means no fake/demo model: Gearbox runs onboarding first.
74
53
 
75
- You can still run without installing:
54
+ ## Uninstall
55
+
56
+ macOS, Linux, WSL:
76
57
 
77
58
  ```bash
78
- npx gearbox-code@latest
59
+ rm -f ~/.local/bin/gearbox
60
+ rm -rf ~/.local/share/gearbox
79
61
  ```
80
62
 
81
- **Upgrade** later by rerunning the install command. `gearbox upgrade` still works
82
- for git checkouts.
63
+ Windows PowerShell:
83
64
 
84
- ## Develop From Source
65
+ ```powershell
66
+ Remove-Item "$env:LOCALAPPDATA\Gearbox" -Recurse -Force
67
+ ```
85
68
 
86
- Requires [Bun](https://bun.sh). Clone the repo, then:
69
+ If you previously installed with npm global:
87
70
 
88
71
  ```bash
89
- bun install
90
- bun run src/cli.tsx
72
+ npm uninstall -g gearbox-code
91
73
  ```
92
74
 
93
- **Standalone binary** (no clone/install on the target, same OS/arch):
75
+ ## What It Is
94
76
 
95
- ```bash
96
- bun run build # dist/gearbox (single ~64MB executable)
97
- cp dist/gearbox ~/.bun/bin/ # or anywhere on PATH; share the file directly
98
- ```
77
+ Gearbox is a terminal coding agent that can use the model accounts you already
78
+ pay for. It supports provider accounts, local credential storage, model routing,
79
+ session history, file edits, shell commands, and permission gates.
99
80
 
100
- > **Before running on real code:** there is no permission/confirm gate yet — `write_file`, `edit_file`, `run_shell`, and the `!` prefix execute without asking. Fine for trusted internal use on your own repos; do not point it at anything you don't want modified. A confirm-gate is the next thing to land.
81
+ Supported setup paths include API keys, detected env/cloud credentials, Azure,
82
+ and provider CLIs where available.
101
83
 
102
84
  ## Develop
103
85
 
86
+ Requires [Bun](https://bun.sh).
87
+
104
88
  ```bash
105
- bun test # render + agent tests (no API key needed)
89
+ bun install
90
+ bun run src/cli.tsx
91
+ bun test
106
92
  bun run typecheck
107
93
  ```
108
-
109
- ## Principles
110
-
111
- - **Open + free.** MIT. No paid dependencies, no hosted backend, no telemetry. The only cost is your own model calls on your own keys.
112
- - **Beautiful + calm.** One accent color, generous spacing, consistent glyphs. The whole look lives in `src/ui/theme.ts`.
113
- - **Routing-ready.** Model choice happens in exactly one place (`src/model/selector.ts`); the router drops in there later with no changes upstream. See [`CLAUDE.md`](./CLAUDE.md).
114
-
115
- ## Status
116
-
117
- v0.1 — streaming agent loop, real file + shell tools, a polished Ink TUI, multi-provider support, accounts + spend ledger, BM25 context retrieval, and basic per-task routing (classify → quality bar → cheapest winner). The richer routing engine (shadow-eval, credit/limit/plan scoring, per-repo calibration) is next (`DESIGN.md`).
package/dist/cli.mjs CHANGED
@@ -63894,8 +63894,18 @@ var require_src6 = __commonJS((exports) => {
63894
63894
  });
63895
63895
 
63896
63896
  // src/proc.ts
63897
+ var exports_proc = {};
63898
+ __export(exports_proc, {
63899
+ write: () => writeFile,
63900
+ which: () => which,
63901
+ spawnSyncProc: () => spawnSyncProc,
63902
+ spawnProc: () => spawnProc,
63903
+ readStream: () => readStream,
63904
+ Glob: () => Glob
63905
+ });
63897
63906
  import { spawn, spawnSync as nodeSpawnSync, execFileSync } from "node:child_process";
63898
63907
  import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
63908
+ import { writeFile } from "node:fs/promises";
63899
63909
  import { resolve as resolve6 } from "node:path";
63900
63910
  function which(bin) {
63901
63911
  try {
@@ -64270,6 +64280,13 @@ var init_onboarding = __esm(() => {
64270
64280
  });
64271
64281
 
64272
64282
  // src/agent/cli-backend.ts
64283
+ var exports_cli_backend = {};
64284
+ __export(exports_cli_backend, {
64285
+ subscriptionEnv: () => subscriptionEnv,
64286
+ runCliTask: () => runCliTask,
64287
+ parseCliLines: () => parseCliLines,
64288
+ buildCliArgs: () => buildCliArgs
64289
+ });
64273
64290
  function newState() {
64274
64291
  return { text: "", usage: { inputTokens: 0, outputTokens: 0 }, rates: new Map, toolNames: new Map };
64275
64292
  }
@@ -64407,6 +64424,22 @@ function buildCliArgs(binary, prompt, opts = {}) {
64407
64424
  args.push("--resume", opts.sessionId);
64408
64425
  return args;
64409
64426
  }
64427
+ function parseCliLines(binary, lines, onEvent) {
64428
+ const state = newState();
64429
+ for (const line of lines) {
64430
+ const t2 = line.trim();
64431
+ if (!t2)
64432
+ continue;
64433
+ let obj;
64434
+ try {
64435
+ obj = JSON.parse(t2);
64436
+ } catch {
64437
+ continue;
64438
+ }
64439
+ mapCliEvent(binary, obj, state, onEvent);
64440
+ }
64441
+ return finalize(state);
64442
+ }
64410
64443
  function finalize(state) {
64411
64444
  return { messages: [], usage: state.usage, sessionId: state.sessionId, costUSD: state.costUSD, rates: [...state.rates.values()] };
64412
64445
  }
@@ -70267,7 +70300,8 @@ var import_react20 = __toESM(require_react(), 1);
70267
70300
  // node_modules/ink/build/hooks/use-focus-manager.js
70268
70301
  var import_react21 = __toESM(require_react(), 1);
70269
70302
  // src/cli.tsx
70270
- import { execFileSync as execFileSync4 } from "node:child_process";
70303
+ import { createInterface } from "node:readline/promises";
70304
+ import { execFileSync as execFileSync4, spawnSync } from "node:child_process";
70271
70305
  import { resolve as resolve11 } from "node:path";
70272
70306
  import { existsSync as existsSync8 } from "node:fs";
70273
70307
 
@@ -121288,6 +121322,9 @@ function pickDefaultModel(preferredId) {
121288
121322
  return wanted;
121289
121323
  return MODELS.find((m2) => providerAvailable(m2.provider));
121290
121324
  }
121325
+ function anyProviderAvailable() {
121326
+ return MODELS.some((m2) => providerAvailable(m2.provider));
121327
+ }
121291
121328
 
121292
121329
  // src/model/selector.ts
121293
121330
  class FixedSelector {
@@ -131808,7 +131845,7 @@ var uiMessagesSchema = lazyValidator3(() => zodSchema7(exports_external.array(ex
131808
131845
  })).nonempty("Messages array must not be empty")));
131809
131846
 
131810
131847
  // src/tools.ts
131811
- import { readFile, writeFile, readdir, stat as stat2 } from "node:fs/promises";
131848
+ import { readFile, writeFile as writeFile2, readdir, stat as stat2 } from "node:fs/promises";
131812
131849
  import { existsSync as existsSync3 } from "node:fs";
131813
131850
  import { resolve as resolve7, relative, isAbsolute } from "node:path";
131814
131851
  // node_modules/diff/libesm/diff/base.js
@@ -132171,7 +132208,7 @@ function createTools(onEvent) {
132171
132208
  if (!await requestPermission({ kind: "write", title: exists ? "Overwrite a file" : "Create a file", detail: path }))
132172
132209
  throw new Error(DENIED);
132173
132210
  const before2 = exists ? await readFile(abs, "utf8") : "";
132174
- await writeFile(abs, content, "utf8");
132211
+ await writeFile2(abs, content, "utf8");
132175
132212
  const diff2 = computeDiff(before2, content);
132176
132213
  return { summary: `wrote ${path} (${diffStat(diff2)})`, diff: diff2 };
132177
132214
  }
@@ -132187,7 +132224,7 @@ function createTools(onEvent) {
132187
132224
  if (!await requestPermission({ kind: "edit", title: "Edit a file", detail: path }))
132188
132225
  throw new Error(DENIED);
132189
132226
  const after2 = before2.replace(find2, replace2);
132190
- await writeFile(abs, after2, "utf8");
132227
+ await writeFile2(abs, after2, "utf8");
132191
132228
  const diff2 = computeDiff(before2, after2);
132192
132229
  return { summary: `edited ${path} (${diffStat(diff2)})`, diff: diff2 };
132193
132230
  }
@@ -136244,8 +136281,222 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
136244
136281
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
136245
136282
  process.env.LANG = process.env.LANG || "en_US.UTF-8";
136246
136283
  process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
136247
- var VERSION16 = "0.1.7";
136284
+ var VERSION16 = "0.1.9";
136248
136285
  var args = process.argv.slice(2);
136286
+ var supportsAnsi = process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb" && (process.stdout.isTTY || process.env.FORCE_COLOR === "1");
136287
+ var ansi = (code) => supportsAnsi ? `\x1B[${code}m` : "";
136288
+ var paint = (code, text2) => `${ansi(code)}${text2}${ansi("0")}`;
136289
+ var bold = (text2) => paint("1", text2);
136290
+ var accent = (text2) => paint("36", text2);
136291
+ var dim = (text2) => paint("2", text2);
136292
+ var ok = (text2) => paint("32", text2);
136293
+ var warn = (text2) => paint("33", text2);
136294
+ var errColor = (text2) => paint("31", text2);
136295
+ var stripAnsi2 = (text2) => text2.replace(/\x1b\[[0-9;]*m/g, "");
136296
+ var visibleLength = (text2) => stripAnsi2(text2).length;
136297
+ var padVisible = (text2, width) => text2 + " ".repeat(Math.max(width - visibleLength(text2), 0));
136298
+ function onboardingBoo() {
136299
+ return [
136300
+ " .-''''-.",
136301
+ " .' .--. '.",
136302
+ " / ( ) \\",
136303
+ " | .-. .-. |",
136304
+ " | |_| |_| |",
136305
+ " | __ |",
136306
+ " \\ .' '. /",
136307
+ " '._\\____/_.'",
136308
+ " /| |\\"
136309
+ ].join(`
136310
+ `);
136311
+ }
136312
+ function box(title, lines) {
136313
+ const width = Math.min(78, Math.max(title.length + 4, ...lines.map((l) => visibleLength(l) + 4)));
136314
+ const rule = "─".repeat(width - 2);
136315
+ console.log(accent(`╭${rule}╮`));
136316
+ console.log(accent("│ ") + padVisible(bold(title), width - 3) + accent("│"));
136317
+ console.log(accent(`├${rule}┤`));
136318
+ for (const line of lines) {
136319
+ console.log(accent("│ ") + padVisible(line, width - 3) + accent("│"));
136320
+ }
136321
+ console.log(accent(`╰${rule}╯`));
136322
+ }
136323
+ function optionLine(key, label, detail) {
136324
+ return `${accent(key.padStart(2))} ${bold(label)} ${dim(detail)}`;
136325
+ }
136326
+ async function runCliOnboarding() {
136327
+ const { listAccounts: listAccounts2 } = await Promise.resolve().then(() => (init_store(), exports_store));
136328
+ const { importableEnvCreds: importableEnvCreds2, importEnvCred: importEnvCred2, importableCloudCreds: importableCloudCreds2, importCloudCred: importCloudCred2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
136329
+ const { addApiKeyAccount: addApiKeyAccount2, addAzureAccount: addAzureAccount2, addAzureFoundryAccount: addAzureFoundryAccount2, addByPastedKey: addByPastedKey2, testAccount: testAccount2, addableProviders: addableProviders2, addCliAccount: addCliAccount2, cliAuthStatus: cliAuthStatus2, cliLoginArgs: cliLoginArgs2 } = await Promise.resolve().then(() => (init_onboard(), exports_onboard));
136330
+ const { subscriptionEnv: subscriptionEnv2 } = await Promise.resolve().then(() => (init_cli_backend(), exports_cli_backend));
136331
+ const { detectProviderByKey: detectProviderByKey2 } = await Promise.resolve().then(() => (init_catalog(), exports_catalog));
136332
+ const { which: which2 } = await Promise.resolve().then(() => (init_proc(), exports_proc));
136333
+ const pipedAnswers = process.stdin.isTTY ? null : (await readStdin()).split(/\r?\n/);
136334
+ const rl = pipedAnswers ? null : createInterface({ input: process.stdin, output: process.stdout });
136335
+ const ask = async (q) => {
136336
+ if (pipedAnswers) {
136337
+ const answer = (pipedAnswers.shift() ?? "").trim();
136338
+ console.log(accent(q) + answer);
136339
+ return answer;
136340
+ }
136341
+ return (await rl.question(accent(q))).trim();
136342
+ };
136343
+ const providerRows = () => addableProviders2().map((p) => ` ${accent(p.id.padEnd(16))} ${p.label}`).join(`
136344
+ `);
136345
+ const testAndReport = async (account) => {
136346
+ console.log(dim("Testing credential with the provider..."));
136347
+ const t2 = await testAccount2(account);
136348
+ console.log(t2.ok ? ok(`Credential works: ${t2.message}`) : warn(`Stored, but the live test failed: ${t2.message}`));
136349
+ };
136350
+ const addSubscription = async (provider) => {
136351
+ const res = addCliAccount2(provider);
136352
+ console.log(res.ok ? ok(res.message) : errColor(res.message));
136353
+ if (!res.ok || !res.account || res.account.auth.kind !== "cli")
136354
+ return false;
136355
+ const bin = res.account.auth.binary;
136356
+ const profile = res.account.auth.loginProfile;
136357
+ let status = await cliAuthStatus2(bin, profile);
136358
+ if (!status.loggedIn) {
136359
+ console.log(dim(`Starting ${bin} sign-in in this terminal...`));
136360
+ const r2 = spawnSync(bin, cliLoginArgs2(bin), { stdio: "inherit", env: subscriptionEnv2(bin, profile) });
136361
+ if ((r2.status ?? 1) !== 0)
136362
+ return false;
136363
+ status = await cliAuthStatus2(bin, profile);
136364
+ }
136365
+ if (!status.loggedIn) {
136366
+ console.log(warn(`${bin} did not report a completed sign-in.`));
136367
+ return false;
136368
+ }
136369
+ console.log(ok(`${bin} subscription ready${status.detail ? ` (${status.detail})` : ""}`));
136370
+ return true;
136371
+ };
136372
+ try {
136373
+ console.log("");
136374
+ if (supportsAnsi)
136375
+ console.log(paint("38;5;117", onboardingBoo()));
136376
+ else
136377
+ console.log(onboardingBoo());
136378
+ console.log("");
136379
+ console.log(bold("Gearbox setup"));
136380
+ console.log("Boo needs one model account before the coding app opens.");
136381
+ console.log(dim("Your credentials stay local. API keys are stored in Gearbox's credential store; subscription sign-ins stay inside the vendor CLI."));
136382
+ console.log("");
136383
+ while (!anyProviderAvailable()) {
136384
+ const env3 = importableEnvCreds2();
136385
+ const cloud = importableCloudCreds2();
136386
+ const existing = listAccounts2();
136387
+ if (existing.length)
136388
+ break;
136389
+ const options2 = [];
136390
+ if (env3.length || cloud.length) {
136391
+ const names = [...env3.map((c) => c.envVar), ...cloud.map((c) => `${c.label} (${c.source})`)];
136392
+ options2.push(optionLine("1", "Import detected credentials", names.join(", ")));
136393
+ }
136394
+ options2.push(optionLine("2", "Paste API key", "auto-detects common key prefixes"));
136395
+ options2.push(optionLine("3", "Choose provider + key", "Anthropic, OpenAI, Gemini, OpenRouter, Groq, ..."));
136396
+ options2.push(optionLine("4", "Azure endpoint + key", "Azure OpenAI or Azure AI Foundry"));
136397
+ if (which2("claude"))
136398
+ options2.push(optionLine("5", "Claude subscription", "uses the official claude CLI; no token extraction"));
136399
+ if (which2("codex"))
136400
+ options2.push(optionLine("6", "ChatGPT subscription", "uses the official codex CLI; no token extraction"));
136401
+ options2.push(optionLine("p", "Show provider catalog", "all API-key providers Gearbox knows how to add"));
136402
+ options2.push(optionLine("q", "Quit setup", "Gearbox will not open the coding app yet"));
136403
+ box("Choose how Gearbox should connect", options2);
136404
+ console.log("");
136405
+ const choice = (await ask("Selection: ")).toLowerCase();
136406
+ if (choice === "q" || choice === "quit" || choice === "skip") {
136407
+ console.log("");
136408
+ console.log(warn("Setup skipped. Run `gearbox onboard` when you are ready."));
136409
+ return false;
136410
+ }
136411
+ if (choice === "p" || choice === "providers") {
136412
+ console.log("");
136413
+ console.log(bold("Provider catalog"));
136414
+ console.log(dim("Use these ids with: gearbox auth add <provider> <api-key>"));
136415
+ console.log(providerRows());
136416
+ console.log("");
136417
+ continue;
136418
+ }
136419
+ if (choice === "1" && (env3.length || cloud.length)) {
136420
+ for (const c of env3)
136421
+ await importEnvCred2(c);
136422
+ for (const c of cloud)
136423
+ await importCloudCred2(c);
136424
+ console.log(ok(`Imported ${env3.length + cloud.length} credential${env3.length + cloud.length === 1 ? "" : "s"}.`));
136425
+ break;
136426
+ }
136427
+ if (choice === "2") {
136428
+ console.log(dim("Paste is visible in most terminals. Use option 3 if you want to be explicit about the provider."));
136429
+ const key = await ask("Paste API key: ");
136430
+ if (!key)
136431
+ continue;
136432
+ const detected = detectProviderByKey2(key);
136433
+ if (!detected) {
136434
+ console.log(warn("Could not detect the provider from that key. Use option 3."));
136435
+ continue;
136436
+ }
136437
+ const res = await addByPastedKey2(key);
136438
+ console.log(res.ok ? ok(res.message) : errColor(res.message));
136439
+ if (res.ok && res.account) {
136440
+ await testAndReport(res.account);
136441
+ break;
136442
+ }
136443
+ continue;
136444
+ }
136445
+ if (choice === "3") {
136446
+ console.log("");
136447
+ console.log(bold("Provider catalog"));
136448
+ console.log(providerRows());
136449
+ console.log("");
136450
+ const provider = await ask("Provider id: ");
136451
+ console.log(dim("The key is stored locally and tested before setup finishes."));
136452
+ const key = await ask("API key: ");
136453
+ const res = await addApiKeyAccount2(provider, key);
136454
+ console.log(res.ok ? ok(res.message) : errColor(res.message));
136455
+ if (res.ok && res.account) {
136456
+ await testAndReport(res.account);
136457
+ break;
136458
+ }
136459
+ continue;
136460
+ }
136461
+ if (choice === "4") {
136462
+ console.log(dim("Use a resource name like my-openai-resource, or a full Foundry endpoint URL."));
136463
+ const endpoint = await ask("Azure resource name or endpoint: ");
136464
+ const key = await ask("API key: ");
136465
+ const apiVersion = await ask("API version (optional): ");
136466
+ const res = /^https?:\/\//i.test(endpoint) ? await addAzureFoundryAccount2(endpoint, key) : await addAzureAccount2(endpoint, key, { apiVersion: apiVersion || undefined });
136467
+ console.log(res.ok ? ok(res.message) : errColor(res.message));
136468
+ if (res.ok && res.account) {
136469
+ await testAndReport(res.account);
136470
+ break;
136471
+ }
136472
+ continue;
136473
+ }
136474
+ if (choice === "5" && which2("claude")) {
136475
+ if (await addSubscription("claude-cli"))
136476
+ break;
136477
+ continue;
136478
+ }
136479
+ if (choice === "6" && which2("codex")) {
136480
+ if (await addSubscription("codex-cli"))
136481
+ break;
136482
+ continue;
136483
+ }
136484
+ console.log(warn("Choose one of the listed options."));
136485
+ }
136486
+ console.log("");
136487
+ console.log(ok("Gearbox is ready."));
136488
+ console.log(`Next: ${accent("cd ~/your-project")} and run ${accent("gearbox")}.`);
136489
+ return true;
136490
+ } finally {
136491
+ rl?.close();
136492
+ }
136493
+ }
136494
+ async function readStdin() {
136495
+ let input = "";
136496
+ for await (const chunk2 of process.stdin)
136497
+ input += chunk2;
136498
+ return input;
136499
+ }
136249
136500
  if (args[0] === "upgrade" || args[0] === "update") {
136250
136501
  const root2 = resolve11(import.meta.dir, "..");
136251
136502
  if (!existsSync8(resolve11(root2, ".git"))) {
@@ -136270,6 +136521,7 @@ if (args.includes("--help") || args.includes("-h")) {
136270
136521
 
136271
136522
  Usage:
136272
136523
  gearbox start in the current directory (it becomes the workspace)
136524
+ gearbox onboard set up a provider before opening the app
136273
136525
  gearbox --model <name> start with a specific model
136274
136526
  gearbox --continue resume the most recent session in this directory
136275
136527
  gearbox upgrade pull the latest version + reinstall deps
@@ -136284,6 +136536,7 @@ Options:
136284
136536
  -h, --help this help
136285
136537
 
136286
136538
  Set up at least one provider first:
136539
+ gearbox onboard
136287
136540
  gearbox auth add <api-key>
136288
136541
  gearbox auth add <provider> <api-key>
136289
136542
  gearbox auth import
@@ -136296,6 +136549,10 @@ if (args.includes("--version") || args.includes("-v")) {
136296
136549
  console.log(VERSION16);
136297
136550
  process.exit(0);
136298
136551
  }
136552
+ if (args[0] === "onboard" || args[0] === "setup") {
136553
+ await runCliOnboarding();
136554
+ process.exit(0);
136555
+ }
136299
136556
  if (args[0] === "auth") {
136300
136557
  const { listAccounts: listAccounts2, loadAccounts: loadAccounts3, removeAccount: removeAccount2 } = await Promise.resolve().then(() => (init_store(), exports_store));
136301
136558
  const { importableEnvCreds: importableEnvCreds2, importEnvCred: importEnvCred2, importableCloudCreds: importableCloudCreds2, importCloudCred: importCloudCred2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
@@ -136343,6 +136600,18 @@ Importable from your env (gearbox auth import): ${imp.map((c) => c.envVar).join(
136343
136600
  }
136344
136601
  process.exit(0);
136345
136602
  }
136603
+ if (!anyProviderAvailable()) {
136604
+ if (process.stdin.isTTY && process.stdout.isTTY && process.env.GEARBOX_SKIP_ONBOARD !== "1") {
136605
+ const ready = await runCliOnboarding();
136606
+ if (!ready || !anyProviderAvailable())
136607
+ process.exit(0);
136608
+ } else {
136609
+ console.log("Gearbox needs one provider before the coding app can open.");
136610
+ console.log("Run: gearbox onboard");
136611
+ console.log("Or: gearbox auth add <api-key>");
136612
+ process.exit(1);
136613
+ }
136614
+ }
136346
136615
  var mi = args.indexOf("--model");
136347
136616
  var preferred = mi >= 0 ? args[mi + 1] : undefined;
136348
136617
  var pinned = preferred ?? loadPrefs().pinnedModel;
package/install.ps1 CHANGED
@@ -81,6 +81,12 @@ node "$TargetCli" %*
81
81
  if (-not $AlreadyOnPath) {
82
82
  Write-Host "Open a new terminal if this shell does not pick up the PATH change."
83
83
  }
84
+
85
+ if ($env:GEARBOX_SKIP_ONBOARD -ne "1") {
86
+ Write-Host ""
87
+ Write-Host "Starting setup..."
88
+ & $CmdPath onboard
89
+ }
84
90
  }
85
91
  finally {
86
92
  Remove-Item -Recurse -Force $Temp -ErrorAction SilentlyContinue
package/install.sh CHANGED
@@ -93,3 +93,13 @@ case ":${PATH}:" in
93
93
  echo "Then run: gearbox"
94
94
  ;;
95
95
  esac
96
+
97
+ if [[ "${GEARBOX_SKIP_ONBOARD:-}" != "1" ]]; then
98
+ echo ""
99
+ echo "Starting setup..."
100
+ if [[ -r /dev/tty && -w /dev/tty ]]; then
101
+ "${BIN_DIR}/gearbox" onboard < /dev/tty > /dev/tty
102
+ else
103
+ echo "No interactive terminal detected. Run: ${BIN_DIR}/gearbox onboard"
104
+ fi
105
+ fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gearbox-code",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",