gearbox-code 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/cli.mjs +294 -152
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -31,12 +31,14 @@ A beautiful, simple coding harness for the terminal. It reads, edits, and runs y
31
31
 
32
32
  ```bash
33
33
  bun install
34
- export ANTHROPIC_API_KEY=... # or OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY / DEEPSEEK_API_KEY
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
35
37
  bun start # or: bun run src/cli.tsx
36
38
  bun start -- --model gemini-flash # pick a model
37
39
  ```
38
40
 
39
- No key? It launches in demo mode so you can see the interface. Preview the look without running anything:
41
+ No provider configured? Gearbox opens a setup screen and will not run a fake model. Preview the look without running anything:
40
42
 
41
43
  ```bash
42
44
  bun run scripts/preview.tsx
@@ -48,7 +50,7 @@ Requires [Bun](https://bun.sh). Clone the repo, then:
48
50
 
49
51
  ```bash
50
52
  ./install.sh # bun install + bun link → 'gearbox' on your PATH
51
- export ANTHROPIC_API_KEY=... # each person uses their own key (add to ~/.zshrc)
53
+ gearbox auth add <api-key> # each person uses their own provider account
52
54
  cd ~/any/project && gearbox # the current directory is the workspace
53
55
  ```
54
56
 
package/dist/cli.mjs CHANGED
@@ -64171,6 +64171,104 @@ var init_detect = __esm(() => {
64171
64171
  init_store();
64172
64172
  });
64173
64173
 
64174
+ // src/accounts/onboarding.ts
64175
+ function normalizeProviderId(input) {
64176
+ const key = input.trim().toLowerCase().replace(/[_\s]+/g, "-");
64177
+ return ALIASES[key] ?? key;
64178
+ }
64179
+ function apiKeyProviders() {
64180
+ return CATALOG.filter((p) => (p.authKind === "api-key" || p.authKind === "openai-compat") && p.group !== "local" && (p.authKind !== "openai-compat" || Boolean(p.baseUrl)));
64181
+ }
64182
+ function featuredApiKeyProviders() {
64183
+ const providers = apiKeyProviders();
64184
+ const byId = new Map(providers.map((p) => [p.id, p]));
64185
+ const first = FEATURED.map((id) => byId.get(id)).filter((p) => Boolean(p));
64186
+ const rest2 = providers.filter((p) => !FEATURED.includes(p.id)).sort((a, b) => a.label.localeCompare(b.label));
64187
+ return [...first, ...rest2];
64188
+ }
64189
+ function needsOnboarding(state) {
64190
+ return state.configured.length === 0;
64191
+ }
64192
+ function onboardingSummary(state) {
64193
+ if (!needsOnboarding(state))
64194
+ return "ready";
64195
+ const lines = [
64196
+ "setup required",
64197
+ " Add any common provider API key:",
64198
+ ...featuredApiKeyProviders().slice(0, 10).map((p) => ` /account add ${p.id} <api-key>`.padEnd(38) + `${p.label}`)
64199
+ ];
64200
+ const more = featuredApiKeyProviders().length - 10;
64201
+ if (more > 0)
64202
+ lines.push(` /onboard providers show ${more} more providers`);
64203
+ if (state.importable.length || state.cloudImportable.length)
64204
+ lines.push("", " /account import import detected env/cloud credentials");
64205
+ lines.push(" /account add azure <endpoint> <api-key> Azure OpenAI / Foundry");
64206
+ if (state.hasClaudeCli)
64207
+ lines.push(" /account add claude use Claude subscription CLI");
64208
+ if (state.hasCodexCli)
64209
+ lines.push(" /account add codex use ChatGPT subscription CLI");
64210
+ lines.push("", "Paste-detect works for known key prefixes: /account add <api-key>");
64211
+ return lines.join(`
64212
+ `);
64213
+ }
64214
+ var ALIASES, FEATURED;
64215
+ var init_onboarding = __esm(() => {
64216
+ init_catalog();
64217
+ ALIASES = {
64218
+ anthropic: "anthropic",
64219
+ claude: "anthropic",
64220
+ openai: "openai",
64221
+ gpt: "openai",
64222
+ google: "google",
64223
+ gemini: "google",
64224
+ deepseek: "deepseek",
64225
+ grok: "xai",
64226
+ xai: "xai",
64227
+ "x-ai": "xai",
64228
+ mistral: "mistral",
64229
+ groq: "groq",
64230
+ together: "together",
64231
+ fireworks: "fireworks",
64232
+ deepinfra: "deepinfra",
64233
+ cerebras: "cerebras",
64234
+ perplexity: "perplexity",
64235
+ baseten: "baseten",
64236
+ moonshot: "moonshot",
64237
+ kimi: "moonshot",
64238
+ zai: "zai",
64239
+ zhipu: "zai",
64240
+ nebius: "nebius",
64241
+ hyperbolic: "hyperbolic",
64242
+ sambanova: "sambanova",
64243
+ novita: "novita",
64244
+ openrouter: "openrouter",
64245
+ requesty: "requesty",
64246
+ portkey: "portkey",
64247
+ litellm: "litellm",
64248
+ "vercel-gateway": "vercel-gateway",
64249
+ vercel: "vercel-gateway",
64250
+ "ai-gateway": "vercel-gateway",
64251
+ azure: "azure",
64252
+ "azure-foundry": "azure-foundry",
64253
+ foundry: "azure-foundry"
64254
+ };
64255
+ FEATURED = [
64256
+ "anthropic",
64257
+ "openai",
64258
+ "google",
64259
+ "deepseek",
64260
+ "openrouter",
64261
+ "xai",
64262
+ "mistral",
64263
+ "groq",
64264
+ "together",
64265
+ "fireworks",
64266
+ "perplexity",
64267
+ "cerebras",
64268
+ "requesty"
64269
+ ];
64270
+ });
64271
+
64174
64272
  // src/agent/cli-backend.ts
64175
64273
  function newState() {
64176
64274
  return { text: "", usage: { inputTokens: 0, outputTokens: 0 }, rates: new Map, toolNames: new Map };
@@ -64423,9 +64521,10 @@ import { mkdirSync as mkdirSync7 } from "node:fs";
64423
64521
  import { join as join10 } from "node:path";
64424
64522
  import { homedir as homedir8 } from "node:os";
64425
64523
  async function addApiKeyAccount(provider, key, opts = {}) {
64524
+ provider = normalizeProviderId(provider);
64426
64525
  const cat = catalogProvider(provider);
64427
64526
  if (!cat)
64428
- return { ok: false, message: `unknown provider "${provider}" — see /accounts catalog` };
64527
+ return { ok: false, message: `unknown provider "${provider}" — use /onboard providers` };
64429
64528
  if (cat.group === "cli")
64430
64529
  return { ok: false, message: `${cat.label} is a subscription account — use /login ${provider} (P3), not a key` };
64431
64530
  if (cat.authKind !== "api-key" && cat.authKind !== "openai-compat") {
@@ -64638,6 +64737,7 @@ function shortId() {
64638
64737
  var init_onboard = __esm(() => {
64639
64738
  init_store();
64640
64739
  init_catalog();
64740
+ init_onboarding();
64641
64741
  init_resolve();
64642
64742
  init_cli_backend();
64643
64743
  init_proc();
@@ -118420,6 +118520,7 @@ var COMMANDS = [
118420
118520
  { name: "/context", usage: "/context", desc: "see what's loaded and how many tokens it uses", group: "chat" },
118421
118521
  { name: "/memory", usage: "/memory [note]", desc: "show or add facts to remember (or start a line with #)", group: "chat" },
118422
118522
  { name: "/account", usage: "/account", desc: "list accounts; /account <number> to switch, /account add to add one", group: "accounts" },
118523
+ { name: "/onboard", usage: "/onboard", desc: "first-run setup; provider list and import/add commands", group: "accounts" },
118423
118524
  { name: "/cost", usage: "/cost", desc: "see what you've spent per account", group: "accounts" },
118424
118525
  { name: "/copy", usage: "/copy", desc: "copy the last reply to the clipboard", group: "output" },
118425
118526
  { name: "/export", usage: "/export [file]", desc: "save the conversation to a file", group: "output" },
@@ -121187,9 +121288,6 @@ function pickDefaultModel(preferredId) {
121187
121288
  return wanted;
121188
121289
  return MODELS.find((m2) => providerAvailable(m2.provider));
121189
121290
  }
121190
- function anyProviderAvailable() {
121191
- return MODELS.some((m2) => providerAvailable(m2.provider));
121192
- }
121193
121291
 
121194
121292
  // src/model/selector.ts
121195
121293
  class FixedSelector {
@@ -133121,6 +133219,7 @@ init_store();
133121
133219
  init_detect();
133122
133220
  init_onboard();
133123
133221
  init_catalog();
133222
+ init_onboarding();
133124
133223
  init_cli_backend();
133125
133224
 
133126
133225
  // src/accounts/balance.ts
@@ -133233,72 +133332,6 @@ ${summary}` },
133233
133332
  return { messages, summarizedTurns: old.length, before: before2, after: after2 };
133234
133333
  }
133235
133334
 
133236
- // src/agent/mock.ts
133237
- var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
133238
- async function stream(text2, onEvent, chunk2 = 3, delay3 = 8) {
133239
- for (let i2 = 0;i2 < text2.length; i2 += chunk2) {
133240
- onEvent({ type: "text", text: text2.slice(i2, i2 + chunk2) });
133241
- await sleep(delay3);
133242
- }
133243
- }
133244
- async function streamWrite(id, path, content, onEvent, stop) {
133245
- onEvent({ type: "tool-start", id, name: "write_file", arg: "" });
133246
- onEvent({ type: "tool-stream", id, arg: path });
133247
- for (let i2 = 0;i2 < content.length; i2 += 8) {
133248
- if (stop())
133249
- return;
133250
- onEvent({ type: "tool-stream", id, delta: content.slice(i2, i2 + 8) });
133251
- await sleep(16);
133252
- }
133253
- const diff2 = content.replace(/\n$/, "").split(`
133254
- `).map((text2) => ({ sign: "+", text: text2 }));
133255
- onEvent({ type: "tool-end", id, ok: true, summary: `wrote ${path} (+${diff2.length} −0)`, diff: diff2 });
133256
- }
133257
- async function runTaskMock(opts) {
133258
- const { prompt, messages, onEvent, signal } = opts;
133259
- const stop = () => signal?.aborted === true;
133260
- await stream(`Sure — let me take a look around first.
133261
- `, onEvent);
133262
- if (stop())
133263
- return done(messages, onEvent);
133264
- onEvent({ type: "tool-start", id: "1", name: "list_dir", arg: "." });
133265
- await sleep(260);
133266
- if (stop())
133267
- return done(messages, onEvent);
133268
- onEvent({ type: "tool-end", id: "1", ok: true, summary: "src · package.json · README.md · 5 more" });
133269
- onEvent({ type: "tool-start", id: "2", name: "read_file", arg: "src/cli.tsx" });
133270
- await sleep(220);
133271
- if (stop())
133272
- return done(messages, onEvent);
133273
- onEvent({ type: "tool-end", id: "2", ok: true, summary: "renders the Ink app · 38 lines" });
133274
- await stream(`
133275
- Here's a quick file — watch it stream in:
133276
- `, onEvent);
133277
- if (stop())
133278
- return done(messages, onEvent);
133279
- const demoFile = `# hello.py — written live in demo mode
133280
-
133281
- def greet(name: str) -> str:
133282
- return f"Hello, {name}!"
133283
-
133284
- if __name__ == "__main__":
133285
- for who in ("world", "gearbox", "Boo"):
133286
- print(greet(who))
133287
- `;
133288
- await streamWrite("3", "hello.py", demoFile, onEvent, stop);
133289
- if (stop())
133290
- return done(messages, onEvent);
133291
- await stream(`
133292
- This is demo mode — no API key is set, so I'm not calling a real model. ` + `Set ANTHROPIC_API_KEY (or OPENAI / GOOGLE / DEEPSEEK) and I'll actually work on: "${prompt}".
133293
- `, onEvent);
133294
- return done(messages, onEvent);
133295
- }
133296
- function done(messages, onEvent) {
133297
- const usage = { inputTokens: 0, outputTokens: 0 };
133298
- onEvent({ type: "done", usage });
133299
- return { messages, usage };
133300
- }
133301
-
133302
133335
  // src/ui/clipboard.ts
133303
133336
  init_proc();
133304
133337
  function osc52(text2) {
@@ -133653,11 +133686,11 @@ function ActivityRail({ items, width }) {
133653
133686
  ]
133654
133687
  }, undefined, true, undefined, this);
133655
133688
  }
133656
- function App2({ selector: initialSelector, demo, runner, fullscreen = false, resumeId }) {
133689
+ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId }) {
133657
133690
  const { exit } = use_app_default();
133658
133691
  const { stdin, isRawModeSupported, setRawMode } = use_stdin_default();
133659
133692
  const { columns, rows } = useTerminalSize();
133660
- const online = useOnline(20000, !demo);
133693
+ const online = useOnline(20000, true);
133661
133694
  const width = columns;
133662
133695
  const splashSize = rows >= 24 && columns >= 42 ? "big" : rows >= 13 && columns >= 22 ? "mini" : "none";
133663
133696
  const [items, setItems] = import_react26.useState([]);
@@ -133736,10 +133769,6 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
133736
133769
  const t2 = setInterval(() => setElapsed(Math.floor((Date.now() - start) / 1000)), 500);
133737
133770
  return () => clearInterval(t2);
133738
133771
  }, [busy]);
133739
- import_react26.useEffect(() => {
133740
- if (firstRunRef.current)
133741
- updatePrefs({ onboarded: true });
133742
- }, []);
133743
133772
  import_react26.useEffect(() => {
133744
133773
  const proj = basename2(process.cwd());
133745
133774
  setTitle(busy ? `✳ ${proj} · working` : `${proj} · gearbox`);
@@ -133800,8 +133829,6 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
133800
133829
  setTimeout(pumpPerm, 0);
133801
133830
  };
133802
133831
  import_react26.useEffect(() => {
133803
- if (demo)
133804
- return;
133805
133832
  const acctId = loadPrefs().activeAccount;
133806
133833
  if (!acctId)
133807
133834
  return;
@@ -133813,7 +133840,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
133813
133840
  setActiveCliModelId(undefined);
133814
133841
  setActiveCli({ id: a.id, label: bin });
133815
133842
  }
133816
- }, [demo]);
133843
+ }, []);
133817
133844
  import_react26.useEffect(() => {
133818
133845
  setPermissionHandler((req) => new Promise((resolve11) => {
133819
133846
  if (modeRef.current === "auto-accept" && (req.kind === "write" || req.kind === "edit")) {
@@ -134055,8 +134082,6 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134055
134082
  };
134056
134083
  }, [stdin, fullscreen, rows, scrollBy, copyWithFeedback]);
134057
134084
  const persist = import_react26.useCallback(() => {
134058
- if (demo)
134059
- return;
134060
134085
  const s2 = sessionRef.current;
134061
134086
  if (!itemsRef.current.length)
134062
134087
  return;
@@ -134070,7 +134095,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134070
134095
  items: itemsRef.current,
134071
134096
  turns: s2.turns
134072
134097
  });
134073
- }, [demo]);
134098
+ }, []);
134074
134099
  const loadInto = (s2) => {
134075
134100
  idRef.current = s2.items.reduce((m2, i2) => Math.max(m2, i2.id), 0) + 1;
134076
134101
  setItems(s2.items);
@@ -134230,10 +134255,24 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134230
134255
  const [lastPick, setLastPick] = import_react26.useState(null);
134231
134256
  const [activeCli, setActiveCli] = import_react26.useState(null);
134232
134257
  const [activeCliModel, setActiveCliModel] = import_react26.useState(null);
134258
+ const onboardingState = {
134259
+ configured: listAccounts(),
134260
+ importable: importableEnvCreds(),
134261
+ cloudImportable: importableCloudCreds().map((c) => ({ provider: c.provider, label: c.label, source: c.source })),
134262
+ hasClaudeCli: Boolean(which("claude")),
134263
+ hasCodexCli: Boolean(which("codex"))
134264
+ };
134265
+ const setupRequired = needsOnboarding(onboardingState);
134266
+ import_react26.useEffect(() => {
134267
+ if (!setupRequired && firstRunRef.current) {
134268
+ firstRunRef.current = false;
134269
+ updatePrefs({ onboarded: true });
134270
+ }
134271
+ }, [setupRequired]);
134233
134272
  const model = lastPick?.model ?? choice?.model ?? null;
134234
- const modelLabel = demo ? "demo · no key" : activeCli ? `${activeCli.label}${activeCliModel ? ` · ${activeCliModel}` : ""}` : model?.label ?? "none";
134273
+ const modelLabel = setupRequired ? "setup required" : activeCli ? `${activeCli.label}${activeCliModel ? ` · ${activeCliModel}` : ""}` : model?.label ?? "none";
134235
134274
  const subscription = activeCli ? activeCli.label : null;
134236
- const routing = demo || activeCli ? null : lastPick?.reason ?? choice?.reason ?? null;
134275
+ const routing = setupRequired || activeCli ? null : lastPick?.reason ?? choice?.reason ?? null;
134237
134276
  const ctxPct = !activeCli && model && lastInput > 0 ? Math.round(lastInput / model.contextWindow * 100) : null;
134238
134277
  const push = (it) => setItems((prev) => [...prev, it]);
134239
134278
  const pushPhase = (label, detail) => {
@@ -134304,8 +134343,6 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134304
134343
  };
134305
134344
  };
134306
134345
  const defaultRunner = import_react26.useCallback(async ({ prompt, messages, onEvent, selector: sel, signal }) => {
134307
- if (demo)
134308
- return runTaskMock({ prompt, messages, onEvent, signal });
134309
134346
  const cli = activeCliRef.current;
134310
134347
  if (cli) {
134311
134348
  routedRef.current = null;
@@ -134338,10 +134375,8 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134338
134375
  const produced = r2.messages.slice(ctx.length);
134339
134376
  const ledger = sanitizeToolPairs([...messages, { role: "user", content: prompt }, ...produced]);
134340
134377
  return { messages: ledger, usage: r2.usage };
134341
- }, [demo]);
134378
+ }, []);
134342
134379
  const compactNow = import_react26.useCallback(async (keepRecent, signal) => {
134343
- if (demo)
134344
- return "compaction needs a model (no key in demo)";
134345
134380
  let model2;
134346
134381
  try {
134347
134382
  model2 = selectorRef.current.select({ prompt: "", kind: "summarize" }).model;
@@ -134355,7 +134390,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134355
134390
  const saved = res.before - res.after;
134356
134391
  const savedStr = saved >= 1000 ? `${(saved / 1000).toFixed(1)}k` : String(Math.max(0, saved));
134357
134392
  return `compacted ${res.summarizedTurns} earlier turn${res.summarizedTurns > 1 ? "s" : ""} · ~${savedStr} tokens freed`;
134358
- }, [demo]);
134393
+ }, []);
134359
134394
  const MODE_NOTE = {
134360
134395
  normal: "normal mode — I'll ask before writes, edits, and shell",
134361
134396
  "auto-accept": "auto-accept edits — file writes/edits apply without asking (shell still gated)",
@@ -134603,7 +134638,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134603
134638
  recordUsage({ accountId: acctId, inputTokens: r2.usage.inputTokens, outputTokens: r2.usage.outputTokens, costUSD: cost, estimated: cm?.costUSD == null });
134604
134639
  if (cm?.rates?.length)
134605
134640
  recordRateLimits(acctId, cm.rates);
134606
- if (!demo && !ac.signal.aborted) {
134641
+ if (!ac.signal.aborted) {
134607
134642
  try {
134608
134643
  const cm2 = selectorRef.current.select({ prompt: "", kind: "summarize" }).model;
134609
134644
  const budget = Math.max(8000, cm2.contextWindow - 32000);
@@ -134981,13 +135016,26 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
134981
135016
  }
134982
135017
  })();
134983
135018
  if (!m2) {
134984
- notice("no model available — set a key to see the working set");
135019
+ notice(`no model available — add a provider first
135020
+
135021
+ ` + onboardingSummary(onboardingState));
134985
135022
  return;
134986
135023
  }
134987
135024
  const { sections } = buildContext({ history: msgRef.current, userText: lastPromptRef.current || "(your next message)", model: m2, plan: modeRef.current === "plan" });
134988
135025
  push({ kind: "context", id: idRef.current++, view: buildContextView(sections, m2.contextWindow, process.cwd()) });
134989
135026
  return;
134990
135027
  }
135028
+ case "onboard": {
135029
+ echo(text2);
135030
+ if (arg.trim().toLowerCase() === "providers") {
135031
+ const rows2 = featuredApiKeyProviders().map((p) => ` ${p.id.padEnd(18)} ${p.label.padEnd(24)} ${p.envVars[0] ?? ""}`.trimEnd());
135032
+ notice(["providers you can add with /account add <provider> <api-key>", ...rows2, "", "Aliases work for common names, e.g. gemini -> google, grok -> xai, kimi -> moonshot."].join(`
135033
+ `));
135034
+ } else {
135035
+ notice(onboardingSummary(onboardingState));
135036
+ }
135037
+ return;
135038
+ }
134991
135039
  case "accounts":
134992
135040
  case "account": {
134993
135041
  echo(text2);
@@ -135255,10 +135303,6 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135255
135303
  notice("busy — try /compact once the current turn finishes");
135256
135304
  return;
135257
135305
  }
135258
- if (demo) {
135259
- notice("compaction needs a model (no key in demo)");
135260
- return;
135261
- }
135262
135306
  setBusy(true);
135263
135307
  setVerb("Compacting context");
135264
135308
  setMascotState("thinking");
@@ -135296,7 +135340,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135296
135340
  notice(`/${name31} hit an error: ${(e2?.message ?? String(e2)).split(`
135297
135341
  `)[0]}`);
135298
135342
  }
135299
- }, [exit, runTurn]);
135343
+ }, [exit, runTurn, onboardingState]);
135300
135344
  const submit = import_react26.useCallback((value) => {
135301
135345
  let text2 = value.trim();
135302
135346
  if (pasteStoreRef.current.size) {
@@ -135350,6 +135394,13 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135350
135394
  handleCommand(text2);
135351
135395
  return;
135352
135396
  }
135397
+ if (setupRequired) {
135398
+ echo(text2);
135399
+ notice(`set up a provider before sending a task
135400
+
135401
+ ` + onboardingSummary(onboardingState));
135402
+ return;
135403
+ }
135353
135404
  if (busyRef.current) {
135354
135405
  queueRef.current.push(text2);
135355
135406
  setQueued([...queueRef.current]);
@@ -135357,7 +135408,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135357
135408
  return;
135358
135409
  }
135359
135410
  runTurn(text2);
135360
- }, [handleCommand, runTurn]);
135411
+ }, [handleCommand, runTurn, setupRequired, onboardingState]);
135361
135412
  import_react26.useEffect(() => {
135362
135413
  if (busy || queueRef.current.length === 0)
135363
135414
  return;
@@ -135681,78 +135732,169 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135681
135732
  skin: ghostSkin,
135682
135733
  size: splashSize
135683
135734
  }, undefined, false, undefined, this),
135684
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135735
+ setupRequired ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135685
135736
  marginTop: 1,
135737
+ flexDirection: "column",
135738
+ alignItems: "center",
135686
135739
  children: [
135687
135740
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135688
- color: color.dim,
135689
- children: "talk or type "
135741
+ color: color.text,
135742
+ children: "setup required"
135690
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,
135691
135754
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135692
135755
  color: color.faint,
135693
135756
  children: [
135694
- glyph.bullet,
135695
- " "
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)
135696
135762
  ]
135697
135763
  }, undefined, true, undefined, this),
135698
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135699
- color: color.accentDim,
135700
- children: "/"
135701
- }, undefined, false, undefined, this),
135702
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135703
- color: color.dim,
135704
- children: "commands "
135705
- }, undefined, false, undefined, this),
135706
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135707
- color: color.accentDim,
135708
- children: "@"
135709
- }, undefined, false, undefined, this),
135710
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135711
- color: color.dim,
135712
- children: "files "
135713
- }, undefined, false, undefined, this),
135714
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135715
- color: color.accentDim,
135716
- children: "!"
135717
- }, undefined, false, undefined, this),
135718
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135719
- color: color.dim,
135720
- children: "shell"
135721
- }, undefined, false, undefined, this)
135722
- ]
135723
- }, undefined, true, undefined, this),
135724
- firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
135725
- marginTop: 1,
135726
- flexDirection: "column",
135727
- alignItems: "center",
135728
- children: [
135729
135764
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135730
135765
  color: color.faint,
135731
135766
  children: [
135732
- "new here? press ",
135767
+ "paste-detect: ",
135733
135768
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135734
135769
  color: color.accent,
135735
- children: "?"
135770
+ children: "/account add <api-key>"
135736
135771
  }, undefined, false, undefined, this),
135737
- " for shortcuts · ",
135772
+ " · list: ",
135738
135773
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135739
135774
  color: color.accent,
135740
- children: "shift+tab"
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 "
135741
135830
  }, undefined, false, undefined, this),
135742
- " cycles modes · ",
135743
135831
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135744
- color: color.accent,
135745
- children: "⌃Y"
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 "
135746
135853
  }, undefined, false, undefined, this),
135747
- " copies the last reply"
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)
135748
135862
  ]
135749
135863
  }, undefined, true, undefined, this),
135750
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
135751
- color: color.faint,
135752
- children: demo ? "set ANTHROPIC_API_KEY (or another provider) to start — running in demo mode" : "/config inline on for terminal scrollback · /keys for shortcuts"
135753
- }, undefined, false, 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
135754
135896
  ]
135755
- }, undefined, true, undefined, this) : null
135897
+ }, undefined, true, undefined, this)
135756
135898
  ]
135757
135899
  }, undefined, true, undefined, this);
135758
135900
  const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
@@ -135780,7 +135922,7 @@ function App2({ selector: initialSelector, demo, runner, fullscreen = false, res
135780
135922
  value: edit2.value,
135781
135923
  cursor: edit2.cursor,
135782
135924
  selectionAnchor: edit2.selectionAnchor,
135783
- placeholder: mode2 === "plan" ? "describe what to plan…" : "ask anything",
135925
+ placeholder: setupRequired ? "add a provider with /account add <provider> <api-key>" : mode2 === "plan" ? "describe what to plan…" : "ask anything",
135784
135926
  suggestion,
135785
135927
  busy,
135786
135928
  width,
@@ -135996,8 +136138,10 @@ Options:
135996
136138
  -v, --version print version
135997
136139
  -h, --help this help
135998
136140
 
135999
- Set at least one provider key first (each user uses their own):
136000
- ANTHROPIC_API_KEY · OPENAI_API_KEY · GOOGLE_GENERATIVE_AI_API_KEY · DEEPSEEK_API_KEY
136141
+ Set up at least one provider first:
136142
+ gearbox auth add <api-key>
136143
+ gearbox auth add <provider> <api-key>
136144
+ gearbox auth import
136001
136145
 
136002
136146
  Models: ${MODELS.map((m2) => m2.label).join(", ")}
136003
136147
  In-app: / for commands, @ for files, !cmd for shell, shift+tab for plan mode.`);
@@ -136056,7 +136200,6 @@ Importable from your env (gearbox auth import): ${imp.map((c) => c.envVar).join(
136056
136200
  }
136057
136201
  var mi = args.indexOf("--model");
136058
136202
  var preferred = mi >= 0 ? args[mi + 1] : undefined;
136059
- var demo = !anyProviderAvailable();
136060
136203
  var pinned = preferred ?? loadPrefs().pinnedModel;
136061
136204
  var selector = pinned ? new FixedSelector(pinned) : new RoutingSelector;
136062
136205
  if (args.includes("--yolo"))
@@ -136090,7 +136233,6 @@ if (mouse)
136090
136233
  process.stdout.write("\x1B[?1000h\x1B[?1002h\x1B[?1006h");
136091
136234
  var app = render_default(/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(App2, {
136092
136235
  selector,
136093
- demo,
136094
136236
  fullscreen,
136095
136237
  resumeId
136096
136238
  }, undefined, false, undefined, this), { exitOnCtrlC: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gearbox-code",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",