gearbox-code 0.1.6 → 0.1.8

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,111 +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.
11
+ Windows PowerShell:
15
12
 
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.
23
-
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.
20
+
21
+ Run without installing:
31
22
 
32
23
  ```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
24
+ npx gearbox-code@latest
39
25
  ```
40
26
 
41
- No provider configured? Gearbox opens a setup screen and will not run a fake model. Preview the look without running anything:
27
+ ## First Run
28
+
29
+ Gearbox needs one provider account before it opens the coding app. The installer
30
+ runs setup automatically. You can also run it yourself:
42
31
 
43
32
  ```bash
44
- bun run scripts/preview.tsx
33
+ gearbox onboard
45
34
  ```
46
35
 
47
- ## Install
48
-
49
- One command, no sudo, no npm global permissions:
36
+ Common setup commands:
50
37
 
51
38
  ```bash
52
- curl -fsSL https://unpkg.com/gearbox-code@latest/install.sh | 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
53
43
  ```
54
44
 
55
- The installer is served from the published npm package, downloads the latest
56
- `gearbox-code` tarball, and creates a user-owned `gearbox` command in
57
- `~/.local/bin`.
58
-
59
- Then:
45
+ After setup:
60
46
 
61
47
  ```bash
62
- gearbox auth add <api-key> # each person uses their own provider account
63
- cd ~/any/project && gearbox # the current directory is the workspace
48
+ cd ~/your-project
49
+ gearbox
64
50
  ```
65
51
 
66
- If `~/.local/bin` is not on your PATH, the installer prints the exact line to
67
- add to your shell config.
52
+ No account configured means no fake/demo model: Gearbox runs onboarding first.
68
53
 
69
- You can still run without installing:
54
+ ## Uninstall
55
+
56
+ macOS, Linux, WSL:
70
57
 
71
58
  ```bash
72
- npx gearbox-code@latest
59
+ rm -f ~/.local/bin/gearbox
60
+ rm -rf ~/.local/share/gearbox
73
61
  ```
74
62
 
75
- **Upgrade** later by rerunning the install command. `gearbox upgrade` still works
76
- for git checkouts.
63
+ Windows PowerShell:
77
64
 
78
- ## Develop From Source
65
+ ```powershell
66
+ Remove-Item "$env:LOCALAPPDATA\Gearbox" -Recurse -Force
67
+ ```
79
68
 
80
- Requires [Bun](https://bun.sh). Clone the repo, then:
69
+ If you previously installed with npm global:
81
70
 
82
71
  ```bash
83
- bun install
84
- bun run src/cli.tsx
72
+ npm uninstall -g gearbox-code
85
73
  ```
86
74
 
87
- **Standalone binary** (no clone/install on the target, same OS/arch):
75
+ ## What It Is
88
76
 
89
- ```bash
90
- bun run build # dist/gearbox (single ~64MB executable)
91
- cp dist/gearbox ~/.bun/bin/ # or anywhere on PATH; share the file directly
92
- ```
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.
93
80
 
94
- > **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.
95
83
 
96
84
  ## Develop
97
85
 
86
+ Requires [Bun](https://bun.sh).
87
+
98
88
  ```bash
99
- bun test # render + agent tests (no API key needed)
89
+ bun install
90
+ bun run src/cli.tsx
91
+ bun test
100
92
  bun run typecheck
101
93
  ```
102
-
103
- ## Principles
104
-
105
- - **Open + free.** MIT. No paid dependencies, no hosted backend, no telemetry. The only cost is your own model calls on your own keys.
106
- - **Beautiful + calm.** One accent color, generous spacing, consistent glyphs. The whole look lives in `src/ui/theme.ts`.
107
- - **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).
108
-
109
- ## Status
110
-
111
- 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
  }
@@ -133686,6 +133723,235 @@ function ActivityRail({ items, width }) {
133686
133723
  ]
133687
133724
  }, undefined, true, undefined, this);
133688
133725
  }
133726
+ function SetupSplash({ state, width, skin, splashSize }) {
133727
+ const providers = featuredApiKeyProviders().slice(0, width >= 92 ? 12 : 8);
133728
+ const left = providers.filter((_, i2) => i2 % 2 === 0);
133729
+ const right = providers.filter((_, i2) => i2 % 2 === 1);
133730
+ const detected = state.importable.length + state.cloudImportable.length;
133731
+ const panelWidth = Math.min(Math.max(width - 4, 12), 104);
133732
+ const showTwoCols = panelWidth >= 82;
133733
+ const firstColumn = showTwoCols ? left : providers;
133734
+ const secondColumn = showTwoCols ? right : [];
133735
+ const providerLine = (id, label) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133736
+ color: color.dim,
133737
+ children: [
133738
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133739
+ color: color.accent,
133740
+ children: [
133741
+ "/account add ",
133742
+ id
133743
+ ]
133744
+ }, undefined, true, undefined, this),
133745
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133746
+ color: color.faint,
133747
+ children: [
133748
+ " ",
133749
+ label
133750
+ ]
133751
+ }, undefined, true, undefined, this)
133752
+ ]
133753
+ }, id, true, undefined, this);
133754
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133755
+ flexDirection: "column",
133756
+ alignItems: "center",
133757
+ children: [
133758
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MascotSplash, {
133759
+ skin,
133760
+ size: splashSize
133761
+ }, undefined, false, undefined, this),
133762
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133763
+ marginTop: 1,
133764
+ flexDirection: "column",
133765
+ alignItems: "center",
133766
+ children: [
133767
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133768
+ color: color.accent,
133769
+ bold: true,
133770
+ children: "GEARBOX"
133771
+ }, undefined, false, undefined, this),
133772
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133773
+ color: color.text,
133774
+ children: "one terminal, every model account you already pay for"
133775
+ }, undefined, false, undefined, this),
133776
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133777
+ color: color.faint,
133778
+ children: "Set up one provider. Gearbox routes from there."
133779
+ }, undefined, false, undefined, this)
133780
+ ]
133781
+ }, undefined, true, undefined, this),
133782
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133783
+ marginTop: 1,
133784
+ width: panelWidth,
133785
+ borderStyle: "round",
133786
+ borderColor: color.accentDim,
133787
+ paddingX: 2,
133788
+ paddingY: 1,
133789
+ flexDirection: "column",
133790
+ children: [
133791
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133792
+ justifyContent: "space-between",
133793
+ children: [
133794
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133795
+ color: color.text,
133796
+ bold: true,
133797
+ children: "start here"
133798
+ }, undefined, false, undefined, this),
133799
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133800
+ color: detected ? color.ok : color.faint,
133801
+ children: detected ? `${detected} detected` : "no account yet"
133802
+ }, undefined, false, undefined, this)
133803
+ ]
133804
+ }, undefined, true, undefined, this),
133805
+ detected ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133806
+ marginTop: 1,
133807
+ children: [
133808
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133809
+ color: color.ok,
133810
+ children: [
133811
+ glyph.on,
133812
+ " "
133813
+ ]
133814
+ }, undefined, true, undefined, this),
133815
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133816
+ color: color.text,
133817
+ children: "Credentials found on this machine: "
133818
+ }, undefined, false, undefined, this),
133819
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133820
+ color: color.accent,
133821
+ children: "/account import"
133822
+ }, undefined, false, undefined, this)
133823
+ ]
133824
+ }, undefined, true, undefined, this) : null,
133825
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133826
+ marginTop: 1,
133827
+ flexDirection: showTwoCols ? "row" : "column",
133828
+ children: [
133829
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133830
+ flexDirection: "column",
133831
+ width: showTwoCols ? Math.floor((panelWidth - 6) / 2) : undefined,
133832
+ children: [
133833
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133834
+ color: color.faint,
133835
+ children: "API key"
133836
+ }, undefined, false, undefined, this),
133837
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133838
+ color: color.text,
133839
+ children: [
133840
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133841
+ color: color.accent,
133842
+ children: "/account add <api-key>"
133843
+ }, undefined, false, undefined, this),
133844
+ " auto-detect when possible"
133845
+ ]
133846
+ }, undefined, true, undefined, this),
133847
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133848
+ color: color.text,
133849
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133850
+ color: color.accent,
133851
+ children: "/account add <provider> <api-key>"
133852
+ }, undefined, false, undefined, this)
133853
+ }, undefined, false, undefined, this)
133854
+ ]
133855
+ }, undefined, true, undefined, this),
133856
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133857
+ flexDirection: "column",
133858
+ marginLeft: showTwoCols ? 4 : 0,
133859
+ marginTop: showTwoCols ? 0 : 1,
133860
+ children: [
133861
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133862
+ color: color.faint,
133863
+ children: "Subscriptions and cloud"
133864
+ }, undefined, false, undefined, this),
133865
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133866
+ color: color.text,
133867
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133868
+ color: color.accent,
133869
+ children: "/account add azure <endpoint> <api-key>"
133870
+ }, undefined, false, undefined, this)
133871
+ }, undefined, false, undefined, this),
133872
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133873
+ color: color.text,
133874
+ children: [
133875
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133876
+ color: color.accent,
133877
+ children: "/account add claude"
133878
+ }, undefined, false, undefined, this),
133879
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133880
+ color: color.faint,
133881
+ children: " "
133882
+ }, undefined, false, undefined, this),
133883
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133884
+ color: color.accent,
133885
+ children: "/account add codex"
133886
+ }, undefined, false, undefined, this)
133887
+ ]
133888
+ }, undefined, true, undefined, this)
133889
+ ]
133890
+ }, undefined, true, undefined, this)
133891
+ ]
133892
+ }, undefined, true, undefined, this),
133893
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133894
+ marginTop: 1,
133895
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133896
+ color: color.faint,
133897
+ children: glyph.rule.repeat(Math.max(panelWidth - 6, 20))
133898
+ }, undefined, false, undefined, this)
133899
+ }, undefined, false, undefined, this),
133900
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133901
+ marginTop: 1,
133902
+ flexDirection: showTwoCols ? "row" : "column",
133903
+ children: [
133904
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133905
+ flexDirection: "column",
133906
+ width: showTwoCols ? Math.floor((panelWidth - 6) / 2) : undefined,
133907
+ children: [
133908
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133909
+ color: color.faint,
133910
+ children: "Common providers"
133911
+ }, undefined, false, undefined, this),
133912
+ firstColumn.map((p) => providerLine(p.id, p.label))
133913
+ ]
133914
+ }, undefined, true, undefined, this),
133915
+ secondColumn.length ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133916
+ flexDirection: "column",
133917
+ marginLeft: 4,
133918
+ children: [
133919
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133920
+ color: color.faint,
133921
+ children: " "
133922
+ }, undefined, false, undefined, this),
133923
+ secondColumn.map((p) => providerLine(p.id, p.label))
133924
+ ]
133925
+ }, undefined, true, undefined, this) : null
133926
+ ]
133927
+ }, undefined, true, undefined, this),
133928
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
133929
+ marginTop: 1,
133930
+ justifyContent: "space-between",
133931
+ children: [
133932
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133933
+ color: color.faint,
133934
+ children: "Full catalog: "
133935
+ }, undefined, false, undefined, this),
133936
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133937
+ color: color.accent,
133938
+ children: "/onboard providers"
133939
+ }, undefined, false, undefined, this),
133940
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133941
+ color: color.faint,
133942
+ children: " Help: "
133943
+ }, undefined, false, undefined, this),
133944
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
133945
+ color: color.accent,
133946
+ children: "/account"
133947
+ }, undefined, false, undefined, this)
133948
+ ]
133949
+ }, undefined, true, undefined, this)
133950
+ ]
133951
+ }, undefined, true, undefined, this)
133952
+ ]
133953
+ }, undefined, true, undefined, this);
133954
+ }
133689
133955
  function App2({ selector: initialSelector, runner, fullscreen = false, resumeId }) {
133690
133956
  const { exit } = use_app_default();
133691
133957
  const { stdin, isRawModeSupported, setRawMode } = use_stdin_default();
@@ -135727,176 +135993,92 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
135727
135993
  const hero = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135728
135994
  flexDirection: "column",
135729
135995
  alignItems: "center",
135730
- children: [
135731
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MascotSplash, {
135732
- skin: ghostSkin,
135733
- size: splashSize
135734
- }, undefined, false, undefined, this),
135735
- setupRequired ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135736
- marginTop: 1,
135737
- flexDirection: "column",
135738
- alignItems: "center",
135739
- children: [
135740
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135741
- color: color.text,
135742
- children: "setup required"
135743
- }, undefined, false, undefined, this),
135744
- onboardingState.importable.length || onboardingState.cloudImportable.length ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135745
- color: color.faint,
135746
- children: [
135747
- "detected credentials: ",
135748
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135749
- color: color.accent,
135750
- children: "/account import"
135751
- }, undefined, false, undefined, this)
135752
- ]
135753
- }, undefined, true, undefined, this) : null,
135754
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135755
- color: color.faint,
135756
- children: [
135757
- "add any provider key: ",
135758
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135759
- color: color.accent,
135760
- children: "/account add <provider> <api-key>"
135761
- }, undefined, false, undefined, this)
135762
- ]
135763
- }, undefined, true, undefined, this),
135764
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135765
- color: color.faint,
135766
- children: [
135767
- "paste-detect: ",
135768
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135769
- color: color.accent,
135770
- children: "/account add <api-key>"
135771
- }, undefined, false, undefined, this),
135772
- " · list: ",
135773
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135774
- color: color.accent,
135775
- children: "/onboard providers"
135776
- }, undefined, false, undefined, this)
135777
- ]
135778
- }, undefined, true, undefined, this),
135779
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135780
- marginTop: 1,
135781
- flexDirection: "column",
135782
- alignItems: "flex-start",
135783
- children: [
135784
- featuredApiKeyProviders().slice(0, 6).map((p) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135785
- color: color.dim,
135786
- children: [
135787
- " ",
135788
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135789
- color: color.accent,
135790
- children: [
135791
- "/account add ",
135792
- p.id,
135793
- " <api-key>"
135794
- ]
135795
- }, undefined, true, undefined, this),
135796
- " ",
135797
- p.label
135798
- ]
135799
- }, p.id, true, undefined, this)),
135800
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135801
- color: color.dim,
135802
- children: [
135803
- " ",
135804
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135805
- color: color.accent,
135806
- children: "/account add azure <endpoint> <api-key>"
135807
- }, undefined, false, undefined, this),
135808
- " Azure OpenAI / Foundry"
135809
- ]
135810
- }, undefined, true, undefined, this)
135811
- ]
135812
- }, undefined, true, undefined, this),
135813
- onboardingState.hasClaudeCli || onboardingState.hasCodexCli ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135814
- color: color.faint,
135815
- children: [
135816
- onboardingState.hasClaudeCli ? "/account add claude" : "",
135817
- onboardingState.hasClaudeCli && onboardingState.hasCodexCli ? " · " : "",
135818
- onboardingState.hasCodexCli ? "/account add codex" : ""
135819
- ]
135820
- }, undefined, true, undefined, this) : null
135821
- ]
135822
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(jsx_dev_runtime12.Fragment, {
135823
- children: [
135824
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135825
- marginTop: 1,
135826
- children: [
135827
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135828
- color: color.dim,
135829
- children: "talk or type "
135830
- }, undefined, false, undefined, this),
135831
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135832
- color: color.faint,
135833
- children: [
135834
- glyph.bullet,
135835
- " "
135836
- ]
135837
- }, undefined, true, undefined, this),
135838
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135839
- color: color.accentDim,
135840
- children: "/"
135841
- }, undefined, false, undefined, this),
135842
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135843
- color: color.dim,
135844
- children: "commands "
135845
- }, undefined, false, undefined, this),
135846
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135847
- color: color.accentDim,
135848
- children: "@"
135849
- }, undefined, false, undefined, this),
135850
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135851
- color: color.dim,
135852
- children: "files "
135853
- }, undefined, false, undefined, this),
135854
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135855
- color: color.accentDim,
135856
- children: "!"
135857
- }, undefined, false, undefined, this),
135858
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135859
- color: color.dim,
135860
- children: "shell"
135861
- }, undefined, false, undefined, this)
135862
- ]
135863
- }, undefined, true, undefined, this),
135864
- firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135865
- marginTop: 1,
135866
- flexDirection: "column",
135867
- alignItems: "center",
135868
- children: [
135869
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135870
- color: color.faint,
135871
- children: [
135872
- "new here? press ",
135873
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135874
- color: color.accent,
135875
- children: "?"
135876
- }, undefined, false, undefined, this),
135877
- " for shortcuts · ",
135878
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135879
- color: color.accent,
135880
- children: "shift+tab"
135881
- }, undefined, false, undefined, this),
135882
- " cycles modes · ",
135883
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135884
- color: color.accent,
135885
- children: "⌃Y"
135886
- }, undefined, false, undefined, this),
135887
- " copies the last reply"
135888
- ]
135889
- }, undefined, true, undefined, this),
135890
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135891
- color: color.faint,
135892
- children: "/config inline on for terminal scrollback · /keys for shortcuts"
135893
- }, undefined, false, undefined, this)
135894
- ]
135895
- }, undefined, true, undefined, this) : null
135896
- ]
135897
- }, undefined, true, undefined, this)
135898
- ]
135899
- }, undefined, true, undefined, this);
135996
+ children: setupRequired ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(SetupSplash, {
135997
+ state: onboardingState,
135998
+ width,
135999
+ skin: ghostSkin,
136000
+ splashSize
136001
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(jsx_dev_runtime12.Fragment, {
136002
+ children: [
136003
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MascotSplash, {
136004
+ skin: ghostSkin,
136005
+ size: splashSize
136006
+ }, undefined, false, undefined, this),
136007
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
136008
+ marginTop: 1,
136009
+ children: [
136010
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136011
+ color: color.dim,
136012
+ children: "talk or type "
136013
+ }, undefined, false, undefined, this),
136014
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136015
+ color: color.faint,
136016
+ children: [
136017
+ glyph.bullet,
136018
+ " "
136019
+ ]
136020
+ }, undefined, true, undefined, this),
136021
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136022
+ color: color.accentDim,
136023
+ children: "/"
136024
+ }, undefined, false, undefined, this),
136025
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136026
+ color: color.dim,
136027
+ children: "commands "
136028
+ }, undefined, false, undefined, this),
136029
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136030
+ color: color.accentDim,
136031
+ children: "@"
136032
+ }, undefined, false, undefined, this),
136033
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136034
+ color: color.dim,
136035
+ children: "files "
136036
+ }, undefined, false, undefined, this),
136037
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136038
+ color: color.accentDim,
136039
+ children: "!"
136040
+ }, undefined, false, undefined, this),
136041
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136042
+ color: color.dim,
136043
+ children: "shell"
136044
+ }, undefined, false, undefined, this)
136045
+ ]
136046
+ }, undefined, true, undefined, this),
136047
+ firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
136048
+ marginTop: 1,
136049
+ flexDirection: "column",
136050
+ alignItems: "center",
136051
+ children: [
136052
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136053
+ color: color.faint,
136054
+ children: [
136055
+ "new here? press ",
136056
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136057
+ color: color.accent,
136058
+ children: "?"
136059
+ }, undefined, false, undefined, this),
136060
+ " for shortcuts · ",
136061
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136062
+ color: color.accent,
136063
+ children: "shift+tab"
136064
+ }, undefined, false, undefined, this),
136065
+ " cycles modes · ",
136066
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136067
+ color: color.accent,
136068
+ children: "⌃Y"
136069
+ }, undefined, false, undefined, this),
136070
+ " copies the last reply"
136071
+ ]
136072
+ }, undefined, true, undefined, this),
136073
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
136074
+ color: color.faint,
136075
+ children: "/config inline on for terminal scrollback · /keys for shortcuts"
136076
+ }, undefined, false, undefined, this)
136077
+ ]
136078
+ }, undefined, true, undefined, this) : null
136079
+ ]
136080
+ }, undefined, true, undefined, this)
136081
+ }, undefined, false, undefined, this);
135900
136082
  const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135901
136083
  flexDirection: "column",
135902
136084
  children: [
@@ -136099,8 +136281,153 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
136099
136281
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
136100
136282
  process.env.LANG = process.env.LANG || "en_US.UTF-8";
136101
136283
  process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
136102
- var VERSION16 = "0.1.6";
136284
+ var VERSION16 = "0.1.8";
136103
136285
  var args = process.argv.slice(2);
136286
+ async function runCliOnboarding() {
136287
+ const { listAccounts: listAccounts2 } = await Promise.resolve().then(() => (init_store(), exports_store));
136288
+ const { importableEnvCreds: importableEnvCreds2, importEnvCred: importEnvCred2, importableCloudCreds: importableCloudCreds2, importCloudCred: importCloudCred2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
136289
+ 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));
136290
+ const { subscriptionEnv: subscriptionEnv2 } = await Promise.resolve().then(() => (init_cli_backend(), exports_cli_backend));
136291
+ const { detectProviderByKey: detectProviderByKey2 } = await Promise.resolve().then(() => (init_catalog(), exports_catalog));
136292
+ const { which: which2 } = await Promise.resolve().then(() => (init_proc(), exports_proc));
136293
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
136294
+ const ask = async (q) => (await rl.question(q)).trim();
136295
+ const providerRows = () => addableProviders2().map((p) => ` ${p.id.padEnd(16)} ${p.label}`).join(`
136296
+ `);
136297
+ const testAndReport = async (account) => {
136298
+ console.log("Testing credential...");
136299
+ const t2 = await testAccount2(account);
136300
+ console.log(t2.ok ? `OK: ${t2.message}` : `Stored, but the test failed: ${t2.message}`);
136301
+ };
136302
+ const addSubscription = async (provider) => {
136303
+ const res = addCliAccount2(provider);
136304
+ console.log(res.message);
136305
+ if (!res.ok || !res.account || res.account.auth.kind !== "cli")
136306
+ return false;
136307
+ const bin = res.account.auth.binary;
136308
+ const profile = res.account.auth.loginProfile;
136309
+ let status = await cliAuthStatus2(bin, profile);
136310
+ if (!status.loggedIn) {
136311
+ console.log(`Starting ${bin} sign-in...`);
136312
+ const r2 = spawnSync(bin, cliLoginArgs2(bin), { stdio: "inherit", env: subscriptionEnv2(bin, profile) });
136313
+ if ((r2.status ?? 1) !== 0)
136314
+ return false;
136315
+ status = await cliAuthStatus2(bin, profile);
136316
+ }
136317
+ if (!status.loggedIn) {
136318
+ console.log(`${bin} did not report a completed sign-in.`);
136319
+ return false;
136320
+ }
136321
+ console.log(`OK: ${bin} subscription ready${status.detail ? ` (${status.detail})` : ""}`);
136322
+ return true;
136323
+ };
136324
+ try {
136325
+ console.log("");
136326
+ console.log("Gearbox setup");
136327
+ console.log("One provider account is required before the coding app opens.");
136328
+ console.log("");
136329
+ while (!anyProviderAvailable()) {
136330
+ const env3 = importableEnvCreds2();
136331
+ const cloud = importableCloudCreds2();
136332
+ const existing = listAccounts2();
136333
+ if (existing.length)
136334
+ break;
136335
+ console.log("Choose a setup path:");
136336
+ if (env3.length || cloud.length)
136337
+ console.log(" 1) Import detected credentials");
136338
+ console.log(" 2) Paste an API key");
136339
+ console.log(" 3) Choose provider + API key");
136340
+ console.log(" 4) Azure OpenAI / Azure AI Foundry");
136341
+ if (which2("claude"))
136342
+ console.log(" 5) Claude subscription CLI");
136343
+ if (which2("codex"))
136344
+ console.log(" 6) ChatGPT subscription CLI");
136345
+ console.log(" p) Show providers");
136346
+ console.log(" q) Quit setup");
136347
+ console.log("");
136348
+ const choice = (await ask("Selection: ")).toLowerCase();
136349
+ if (choice === "q" || choice === "quit" || choice === "skip") {
136350
+ console.log("");
136351
+ console.log("Setup skipped. Run `gearbox onboard` when you are ready.");
136352
+ return false;
136353
+ }
136354
+ if (choice === "p" || choice === "providers") {
136355
+ console.log("");
136356
+ console.log(providerRows());
136357
+ console.log("");
136358
+ continue;
136359
+ }
136360
+ if (choice === "1" && (env3.length || cloud.length)) {
136361
+ for (const c of env3)
136362
+ await importEnvCred2(c);
136363
+ for (const c of cloud)
136364
+ await importCloudCred2(c);
136365
+ console.log(`Imported ${env3.length + cloud.length} credential${env3.length + cloud.length === 1 ? "" : "s"}.`);
136366
+ break;
136367
+ }
136368
+ if (choice === "2") {
136369
+ const key = await ask("Paste API key: ");
136370
+ if (!key)
136371
+ continue;
136372
+ const detected = detectProviderByKey2(key);
136373
+ if (!detected) {
136374
+ console.log("Could not detect the provider from that key. Use option 3.");
136375
+ continue;
136376
+ }
136377
+ const res = await addByPastedKey2(key);
136378
+ console.log(res.message);
136379
+ if (res.ok && res.account) {
136380
+ await testAndReport(res.account);
136381
+ break;
136382
+ }
136383
+ continue;
136384
+ }
136385
+ if (choice === "3") {
136386
+ console.log("");
136387
+ console.log(providerRows());
136388
+ console.log("");
136389
+ const provider = await ask("Provider id: ");
136390
+ const key = await ask("API key: ");
136391
+ const res = await addApiKeyAccount2(provider, key);
136392
+ console.log(res.message);
136393
+ if (res.ok && res.account) {
136394
+ await testAndReport(res.account);
136395
+ break;
136396
+ }
136397
+ continue;
136398
+ }
136399
+ if (choice === "4") {
136400
+ const endpoint = await ask("Azure resource name or endpoint: ");
136401
+ const key = await ask("API key: ");
136402
+ const apiVersion = await ask("API version (optional): ");
136403
+ const res = /^https?:\/\//i.test(endpoint) ? await addAzureFoundryAccount2(endpoint, key) : await addAzureAccount2(endpoint, key, { apiVersion: apiVersion || undefined });
136404
+ console.log(res.message);
136405
+ if (res.ok && res.account) {
136406
+ await testAndReport(res.account);
136407
+ break;
136408
+ }
136409
+ continue;
136410
+ }
136411
+ if (choice === "5" && which2("claude")) {
136412
+ if (await addSubscription("claude-cli"))
136413
+ break;
136414
+ continue;
136415
+ }
136416
+ if (choice === "6" && which2("codex")) {
136417
+ if (await addSubscription("codex-cli"))
136418
+ break;
136419
+ continue;
136420
+ }
136421
+ console.log("Choose one of the listed options.");
136422
+ }
136423
+ console.log("");
136424
+ console.log("Gearbox is ready.");
136425
+ console.log("Run `gearbox` inside a project.");
136426
+ return true;
136427
+ } finally {
136428
+ rl.close();
136429
+ }
136430
+ }
136104
136431
  if (args[0] === "upgrade" || args[0] === "update") {
136105
136432
  const root2 = resolve11(import.meta.dir, "..");
136106
136433
  if (!existsSync8(resolve11(root2, ".git"))) {
@@ -136125,6 +136452,7 @@ if (args.includes("--help") || args.includes("-h")) {
136125
136452
 
136126
136453
  Usage:
136127
136454
  gearbox start in the current directory (it becomes the workspace)
136455
+ gearbox onboard set up a provider before opening the app
136128
136456
  gearbox --model <name> start with a specific model
136129
136457
  gearbox --continue resume the most recent session in this directory
136130
136458
  gearbox upgrade pull the latest version + reinstall deps
@@ -136139,6 +136467,7 @@ Options:
136139
136467
  -h, --help this help
136140
136468
 
136141
136469
  Set up at least one provider first:
136470
+ gearbox onboard
136142
136471
  gearbox auth add <api-key>
136143
136472
  gearbox auth add <provider> <api-key>
136144
136473
  gearbox auth import
@@ -136151,6 +136480,10 @@ if (args.includes("--version") || args.includes("-v")) {
136151
136480
  console.log(VERSION16);
136152
136481
  process.exit(0);
136153
136482
  }
136483
+ if (args[0] === "onboard" || args[0] === "setup") {
136484
+ await runCliOnboarding();
136485
+ process.exit(0);
136486
+ }
136154
136487
  if (args[0] === "auth") {
136155
136488
  const { listAccounts: listAccounts2, loadAccounts: loadAccounts3, removeAccount: removeAccount2 } = await Promise.resolve().then(() => (init_store(), exports_store));
136156
136489
  const { importableEnvCreds: importableEnvCreds2, importEnvCred: importEnvCred2, importableCloudCreds: importableCloudCreds2, importCloudCred: importCloudCred2 } = await Promise.resolve().then(() => (init_detect(), exports_detect));
@@ -136198,6 +136531,18 @@ Importable from your env (gearbox auth import): ${imp.map((c) => c.envVar).join(
136198
136531
  }
136199
136532
  process.exit(0);
136200
136533
  }
136534
+ if (!anyProviderAvailable()) {
136535
+ if (process.stdin.isTTY && process.stdout.isTTY && process.env.GEARBOX_SKIP_ONBOARD !== "1") {
136536
+ const ready = await runCliOnboarding();
136537
+ if (!ready || !anyProviderAvailable())
136538
+ process.exit(0);
136539
+ } else {
136540
+ console.log("Gearbox needs one provider before the coding app can open.");
136541
+ console.log("Run: gearbox onboard");
136542
+ console.log("Or: gearbox auth add <api-key>");
136543
+ process.exit(1);
136544
+ }
136545
+ }
136201
136546
  var mi = args.indexOf("--model");
136202
136547
  var preferred = mi >= 0 ? args[mi + 1] : undefined;
136203
136548
  var pinned = preferred ?? loadPrefs().pinnedModel;
package/install.ps1 ADDED
@@ -0,0 +1,93 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ # Public Gearbox installer for Windows PowerShell.
4
+ #
5
+ # Installs the published npm package into user-owned directories:
6
+ # %LOCALAPPDATA%\Gearbox\versions\<version>\cli.mjs
7
+ # %LOCALAPPDATA%\Gearbox\bin\gearbox.cmd
8
+ #
9
+ # It avoids npm global installs, admin privileges, Program Files, and system PATH
10
+ # writes. The user PATH is updated when needed.
11
+
12
+ $PackageName = if ($env:GEARBOX_PACKAGE) { $env:GEARBOX_PACKAGE } else { "gearbox-code" }
13
+ $Version = if ($env:GEARBOX_VERSION) { $env:GEARBOX_VERSION } else { "latest" }
14
+ $InstallRoot = if ($env:GEARBOX_INSTALL_DIR) { $env:GEARBOX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Gearbox" }
15
+ $BinDir = if ($env:GEARBOX_BIN_DIR) { $env:GEARBOX_BIN_DIR } else { Join-Path $InstallRoot "bin" }
16
+
17
+ function Require-Command($Name, $InstallHint) {
18
+ if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
19
+ Write-Error "Gearbox installer needs '$Name'. $InstallHint"
20
+ }
21
+ }
22
+
23
+ Require-Command "node" "Install Node.js, then rerun this installer."
24
+ Require-Command "tar" "Install a current Windows build or Git for Windows, then rerun this installer."
25
+
26
+ $Temp = Join-Path ([System.IO.Path]::GetTempPath()) ("gearbox-install-" + [System.Guid]::NewGuid().ToString("N"))
27
+ New-Item -ItemType Directory -Path $Temp | Out-Null
28
+
29
+ try {
30
+ $MetaUrl = "https://registry.npmjs.org/$PackageName/$Version"
31
+ $MetaFile = Join-Path $Temp "meta.json"
32
+ Write-Host "-> Fetching $PackageName@$Version"
33
+ Invoke-WebRequest -Uri $MetaUrl -OutFile $MetaFile -UseBasicParsing
34
+
35
+ $Meta = Get-Content $MetaFile -Raw | ConvertFrom-Json
36
+ $ResolvedVersion = $Meta.version
37
+ $TarballUrl = $Meta.dist.tarball
38
+ if (-not $ResolvedVersion -or -not $TarballUrl) {
39
+ Write-Error "Could not resolve $PackageName@$Version from npm."
40
+ }
41
+
42
+ $TargetDir = Join-Path (Join-Path $InstallRoot "versions") $ResolvedVersion
43
+ $Archive = Join-Path $Temp "package.tgz"
44
+ $ExtractDir = Join-Path $Temp "extract"
45
+
46
+ Write-Host "-> Downloading $PackageName@$ResolvedVersion"
47
+ Invoke-WebRequest -Uri $TarballUrl -OutFile $Archive -UseBasicParsing
48
+ New-Item -ItemType Directory -Force -Path $ExtractDir, $TargetDir, $BinDir | Out-Null
49
+ tar -xzf $Archive -C $ExtractDir
50
+
51
+ $Cli = Join-Path $ExtractDir "package\dist\cli.mjs"
52
+ if (-not (Test-Path $Cli)) {
53
+ Write-Error "Package did not contain dist/cli.mjs."
54
+ }
55
+
56
+ $TargetCli = Join-Path $TargetDir "cli.mjs"
57
+ Copy-Item $Cli $TargetCli -Force
58
+
59
+ $CmdPath = Join-Path $BinDir "gearbox.cmd"
60
+ $Cmd = @"
61
+ @echo off
62
+ node "$TargetCli" %*
63
+ "@
64
+ Set-Content -Path $CmdPath -Value $Cmd -Encoding ASCII
65
+
66
+ $UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
67
+ $PathParts = @()
68
+ if ($UserPath) { $PathParts = $UserPath -split ";" }
69
+ $AlreadyOnPath = $PathParts | Where-Object { $_ -ieq $BinDir }
70
+ if (-not $AlreadyOnPath) {
71
+ $NextPath = if ($UserPath) { "$UserPath;$BinDir" } else { $BinDir }
72
+ [Environment]::SetEnvironmentVariable("Path", $NextPath, "User")
73
+ $env:Path = "$env:Path;$BinDir"
74
+ }
75
+
76
+ Write-Host ""
77
+ Write-Host "Installed Gearbox $ResolvedVersion"
78
+ Write-Host " $CmdPath"
79
+ Write-Host ""
80
+ Write-Host "Run it with: gearbox"
81
+ if (-not $AlreadyOnPath) {
82
+ Write-Host "Open a new terminal if this shell does not pick up the PATH change."
83
+ }
84
+
85
+ if ($env:GEARBOX_SKIP_ONBOARD -ne "1") {
86
+ Write-Host ""
87
+ Write-Host "Starting setup..."
88
+ & $CmdPath onboard
89
+ }
90
+ }
91
+ finally {
92
+ Remove-Item -Recurse -Force $Temp -ErrorAction SilentlyContinue
93
+ }
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.6",
3
+ "version": "0.1.8",
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",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "files": [
11
11
  "dist/cli.mjs",
12
- "install.sh"
12
+ "install.sh",
13
+ "install.ps1"
13
14
  ],
14
15
  "scripts": {
15
16
  "start": "bun run src/cli.tsx",