gearbox-code 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.mjs +482 -288
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -138748,7 +138748,7 @@ var COMMANDS = [
138748
138748
  { name: "/retry", usage: "/retry", desc: "send your last message again", group: "chat" },
138749
138749
  { name: "/compact", usage: "/compact", desc: "shrink the conversation to free up room", group: "chat" },
138750
138750
  { name: "/context", usage: "/context", desc: "see what's loaded and how many tokens it uses", group: "chat" },
138751
- { name: "/ask", usage: "/ask <q>", desc: "ask about Gearbox itself answered from its own docs", group: "chat" },
138751
+ { name: "/ask", usage: "/ask <q>", desc: "ask about Gearbox itself · answered from its own docs", group: "chat" },
138752
138752
  { name: "/memory", usage: "/memory [note]", desc: "show or add facts to remember (or start a line with #)", group: "chat" },
138753
138753
  { name: "/account", usage: "/account", desc: "list accounts; /account <name> switches, /account login <name> re-auths, /account add adds one", group: "accounts" },
138754
138754
  { name: "/onboard", usage: "/onboard", desc: "first-run setup; provider list and import/add commands", group: "accounts" },
@@ -138853,10 +138853,10 @@ function formatAccounts(accounts, activeCliId, importable, statuses = {}) {
138853
138853
  lines.push(` ${st.detail}`);
138854
138854
  });
138855
138855
  if (!activeCliId)
138856
- lines.push("", " no subscription active your API keys auto-route per task");
138856
+ lines.push("", " no subscription active · your API keys auto-route per task");
138857
138857
  }
138858
138858
  if (importable.length) {
138859
- lines.push("", "found in your environment /account import to add:");
138859
+ lines.push("", "found in your environment · /account import to add:");
138860
138860
  for (const c of importable)
138861
138861
  lines.push(` + ${c.label} (${c.envVar})`);
138862
138862
  }
@@ -138936,16 +138936,16 @@ function formatModelList(currentId, showAll = false) {
138936
138936
  rows.push(line(m2));
138937
138937
  }
138938
138938
  if (hidden)
138939
- rows.push(` + ${hidden} more on your accounts /model all to list · /model <name> to pick`);
138939
+ rows.push(` + ${hidden} more on your accounts · /model all to list · /model <name> to pick`);
138940
138940
  } else {
138941
- rows.push("", "no accounts yet /account to add one");
138941
+ rows.push("", "no accounts yet · /account to add one");
138942
138942
  }
138943
138943
  if (showAll && rest2.length) {
138944
138944
  rows.push("", "needs an account");
138945
138945
  for (const m2 of rest2)
138946
138946
  rows.push(` ${glyph.off} ${m2.label.padEnd(18)} ${m2.provider}`);
138947
138947
  } else if (rest2.length) {
138948
- rows.push("", ` + ${rest2.length} more once you add a key /model all to list · /account to add one`);
138948
+ rows.push("", ` + ${rest2.length} more once you add a key · /model all to list · /account to add one`);
138949
138949
  }
138950
138950
  return rows.join(`
138951
138951
  `);
@@ -138957,20 +138957,20 @@ function resolveModelSwitch(query) {
138957
138957
  const MODELS2 = modelRegistry();
138958
138958
  const matches2 = MODELS2.filter((m3) => m3.label.toLowerCase().includes(q) || m3.id.toLowerCase().includes(q));
138959
138959
  if (matches2.length === 0)
138960
- return { ok: false, message: `no model matching “${query}” /model to list` };
138960
+ return { ok: false, message: `no model matching “${query}” · /model to list` };
138961
138961
  const exact = matches2.find((m3) => m3.label.toLowerCase() === q || m3.id.toLowerCase() === q);
138962
138962
  const available = matches2.filter((m3) => providerAvailable(m3.provider));
138963
138963
  if (exact) {
138964
138964
  if (!providerAvailable(exact.provider))
138965
- return { ok: false, message: `${exact.label}: no ${exact.provider} account yet /account add ${exact.provider} <key> or set ${envHint(exact.provider)}` };
138965
+ return { ok: false, message: `${exact.label}: no ${exact.provider} account yet · /account add ${exact.provider} <key> or set ${envHint(exact.provider)}` };
138966
138966
  return { ok: true, modelId: exact.id, message: `model → ${exact.label}` };
138967
138967
  }
138968
138968
  if (available.length === 0) {
138969
138969
  const m3 = matches2[0];
138970
- return { ok: false, message: `“${query}” matches ${m3.label} but no account for ${m3.provider} /accounts add ${m3.provider} <key> or set ${envHint(m3.provider)}` };
138970
+ return { ok: false, message: `“${query}” matches ${m3.label} but no account for ${m3.provider} · /accounts add ${m3.provider} <key> or set ${envHint(m3.provider)}` };
138971
138971
  }
138972
138972
  if (available.length > 1) {
138973
- return { ok: false, message: `“${query}” matches ${available.map((m3) => m3.label).join(", ")} be more specific` };
138973
+ return { ok: false, message: `“${query}” matches ${available.map((m3) => m3.label).join(", ")} · be more specific` };
138974
138974
  }
138975
138975
  const m2 = available[0];
138976
138976
  return { ok: true, modelId: m2.id, message: `model → ${m2.label}` };
@@ -140309,8 +140309,139 @@ function StatusBar({
140309
140309
  }, undefined, true, undefined, this);
140310
140310
  }
140311
140311
 
140312
- // src/ui/components/CommandPalette.tsx
140312
+ // src/ui/components/StatusStrip.tsx
140313
140313
  var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
140314
+ function bar(leftPct, cells = 12) {
140315
+ const filled = Math.max(0, Math.min(cells, Math.round(leftPct / 100 * cells)));
140316
+ return "█".repeat(filled) + "░".repeat(cells - filled);
140317
+ }
140318
+ function fmtTok2(n) {
140319
+ return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
140320
+ }
140321
+ function StatusStrip({
140322
+ ctxPct,
140323
+ tokens,
140324
+ contextWindow,
140325
+ cost,
140326
+ sub,
140327
+ api: api2,
140328
+ width
140329
+ }) {
140330
+ const Row2 = ({ label, children }) => /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140331
+ children: [
140332
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140333
+ color: color.faint,
140334
+ children: [
140335
+ label.padEnd(8),
140336
+ " "
140337
+ ]
140338
+ }, undefined, true, undefined, this),
140339
+ children
140340
+ ]
140341
+ }, undefined, true, undefined, this);
140342
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
140343
+ width,
140344
+ flexDirection: "column",
140345
+ paddingX: 1,
140346
+ marginTop: 1,
140347
+ children: [
140348
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
140349
+ justifyContent: "space-between",
140350
+ children: [
140351
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140352
+ color: color.accent,
140353
+ bold: true,
140354
+ children: "usage"
140355
+ }, undefined, false, undefined, this),
140356
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140357
+ color: color.faint,
140358
+ children: "/cost to hide"
140359
+ }, undefined, false, undefined, this)
140360
+ ]
140361
+ }, undefined, true, undefined, this),
140362
+ ctxPct != null ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Row2, {
140363
+ label: "context",
140364
+ children: [
140365
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140366
+ color: color.text,
140367
+ children: [
140368
+ 100 - ctxPct,
140369
+ "% left"
140370
+ ]
140371
+ }, undefined, true, undefined, this),
140372
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140373
+ color: color.faint,
140374
+ children: [
140375
+ " · ",
140376
+ fmtTok2(tokens),
140377
+ contextWindow ? ` / ${fmtTok2(contextWindow)}` : ""
140378
+ ]
140379
+ }, undefined, true, undefined, this)
140380
+ ]
140381
+ }, undefined, true, undefined, this) : null,
140382
+ sub?.limits?.map((l) => /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Row2, {
140383
+ label: l.label,
140384
+ children: [
140385
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140386
+ color: l.pct >= 90 ? color.err : color.accentDim,
140387
+ children: bar(100 - l.pct)
140388
+ }, undefined, false, undefined, this),
140389
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140390
+ color: l.pct >= 90 ? color.err : color.text,
140391
+ children: [
140392
+ " ",
140393
+ 100 - l.pct,
140394
+ "% left"
140395
+ ]
140396
+ }, undefined, true, undefined, this),
140397
+ l.resetsIn ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140398
+ color: color.faint,
140399
+ children: [
140400
+ " · ",
140401
+ l.resetsIn
140402
+ ]
140403
+ }, undefined, true, undefined, this) : null
140404
+ ]
140405
+ }, l.label, true, undefined, this)),
140406
+ sub && !sub.limits?.length ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Row2, {
140407
+ label: "limits",
140408
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140409
+ color: color.faint,
140410
+ children: sub.limitNote ?? "not reported yet"
140411
+ }, undefined, false, undefined, this)
140412
+ }, undefined, false, undefined, this) : null,
140413
+ api2?.spend ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Row2, {
140414
+ label: api2.name.slice(0, 8),
140415
+ children: [
140416
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140417
+ color: api2.spendPos ? color.ok : color.faint,
140418
+ children: api2.spend
140419
+ }, undefined, false, undefined, this),
140420
+ api2.balanceLeft ? /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140421
+ color: color.faint,
140422
+ children: [
140423
+ " · ",
140424
+ api2.balanceLeft
140425
+ ]
140426
+ }, undefined, true, undefined, this) : null
140427
+ ]
140428
+ }, undefined, true, undefined, this) : null,
140429
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Row2, {
140430
+ label: "session",
140431
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140432
+ color: cost >= 0.005 ? color.text : color.faint,
140433
+ children: [
140434
+ "$",
140435
+ cost.toFixed(2)
140436
+ ]
140437
+ }, undefined, true, undefined, this)
140438
+ }, undefined, false, undefined, this)
140439
+ ]
140440
+ }, undefined, true, undefined, this);
140441
+ }
140442
+
140443
+ // src/ui/components/CommandPalette.tsx
140444
+ var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
140314
140445
  function windowed(items, selected, limit) {
140315
140446
  const count = Math.max(1, limit);
140316
140447
  const safeSelected = Math.max(0, Math.min(selected, Math.max(items.length - 1, 0)));
@@ -140328,13 +140459,13 @@ function CommandPalette({ draft, selected = 0, limit = 5, rows, width = 80 }) {
140328
140459
  const rowWidth = Math.max(20, width - 2);
140329
140460
  if (rows?.length) {
140330
140461
  const shown2 = windowed(rows, selected, limit);
140331
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
140462
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
140332
140463
  flexDirection: "column",
140333
140464
  paddingX: 1,
140334
140465
  marginTop: 1,
140335
140466
  children: shown2.rows.map((r2, i2) => {
140336
140467
  const active = shown2.start + i2 === selected;
140337
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140468
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
140338
140469
  color: active ? color.text : color.dim,
140339
140470
  bold: active,
140340
140471
  backgroundColor: active ? color.accentBg : undefined,
@@ -140347,13 +140478,13 @@ function CommandPalette({ draft, selected = 0, limit = 5, rows, width = 80 }) {
140347
140478
  if (matches2.length === 0)
140348
140479
  return null;
140349
140480
  const shown = windowed(matches2, selected, limit);
140350
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
140481
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
140351
140482
  flexDirection: "column",
140352
140483
  paddingX: 1,
140353
140484
  marginTop: 1,
140354
140485
  children: shown.rows.map((c, i2) => {
140355
140486
  const active = shown.start + i2 === selected;
140356
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
140487
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
140357
140488
  color: active ? color.text : color.dim,
140358
140489
  bold: active,
140359
140490
  backgroundColor: active ? color.accentBg : undefined,
@@ -140364,7 +140495,7 @@ function CommandPalette({ draft, selected = 0, limit = 5, rows, width = 80 }) {
140364
140495
  }
140365
140496
 
140366
140497
  // src/ui/components/FilePalette.tsx
140367
- var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
140498
+ var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
140368
140499
  function windowed2(items, selected, limit) {
140369
140500
  const count = Math.max(1, limit);
140370
140501
  const safeSelected = Math.max(0, Math.min(selected, Math.max(items.length - 1, 0)));
@@ -140377,12 +140508,12 @@ function FilePalette({ matches: matches2, selected = 0, limit = 5, width = 80 })
140377
140508
  const rowWidth = Math.max(20, width - 2);
140378
140509
  if (shown.rows.length === 0)
140379
140510
  return null;
140380
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
140511
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140381
140512
  flexDirection: "column",
140382
140513
  paddingX: 1,
140383
140514
  marginTop: 1,
140384
140515
  children: [
140385
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
140516
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140386
140517
  color: color.faint,
140387
140518
  children: "@ files · tab to complete"
140388
140519
  }, undefined, false, undefined, this),
@@ -140390,7 +140521,7 @@ function FilePalette({ matches: matches2, selected = 0, limit = 5, width = 80 })
140390
140521
  const active = shown.start + i2 === selected;
140391
140522
  const raw = `${active ? `${glyph.on} ` : " "}${f3}`;
140392
140523
  const text = raw.length > rowWidth ? raw.slice(0, Math.max(0, rowWidth - 1)) + "…" : raw.padEnd(rowWidth);
140393
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
140524
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140394
140525
  color: active ? color.text : color.faint,
140395
140526
  bold: active,
140396
140527
  backgroundColor: active ? color.accentBg : undefined,
@@ -140669,7 +140800,7 @@ function applyMouse(s2, click) {
140669
140800
  }
140670
140801
 
140671
140802
  // src/ui/components/Composer.tsx
140672
- var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
140803
+ var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
140673
140804
  function Composer({
140674
140805
  value,
140675
140806
  cursor,
@@ -140701,15 +140832,15 @@ function Composer({
140701
140832
  const cursorHere = line === curLine;
140702
140833
  if (!hasSel) {
140703
140834
  if (!cursorHere)
140704
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140835
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140705
140836
  backgroundColor: color.panelBg,
140706
140837
  children: ln
140707
140838
  }, undefined, false, undefined, this);
140708
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140839
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140709
140840
  backgroundColor: color.panelBg,
140710
140841
  children: [
140711
140842
  ln.slice(0, curCol),
140712
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140843
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140713
140844
  inverse: true,
140714
140845
  backgroundColor: color.panelBg,
140715
140846
  children: ln[curCol] ?? " "
@@ -140718,11 +140849,11 @@ function Composer({
140718
140849
  ]
140719
140850
  }, undefined, true, undefined, this);
140720
140851
  }
140721
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140852
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140722
140853
  backgroundColor: color.panelBg,
140723
140854
  children: [
140724
140855
  ln.slice(0, selStart),
140725
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140856
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140726
140857
  inverse: true,
140727
140858
  children: ln.slice(selStart, selEnd)
140728
140859
  }, undefined, false, undefined, this),
@@ -140730,34 +140861,34 @@ function Composer({
140730
140861
  ]
140731
140862
  }, undefined, true, undefined, this);
140732
140863
  };
140733
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140864
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140734
140865
  flexDirection: "column",
140735
140866
  width,
140736
140867
  marginTop: 1,
140737
140868
  children: [
140738
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140869
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140739
140870
  paddingX: 1,
140740
- children: vim !== "off" ? /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
140871
+ children: vim !== "off" ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(jsx_dev_runtime8.Fragment, {
140741
140872
  children: [
140742
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140873
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140743
140874
  color: vim === "normal" ? color.accent : color.dim,
140744
140875
  bold: true,
140745
140876
  children: vim === "normal" ? " NORMAL " : " INSERT "
140746
140877
  }, undefined, false, undefined, this),
140747
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140878
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140748
140879
  color: color.faint,
140749
140880
  children: glyph.rule.repeat(Math.max(width - 11, 4))
140750
140881
  }, undefined, false, undefined, this)
140751
140882
  ]
140752
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140883
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140753
140884
  color: color.faint,
140754
140885
  children: glyph.rule.repeat(Math.max(width - 2, 8))
140755
140886
  }, undefined, false, undefined, this)
140756
140887
  }, undefined, false, undefined, this),
140757
- busy ? /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140888
+ busy ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140758
140889
  paddingX: 1,
140759
140890
  children: [
140760
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140891
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140761
140892
  color: color.faint,
140762
140893
  bold: true,
140763
140894
  backgroundColor: color.panelBg,
@@ -140766,16 +140897,16 @@ function Composer({
140766
140897
  " "
140767
140898
  ]
140768
140899
  }, undefined, true, undefined, this),
140769
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140900
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140770
140901
  color: color.faint,
140771
140902
  backgroundColor: color.panelBg,
140772
140903
  children: value || "…"
140773
140904
  }, undefined, false, undefined, this)
140774
140905
  ]
140775
- }, undefined, true, undefined, this) : value === "" ? /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140906
+ }, undefined, true, undefined, this) : value === "" ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140776
140907
  paddingX: 1,
140777
140908
  children: [
140778
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140909
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140779
140910
  color: color.accent,
140780
140911
  bold: true,
140781
140912
  backgroundColor: color.panelBg,
@@ -140784,23 +140915,23 @@ function Composer({
140784
140915
  " "
140785
140916
  ]
140786
140917
  }, undefined, true, undefined, this),
140787
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140918
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140788
140919
  inverse: true,
140789
140920
  backgroundColor: color.panelBg,
140790
140921
  children: " "
140791
140922
  }, undefined, false, undefined, this),
140792
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140923
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140793
140924
  color: color.faint,
140794
140925
  backgroundColor: color.panelBg,
140795
140926
  children: suggestion ?? placeholder
140796
140927
  }, undefined, false, undefined, this)
140797
140928
  ]
140798
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140929
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140799
140930
  flexDirection: "column",
140800
140931
  paddingX: 1,
140801
- children: lines.map((ln, i2) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
140932
+ children: lines.map((ln, i2) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
140802
140933
  children: [
140803
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
140934
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
140804
140935
  color: color.accent,
140805
140936
  bold: true,
140806
140937
  backgroundColor: color.panelBg,
@@ -141686,7 +141817,7 @@ function renderGhost(cfg) {
141686
141817
  }
141687
141818
 
141688
141819
  // src/ui/components/Mascot.tsx
141689
- var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
141820
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
141690
141821
  var SKINS = ["base", "mint", "pink", "golden", "shades"];
141691
141822
  var SKIN_CFG = {
141692
141823
  base: { palette: "default", face: "happy" },
@@ -141708,35 +141839,35 @@ function SpriteRow({ row }) {
141708
141839
  j++;
141709
141840
  const n = j - i2;
141710
141841
  if (t2 && b)
141711
- spans.push(/* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141842
+ spans.push(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141712
141843
  color: t2,
141713
141844
  backgroundColor: b,
141714
141845
  children: "▀".repeat(n)
141715
141846
  }, i2, false, undefined, this));
141716
141847
  else if (t2)
141717
- spans.push(/* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141848
+ spans.push(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141718
141849
  color: t2,
141719
141850
  children: "▀".repeat(n)
141720
141851
  }, i2, false, undefined, this));
141721
141852
  else if (b)
141722
- spans.push(/* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141853
+ spans.push(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141723
141854
  color: b,
141724
141855
  children: "▄".repeat(n)
141725
141856
  }, i2, false, undefined, this));
141726
141857
  else
141727
- spans.push(/* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141858
+ spans.push(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141728
141859
  children: " ".repeat(n)
141729
141860
  }, i2, false, undefined, this));
141730
141861
  i2 = j;
141731
141862
  }
141732
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141863
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141733
141864
  children: spans
141734
141865
  }, undefined, false, undefined, this);
141735
141866
  }
141736
141867
  function Sprite({ data }) {
141737
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141868
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141738
141869
  flexDirection: "column",
141739
- children: data.map((row, i2) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(SpriteRow, {
141870
+ children: data.map((row, i2) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(SpriteRow, {
141740
141871
  row
141741
141872
  }, i2, false, undefined, this))
141742
141873
  }, undefined, false, undefined, this);
@@ -141764,9 +141895,9 @@ function AnimatedGhost({ cfg, scale, anim }) {
141764
141895
  frameCfg.overlay = { kind: anim.overlay, frame: slow };
141765
141896
  const data = import_react23.useMemo(() => renderGhost(frameCfg), [JSON.stringify(frameCfg)]);
141766
141897
  const shake = anim.shake ? tick % 2 : 0;
141767
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141898
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141768
141899
  marginLeft: shake,
141769
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Sprite, {
141900
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Sprite, {
141770
141901
  data
141771
141902
  }, undefined, false, undefined, this)
141772
141903
  }, undefined, false, undefined, this);
@@ -141775,9 +141906,9 @@ function KittyGhost({ variant, size: size2 }) {
141775
141906
  const data = GHOSTS[variant][size2];
141776
141907
  const id = idColor(imageId(variant, size2));
141777
141908
  const lines = placeholderRows(data[0]?.length ?? 0, data.length);
141778
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141909
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141779
141910
  flexDirection: "column",
141780
- children: lines.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141911
+ children: lines.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141781
141912
  color: id,
141782
141913
  children: l
141783
141914
  }, i2, false, undefined, this))
@@ -141785,29 +141916,29 @@ function KittyGhost({ variant, size: size2 }) {
141785
141916
  }
141786
141917
  function MascotSplash({ skin = "base", size: size2 = "big" }) {
141787
141918
  const kitty = getImageMode() === "kitty";
141788
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141919
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141789
141920
  flexDirection: "column",
141790
141921
  alignItems: "center",
141791
141922
  marginTop: 1,
141792
141923
  children: [
141793
- size2 !== "none" ? kitty ? /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(KittyGhost, {
141924
+ size2 !== "none" ? kitty ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(KittyGhost, {
141794
141925
  variant: skin,
141795
141926
  size: size2
141796
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(AnimatedGhost, {
141927
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(AnimatedGhost, {
141797
141928
  cfg: skinToCfg(skin),
141798
141929
  scale: size2 === "big" ? 2 : 1,
141799
141930
  anim: { blink: true }
141800
141931
  }, undefined, false, undefined, this) : null,
141801
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141932
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141802
141933
  marginTop: size2 === "none" ? 0 : 1,
141803
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141934
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141804
141935
  color: color.accent,
141805
141936
  bold: true,
141806
141937
  children: "gearbox"
141807
141938
  }, undefined, false, undefined, this)
141808
141939
  }, undefined, false, undefined, this),
141809
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
141810
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
141940
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141941
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141811
141942
  color: color.dim,
141812
141943
  children: "one ghost · every model"
141813
141944
  }, undefined, false, undefined, this)
@@ -141817,9 +141948,9 @@ function MascotSplash({ skin = "base", size: size2 = "big" }) {
141817
141948
  }
141818
141949
 
141819
141950
  // src/ui/components/PermissionPrompt.tsx
141820
- var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
141951
+ var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
141821
141952
  function PermissionPrompt({ req, width }) {
141822
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141953
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
141823
141954
  flexDirection: "column",
141824
141955
  width,
141825
141956
  marginTop: 1,
@@ -141827,9 +141958,9 @@ function PermissionPrompt({ req, width }) {
141827
141958
  borderStyle: "round",
141828
141959
  borderColor: color.accent,
141829
141960
  children: [
141830
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141961
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
141831
141962
  children: [
141832
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141963
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141833
141964
  color: color.accent,
141834
141965
  bold: true,
141835
141966
  children: [
@@ -141837,7 +141968,7 @@ function PermissionPrompt({ req, width }) {
141837
141968
  " permission"
141838
141969
  ]
141839
141970
  }, undefined, true, undefined, this),
141840
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141971
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141841
141972
  color: color.faint,
141842
141973
  children: [
141843
141974
  " ",
@@ -141846,39 +141977,39 @@ function PermissionPrompt({ req, width }) {
141846
141977
  }, undefined, true, undefined, this)
141847
141978
  ]
141848
141979
  }, undefined, true, undefined, this),
141849
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141980
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
141850
141981
  marginTop: 1,
141851
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141982
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
141852
141983
  flexGrow: 1,
141853
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141984
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141854
141985
  color: color.text,
141855
141986
  wrap: "truncate-end",
141856
141987
  children: req.detail
141857
141988
  }, undefined, false, undefined, this)
141858
141989
  }, undefined, false, undefined, this)
141859
141990
  }, undefined, false, undefined, this),
141860
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
141991
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
141861
141992
  marginTop: 1,
141862
141993
  children: [
141863
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141994
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141864
141995
  color: color.accent,
141865
141996
  bold: true,
141866
141997
  children: "1"
141867
141998
  }, undefined, false, undefined, this),
141868
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
141999
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141869
142000
  color: color.dim,
141870
142001
  children: " once"
141871
142002
  }, undefined, false, undefined, this),
141872
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142003
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141873
142004
  color: color.faint,
141874
142005
  children: " "
141875
142006
  }, undefined, false, undefined, this),
141876
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142007
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141877
142008
  color: color.accent,
141878
142009
  bold: true,
141879
142010
  children: "2"
141880
142011
  }, undefined, false, undefined, this),
141881
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142012
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141882
142013
  color: color.dim,
141883
142014
  children: [
141884
142015
  " always (",
@@ -141886,33 +142017,33 @@ function PermissionPrompt({ req, width }) {
141886
142017
  ")"
141887
142018
  ]
141888
142019
  }, undefined, true, undefined, this),
141889
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142020
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141890
142021
  color: color.faint,
141891
142022
  children: " "
141892
142023
  }, undefined, false, undefined, this),
141893
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142024
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141894
142025
  color: color.accent,
141895
142026
  bold: true,
141896
142027
  children: "a"
141897
142028
  }, undefined, false, undefined, this),
141898
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142029
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141899
142030
  color: color.dim,
141900
142031
  children: " all · yolo"
141901
142032
  }, undefined, false, undefined, this),
141902
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142033
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141903
142034
  color: color.faint,
141904
142035
  children: " "
141905
142036
  }, undefined, false, undefined, this),
141906
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142037
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141907
142038
  color: color.err,
141908
142039
  bold: true,
141909
142040
  children: "3"
141910
142041
  }, undefined, false, undefined, this),
141911
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142042
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141912
142043
  color: color.dim,
141913
142044
  children: " deny"
141914
142045
  }, undefined, false, undefined, this),
141915
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
142046
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
141916
142047
  color: color.faint,
141917
142048
  children: " · esc"
141918
142049
  }, undefined, false, undefined, this)
@@ -141923,7 +142054,7 @@ function PermissionPrompt({ req, width }) {
141923
142054
  }
141924
142055
 
141925
142056
  // src/ui/components/Working.tsx
141926
- var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
142057
+ var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
141927
142058
  var THINK_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
141928
142059
  var STREAM_FRAMES = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▂"];
141929
142060
  var TOOL_FRAMES = ["◐", "◓", "◑", "◒"];
@@ -141957,16 +142088,16 @@ function Working({
141957
142088
  const spinner = linger ? "●" : spinFrame(state);
141958
142089
  const f3 = Math.floor(Date.now() / 360);
141959
142090
  const dots = ["", ".", "..", "..."][f3 % 4];
141960
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
142091
+ return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
141961
142092
  width,
141962
142093
  paddingX: 1,
141963
142094
  marginTop: 1,
141964
142095
  justifyContent: "space-between",
141965
142096
  children: [
141966
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142097
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141967
142098
  color: labelColor,
141968
142099
  children: [
141969
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142100
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141970
142101
  color: dotColor,
141971
142102
  children: [
141972
142103
  spinner,
@@ -141974,22 +142105,22 @@ function Working({
141974
142105
  ]
141975
142106
  }, undefined, true, undefined, this),
141976
142107
  label,
141977
- !linger ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142108
+ !linger ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141978
142109
  color: color.accentDim,
141979
142110
  children: dots
141980
142111
  }, undefined, false, undefined, this) : null
141981
142112
  ]
141982
142113
  }, undefined, true, undefined, this),
141983
- !linger ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142114
+ !linger ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141984
142115
  children: [
141985
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142116
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141986
142117
  color: color.accentDim,
141987
142118
  children: [
141988
142119
  elapsed,
141989
142120
  "s"
141990
142121
  ]
141991
142122
  }, undefined, true, undefined, this),
141992
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142123
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
141993
142124
  color: color.faint,
141994
142125
  children: [
141995
142126
  tps >= 5 ? ` · ~${tps} tok/s` : "",
@@ -141997,7 +142128,7 @@ function Working({
141997
142128
  ]
141998
142129
  }, undefined, true, undefined, this)
141999
142130
  ]
142000
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
142131
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142001
142132
  color: color.faint,
142002
142133
  children: " "
142003
142134
  }, undefined, false, undefined, this)
@@ -142006,7 +142137,7 @@ function Working({
142006
142137
  }
142007
142138
 
142008
142139
  // src/ui/components/Viewport.tsx
142009
- var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
142140
+ var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
142010
142141
  function normalized(sel) {
142011
142142
  if (!sel)
142012
142143
  return null;
@@ -142024,7 +142155,7 @@ function selectedRangeForLine(sel, absLine) {
142024
142155
  }
142025
142156
  function LineRow({ line, absLine, selection, lineWidth }) {
142026
142157
  if (line.length === 0) {
142027
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142158
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142028
142159
  backgroundColor: color.navy,
142029
142160
  children: " ".repeat(lineWidth)
142030
142161
  }, undefined, false, undefined, this);
@@ -142033,7 +142164,7 @@ function LineRow({ line, absLine, selection, lineWidth }) {
142033
142164
  let pos = 0;
142034
142165
  const lineLen = line.reduce((n, s2) => n + s2.text.length, 0);
142035
142166
  const trailing = Math.max(0, lineWidth - lineLen);
142036
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142167
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142037
142168
  children: [
142038
142169
  line.flatMap((s2, j) => {
142039
142170
  const start = pos;
@@ -142041,7 +142172,7 @@ function LineRow({ line, absLine, selection, lineWidth }) {
142041
142172
  pos = end;
142042
142173
  if (!range2 || end <= range2[0] || start >= range2[1]) {
142043
142174
  return [
142044
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142175
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142045
142176
  color: s2.color,
142046
142177
  bold: s2.bold,
142047
142178
  italic: s2.italic,
@@ -142054,7 +142185,7 @@ function LineRow({ line, absLine, selection, lineWidth }) {
142054
142185
  const a = Math.max(range2[0] - start, 0);
142055
142186
  const b = Math.min(range2[1] - start, s2.text.length);
142056
142187
  return [
142057
- s2.text.slice(0, a) ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142188
+ s2.text.slice(0, a) ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142058
142189
  color: s2.color,
142059
142190
  bold: s2.bold,
142060
142191
  italic: s2.italic,
@@ -142062,11 +142193,11 @@ function LineRow({ line, absLine, selection, lineWidth }) {
142062
142193
  backgroundColor: s2.bg ?? color.navy,
142063
142194
  children: s2.text.slice(0, a)
142064
142195
  }, `${j}-a`, false, undefined, this) : null,
142065
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142196
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142066
142197
  inverse: true,
142067
142198
  children: s2.text.slice(a, b)
142068
142199
  }, `${j}-b`, false, undefined, this),
142069
- s2.text.slice(b) ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142200
+ s2.text.slice(b) ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142070
142201
  color: s2.color,
142071
142202
  bold: s2.bold,
142072
142203
  italic: s2.italic,
@@ -142076,7 +142207,7 @@ function LineRow({ line, absLine, selection, lineWidth }) {
142076
142207
  }, `${j}-c`, false, undefined, this) : null
142077
142208
  ].filter(Boolean);
142078
142209
  }),
142079
- trailing > 0 ? /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142210
+ trailing > 0 ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142080
142211
  backgroundColor: color.navy,
142081
142212
  children: " ".repeat(trailing)
142082
142213
  }, undefined, false, undefined, this) : null
@@ -142093,25 +142224,25 @@ function Viewport({ lines, scrollTop, height, width, selection }) {
142093
142224
  const thumb = hasBar ? Math.max(1, Math.round(height / total * height)) : 0;
142094
142225
  const maxTop = Math.max(1, total - height);
142095
142226
  const thumbStart = hasBar ? Math.min(height - thumb, Math.round(scrollTop / maxTop * (height - thumb))) : 0;
142096
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
142227
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
142097
142228
  width,
142098
142229
  children: [
142099
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
142230
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
142100
142231
  flexDirection: "column",
142101
142232
  width: width - 1,
142102
- children: padded.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(LineRow, {
142233
+ children: padded.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(LineRow, {
142103
142234
  line: l,
142104
142235
  absLine: scrollTop + i2,
142105
142236
  selection,
142106
142237
  lineWidth: width - 1
142107
142238
  }, i2, false, undefined, this))
142108
142239
  }, undefined, false, undefined, this),
142109
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
142240
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
142110
142241
  flexDirection: "column",
142111
142242
  width: 1,
142112
142243
  children: Array.from({ length: height }, (_, i2) => {
142113
142244
  const on = hasBar && i2 >= thumbStart && i2 < thumbStart + thumb;
142114
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
142245
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
142115
142246
  color: on ? color.accentDim : color.faint,
142116
142247
  children: on ? "┃" : hasBar ? "│" : " "
142117
142248
  }, i2, false, undefined, this);
@@ -142366,8 +142497,8 @@ function blockLines(tok, width) {
142366
142497
  }
142367
142498
  case "blockquote": {
142368
142499
  const inner = (tok.tokens ?? []).flatMap((t2) => blockLines(t2, Math.max(width - 2, 1)));
142369
- const bar = { text: glyph.userBar + " ", color: color.accentDim };
142370
- return inner.map((l) => [bar, ...l]);
142500
+ const bar2 = { text: glyph.userBar + " ", color: color.accentDim };
142501
+ return inner.map((l) => [bar2, ...l]);
142371
142502
  }
142372
142503
  case "hr":
142373
142504
  return [[{ text: glyph.rule.repeat(Math.min(width, 24)), color: color.faint }]];
@@ -143187,14 +143318,14 @@ class RoutingSelector {
143187
143318
  }
143188
143319
  prepare(task) {
143189
143320
  const kind = task.kind ?? classify(task.prompt);
143190
- const bar = BAR[kind];
143321
+ const bar2 = BAR[kind];
143191
143322
  const required2 = task.requires ?? [];
143192
143323
  const ctx = buildRoutingContext(ctx_now());
143193
143324
  const estInputTokens = task.estTokens || NOMINAL_INPUT_TOKENS;
143194
143325
  const all = this.enumerate(ctx);
143195
143326
  if (all.length === 0) {
143196
143327
  const m2 = pickDefaultModel(this.fallbackId);
143197
- return { kind, bar, required: required2, ctx, pool: [], clears: [], estInputTokens, fallback: m2 ?? undefined };
143328
+ return { kind, bar: bar2, required: required2, ctx, pool: [], clears: [], estInputTokens, fallback: m2 ?? undefined };
143198
143329
  }
143199
143330
  const capable = required2.length ? all.filter((c) => supportsRequirements(c.spec, required2)) : all;
143200
143331
  if (capable.length === 0) {
@@ -143205,8 +143336,8 @@ class RoutingSelector {
143205
143336
  const fits = need > 0 ? capable.filter((c) => c.spec.contextWindow >= need) : capable;
143206
143337
  let pool = fits.length ? fits : capable;
143207
143338
  pool = applyGlobalPreference(pool);
143208
- const clears = pool.filter((c) => qualityOf(c) >= bar);
143209
- return { kind, bar, required: required2, ctx, pool, clears, estInputTokens };
143339
+ const clears = pool.filter((c) => qualityOf(c) >= bar2);
143340
+ return { kind, bar: bar2, required: required2, ctx, pool, clears, estInputTokens };
143210
143341
  }
143211
143342
  preferredIn(kind, candidates) {
143212
143343
  const pref = preferenceFor(kind);
@@ -143406,7 +143537,7 @@ function backspaceFilter(panel) {
143406
143537
  }
143407
143538
 
143408
143539
  // src/ui/components/Panel.tsx
143409
- var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
143540
+ var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
143410
143541
  function accountStateColor3(status) {
143411
143542
  if (status === "active")
143412
143543
  return color.ok;
@@ -143431,9 +143562,9 @@ function Panel({
143431
143562
  const lines = staticLines ?? itemsToLines(panel.items, innerW);
143432
143563
  const maxScroll = Math.max(0, lines.length - bodyH);
143433
143564
  const scroll = Math.min(panel.scroll, maxScroll);
143434
- body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143565
+ body = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143435
143566
  paddingX: 1,
143436
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Viewport, {
143567
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Viewport, {
143437
143568
  lines,
143438
143569
  scrollTop: scroll,
143439
143570
  height: bodyH,
@@ -143447,48 +143578,48 @@ function Panel({
143447
143578
  const start = windowStart(idx, rows.length, bodyH);
143448
143579
  const slice2 = rows.slice(start, start + bodyH);
143449
143580
  const labelPad = accounts?.labelPad ?? 0;
143450
- body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143581
+ body = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143451
143582
  flexDirection: "column",
143452
143583
  paddingX: 1,
143453
- children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143584
+ children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143454
143585
  color: color.faint,
143455
- children: "no accounts yet /account add to add one"
143586
+ children: "no accounts yet · /account add to add one"
143456
143587
  }, undefined, false, undefined, this) : slice2.map((r2, i2) => {
143457
143588
  const sel = start + i2 === idx;
143458
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143589
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143459
143590
  backgroundColor: sel ? color.accentBg : undefined,
143460
143591
  children: [
143461
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143592
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143462
143593
  color: sel ? color.accent : color.faint,
143463
143594
  children: sel ? "▶ " : " "
143464
143595
  }, undefined, false, undefined, this),
143465
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143596
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143466
143597
  color: color.text,
143467
143598
  bold: r2.active,
143468
143599
  children: r2.name.padEnd(labelPad)
143469
143600
  }, undefined, false, undefined, this),
143470
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143601
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143471
143602
  color: color.faint,
143472
143603
  children: [
143473
143604
  " ",
143474
143605
  r2.type
143475
143606
  ]
143476
143607
  }, undefined, true, undefined, this),
143477
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143608
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143478
143609
  color: accountStateColor3(r2.status),
143479
143610
  children: [
143480
143611
  " ",
143481
143612
  r2.status
143482
143613
  ]
143483
143614
  }, undefined, true, undefined, this),
143484
- r2.detail ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143615
+ r2.detail ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143485
143616
  color: color.faint,
143486
143617
  children: [
143487
143618
  " · ",
143488
143619
  r2.detail
143489
143620
  ]
143490
143621
  }, undefined, true, undefined, this) : null,
143491
- r2.type === "subscription" && !(r2.detail && r2.detail.includes("@")) ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143622
+ r2.type === "subscription" && !(r2.detail && r2.detail.includes("@")) ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143492
143623
  color: color.accentDim,
143493
143624
  children: [
143494
143625
  " · /account login ",
@@ -143496,7 +143627,7 @@ function Panel({
143496
143627
  " to identify"
143497
143628
  ]
143498
143629
  }, undefined, true, undefined, this) : null,
143499
- r2.active ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143630
+ r2.active ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143500
143631
  color: color.ok,
143501
143632
  children: [
143502
143633
  " ",
@@ -143514,10 +143645,10 @@ function Panel({
143514
143645
  const idx = clampIndex(panel.index, rows.length);
143515
143646
  const start = windowStart(idx, rows.length, bodyH);
143516
143647
  const slice2 = rows.slice(start, start + bodyH);
143517
- body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143648
+ body = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143518
143649
  flexDirection: "column",
143519
143650
  paddingX: 1,
143520
- children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143651
+ children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143521
143652
  color: color.faint,
143522
143653
  children: [
143523
143654
  "no models match “",
@@ -143527,23 +143658,23 @@ function Panel({
143527
143658
  }, undefined, true, undefined, this) : slice2.map((r2, i2) => {
143528
143659
  const sel = start + i2 === idx;
143529
143660
  const pinned = r2.id === currentModelId;
143530
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143661
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143531
143662
  backgroundColor: sel ? color.accentBg : undefined,
143532
143663
  children: [
143533
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143664
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143534
143665
  color: sel ? color.accent : color.faint,
143535
143666
  children: sel ? "▶ " : " "
143536
143667
  }, undefined, false, undefined, this),
143537
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143668
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143538
143669
  color: pinned ? color.ok : color.text,
143539
143670
  bold: pinned,
143540
143671
  children: r2.label.padEnd(22)
143541
143672
  }, undefined, false, undefined, this),
143542
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143673
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143543
143674
  color: color.faint,
143544
143675
  children: r2.provider
143545
143676
  }, undefined, false, undefined, this),
143546
- pinned ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143677
+ pinned ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143547
143678
  color: color.ok,
143548
143679
  children: [
143549
143680
  " ",
@@ -143557,37 +143688,37 @@ function Panel({
143557
143688
  }, undefined, false, undefined, this);
143558
143689
  hint = `filter: ${panel.filter || "(type to filter)"} · ↑↓ · ⏎ pin · esc close`;
143559
143690
  }
143560
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143691
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143561
143692
  flexDirection: "column",
143562
143693
  width,
143563
143694
  height,
143564
143695
  children: [
143565
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143696
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143566
143697
  width,
143567
143698
  paddingX: 1,
143568
143699
  justifyContent: "space-between",
143569
143700
  children: [
143570
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143701
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143571
143702
  color: color.accent,
143572
143703
  bold: true,
143573
143704
  children: panel.title
143574
143705
  }, undefined, false, undefined, this),
143575
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143706
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143576
143707
  color: color.faint,
143577
143708
  children: "esc to close"
143578
143709
  }, undefined, false, undefined, this)
143579
143710
  ]
143580
143711
  }, undefined, true, undefined, this),
143581
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143712
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143582
143713
  flexDirection: "column",
143583
143714
  width,
143584
143715
  height: bodyH,
143585
143716
  children: body
143586
143717
  }, undefined, false, undefined, this),
143587
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
143718
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
143588
143719
  width,
143589
143720
  paddingX: 1,
143590
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
143721
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
143591
143722
  color: color.faint,
143592
143723
  children: hint
143593
143724
  }, undefined, false, undefined, this)
@@ -145518,7 +145649,7 @@ bun run typecheck
145518
145649
  },
145519
145650
  {
145520
145651
  file: "CLAUDE.md",
145521
- text: "# Gearbox — project guide\n\nGearbox is a multi-provider coding harness for the terminal: a beautiful, simple terminal agent that reads/writes code and runs commands, talking to any provider (Anthropic, OpenAI, Google, DeepSeek) through one clean loop.\n\n**The point of the project:** intelligent per-task *model routing* — automatically picking the right model for each task across every provider and account you pay for. Basic routing is live (`RoutingSelector` — classify → quality bar → cheapest winner); the richer engine (shadow-eval, credit/limit penalties, confidence display) layers on top of the same seam. See `DESIGN.md` for the full vision and `experiments/FINDINGS.md` for the validation behind it.\n\n## The one rule that matters\n\n**Keep the routing seam clean.** The agent must never hardcode a model. It asks a `ModelSelector` for the model to use. `RoutingSelector` is the live default (classify task → filter by quality bar → cheapest winner); `FixedSelector` is used only when a model is explicitly pinned (`--model` flag or `/model <name>`). Concretely:\n\n- `src/model/selector.ts` — the seam. `select(task) => ModelChoice` (now carrying an optional `Backend` so the runner can dispatch in-loop vs a subscription seat). Do not bypass it.\n- `src/model/router.ts` — `RoutingSelector` is **account-aware**: it scores `(model, account)` PAIRS, not just models. Candidates = in-loop registry models × the accounts that serve them + flat-rate subscription **seats** (`providers.subscriptionSeats()`, a seat mirrors a canonical model but runs via the vendor binary). Flow: classify → quality bar → context fit → global/`/prefer` preference filter → score → return `{model, reason, backend}`. A seat is ~free until its rate limit, so it wins by default and fails over to metered API as the window fills. `SubscriptionPinSelector`/`FixedSelector` are hard pins (explicit `/account use` or `/model`) that beat auto-routing.\n- `src/model/scoring.ts` — the PURE scorer: `score = costEst + scarcity + switchPenalty + limitPenalty + apiThrottlePenalty − planBonus`, argmin tie-broken deterministically. No I/O; fixture-tested. Every account-state term degrades safely — and where a provider exposes nothing, we ESTIMATE rather than go blind: live API rate-limit headers (`src/model/rate-headers.ts`, parsed from each response → `apiThrottle`, gentle near-empty penalty) and a self-declared budget − tracked spend (`/budget`, an estimated balance feeding scarcity). A missing signal with no estimate is still neutral (no errors per provider).\n- `src/model/routing-context.ts` — `buildRoutingContext()`: the per-turn account-state snapshot (balance where exposed, subscription rate headroom = min over the 5h/weekly windows) read from disk-cached `usage.json`. No network on the hot path; balances refreshed in the background (App effect).\n- `src/model/profiles.ts` — the data corpus: quality, cost, latency, tokenizer calibration, **and the per-model effort vocabulary** (`efforts`) per the provider research. Routing reads this; effort is clamped/omitted against the chosen model's set, never sent unsupported.\n- `src/providers.ts` — maps a provider+model id to an AI SDK model instance. Already multi-provider. Adding a model is data, not code.\n- Every model call captures token usage (`src/agent/run.ts`) so the cost engine has data. Do not drop usage.\n- The UI consumes a normalized `AgentEvent` stream (`src/agent/events.ts`), never the AI SDK's raw types. This decouples the UI from the provider layer and from routing.\n\nIf you find yourself writing `anthropic('claude-...')` anywhere outside `providers.ts`, stop — route it through the selector.\n\n## Layout\n\n```\nsrc/\n cli.tsx entry point; renders the Ink app; picks RoutingSelector by default\n config.ts minimal config (default model, provider from env)\n providers.ts provider+model id -> AI SDK model (multi-provider; contextWindow per model)\n commands.ts slash-command metadata + pure helpers (fuzzy model match, /help, model list)\n tools.ts read / write / edit / list / search / glob / run_shell (AI SDK tools)\n model/\n selector.ts THE ROUTING SEAM — ModelSelector + ModelChoice.backend + FixedSelector (pinned model)\n router.ts RoutingSelector (account-aware): scores (model, account) pairs incl. subscription seats; SubscriptionPinSelector\n scoring.ts PURE scorer: cost + scarcity + limit − plan bonus; deterministic, fixture-tested\n routing-context.ts per-turn account-state snapshot (balance + subscription rate headroom) from usage.json\n cooldown.ts reactive-failover support: classify a failure + park an exhausted account so the router routes around it\n profiles.ts model corpus: quality (SWE-bench), cost ($/Mtok), latency, tokenizer calibration, per-model effort vocab\n tokens.ts calibrated token counting (js-tiktoken × per-model calibration factor)\n preferences.ts persist /prefer kind model choices to ~/.gearbox/routing-preferences.json\n reasoning.ts reasoning/thinking config helpers\n context/\n builder.ts context engine: system + memory + repo map + retrieved files + curated history\n retrieve.ts BM25 lexical retrieval — top-K relevant files for a prompt (no model call)\n repomap.ts repo structure summary for the system prompt\n memory.ts project memory (GEARBOX.md / CLAUDE.md loaded into context)\n compact.ts context compaction (/compact)\n accounts/\n types.ts Account + AuthMethod types (API key, AWS, Azure, Vertex, CLI, OpenAI-compat)\n store.ts accounts.json persistence (~/.gearbox/accounts.json)\n catalog.ts provider catalog (known providers, env vars, labels)\n detect.ts auto-detect env creds + cloud credentials\n onboard.ts interactive add/test account flows; addByPastedKey routes through sniff.ts\n sniff.ts pure credential sniffer: paste anything (key / AWS block / SA JSON / Azure URL / gateway key) → {kind, provider, fields, missing}\n resolve.ts credential resolution (Account → ResolvedCreds) + rank(model) → ordered failover pool (cross-provider, health-sorted)\n discover.ts per-account model discovery (Azure deployments / Foundry / gateway /models) → account.models; catalog defaultModels are seeds, not callable ids\n health.ts account health: classifyError (provider error → state), checkHealth (cached probe, timeout-bounded), recordHealth; no background polling\n usage.ts per-account spend ledger + rate-limit snapshots + balance tracking\n balance.ts provider balance fetch helpers\n help/\n ask.ts /ask corpus: bundled docs + generated command reference, system prompt, meta-question auto-detect\n agent/\n events.ts AgentEvent — normalized stream the UI consumes\n run.ts real agent loop (AI SDK streamText -> AgentEvent), abort-aware; runCompletion = tool-less grounded answer (used by /ask); returns a structured failure (for failover)\n failover.ts runWithFailover: run a turn over the ranked account pool; on a credential failure before output, advance to the next; clear exhaustion errors\n cli-backend.ts claude/codex CLI subprocess backend (for Pro/Max subscriptions)\n mock.ts scripted demo stream (runs with no API key; used by tests)\n ui/\n theme.ts colors + glyphs (the look)\n input.ts pure key→action reducer for the composer (tested)\n history.ts pure ↑/↓ prompt-history nav (tested)\n net.ts background online probe; status bar shows ⚠ offline when down\n useTerminalSize.ts reactive width on resize (everything reflows)\n git.ts current branch for the status line\n App.tsx the Ink app: state, useInput dispatch, commands, turns\n components/ Banner, Transcript, Composer, CommandPalette, StatusBar, PermissionPrompt, Panel\n panel.ts dismissable command-panel model + pure helpers (clamp/window/filter); tested\ntest/ pure-logic + render tests (ink-testing-library); no keys\nDESIGN.md full product vision (routing, requirements, UX)\nexperiments/ prototypes that validated the architecture\n```\n\nThe composer is custom (Ink `useInput` + `src/ui/input.ts`), not a third-party widget — full control over the cursor, ↑/↓ history, and esc-to-interrupt, with no focus/remount fragility. **Multi-line**: ⌃J (or shift/alt+⏎) inserts a newline, ⏎ submits; ↑/↓ move between lines and fall through to history at the top/bottom line; bracketed paste (enabled in `cli.tsx`) inserts multi-line text literally (CR normalized, paste markers stripped) instead of submitting per line. `caretPos()` is the shared line/col helper. **Readline editing** (all pure in `input.ts`, tested): ⌃U/⌃K kill to line start/end, ⌃W / ⌥⌫ kill word, ⌃D forward-delete, ⌥/⌃ + ←→ word-jump, ⌃A/⌃E line home/end. Keys: ⏎ send · ⌃J newline · ↑↓ line/history · ← → cursor · ⌥←→ word · tab complete @file · **shift+tab cycles mode (normal · auto-accept · plan)** · ⌃Y copy last reply · esc interrupt · ⌃c quit. `/keys` shows the cheatsheet.\n\n**Modes & effort.** Three input modes cycled by shift+tab (`App.tsx` `cycleMode`): **normal** (asks before writes/edits/shell), **auto-accept** (file writes/edits apply without asking — the permission broker auto-resolves `write`/`edit`; shell still gated; diffs still render), **plan** (read-only). Plus **yolo** (auto-approve everything) via `/yolo`. **Effort tiers** (`/effort fast|balanced|max`, or `setEffort`) pin the model through the routing seam (fast→haiku, balanced/max→sonnet) — the active mode + `⚡effort` show as badges in the `StatusBar`. **Click pickers** (fullscreen only): clicking the **model** or **effort** label in the status bar opens a floating picker above it (↑↓ select · ⏎ apply · esc close), reusing the same `/model`/`/effort` command path. The slash commands remain the keyboard path. The fragile row+column hit-test lives in pure, tested `statusBarHit`/`statusBarLayout` (`StatusBar.tsx`); `App.tsx` only supplies live layout (composer line count, `PALETTE_ROWS`, the rendered model/effort/mode) and toggles `quickPicker` state. Inline mode has no mouse grab, so the labels stay informational there. **Copy**: ⌃Y / `/copy` copies the last reply via OSC 52 (`src/ui/clipboard.ts`, works over SSH); `/export [file]` writes the transcript to Markdown. **Terminal integration** (`src/ui/terminal.ts`): the tab title (OSC 2) reflects working/idle, and a long turn (>8s) rings the bell + fires a desktop notification (macOS) so you can step away.\n\n**More UX affordances.** **Type-ahead**: prompts submitted while busy are queued (`queueRef`, shown as chips) and sent when the turn ends. **⌃C** interrupts a turn → clears the composer → \"press again to quit\" (`cli.tsx` renders with `exitOnCtrlC:false`). **Large pastes** collapse to a `[Pasted N lines]` chip (`pasteStoreRef`), expanded back on submit. **Fuzzy** `@file`/`/command` pickers (`src/ui/fuzzy.ts` — substring-first, then subsequence scored by boundary+contiguity; tested). **Cost**: live `$` estimate in the status bar from per-turn model+tokens (`estimateCost` + per-model pricing in `providers.ts`). **Syntax highlighting** for code blocks (`src/ui/highlight.ts` — lightweight per-line tokenizer → Ink spans, NEVER raw ANSI; used by both `lines.ts` `clipSpans` and `Markdown.tsx`). `?` on an empty composer shows the cheatsheet (`KEYS_HELP`).\n\n**Sessions** (`src/session.ts`): conversations persist per-project under `~/.gearbox/sessions/<slug>/` (`GEARBOX_HOME` overrides). Each record holds provider-neutral `messages` + the UI `items` + **per-turn `{model, usage, at}`** (routing/cost data — the record is deliberately not single-model). `gearbox --continue`/`-c` resumes the latest; `/resume [n]` lists/loads in-app; `/clear` starts a fresh session. Prompt history persists across runs (`history.json`). Saving is best-effort (never crashes the app); skipped in demo mode.\n\nFeatures: full markdown via **marked** (parse, `marked.lexer`) + **Ink** (render) in `Markdown.tsx` — headings, bold/italic/inline-code, tables, ordered+nested lists, blockquotes, code blocks. NO foreign ANSI in Ink (cli-highlight/marked-terminal were tried and removed — they corrupt Ink's width/wrapping; render marked's token tree as Ink elements instead). Markdown gets a `width` prop (threaded App→Transcript→Markdown) for table/rule sizing. Colored diffs under edits (`src/diff.ts`, edit/write tools return `{summary,diff}`), plan mode (read-only tools + plan prompt; `/plan` or shift+tab), `!cmd` runs a shell command directly (`src/shell.ts`), `@file` mentions (fuzzy picker `src/ui/mention.ts`+`files.ts`; expanded into the model message on send), live \"working · Ns\" timer.\n\n**Boo (the mascot).** A pixel ghost, now **parametric** (`src/ui/ghost/engine.ts`, ported from a Claude Design handoff). A 20×20 pixel sprite composited from composable layers — body (palette) + face (eyes/mouth) + accessory + persona + a frame-driven overlay (tears/dots/confetti/Z's/sparkle/hearts) — then FOLDED into half-block cells (`▀`/`▄`, top px → `t`/glyph color, bottom px → `b`/bg). `renderGhost(cfg)` is the source of truth for the **default blocks path**; it's pure + memoized. The data: 13 faces (`FACES`), 9 palettes (`PALETTES`), 6 accessories, 9 personas (personas/accessories ported but not yet surfaced in the live UI). Ink `color`/`backgroundColor` props only, NEVER raw ANSI (corrupts Ink's width math). PNG paths are **opt-in** via `GEARBOX_GHOST`:\n\n- `GEARBOX_GHOST=kitty` — real PNG via kitty graphics Unicode placeholders (`U+10EEEE`, fg encodes image id, diacritics encode row/col; PNGs transmitted once in `cli.tsx`). NOTE: the placeholder protocol is young and mis-rendered (squished) in Ghostty during testing — kept opt-in until that's solved.\n- `GEARBOX_GHOST=iterm` — OSC 1337 splash banner (iTerm2/WezTerm).\n\n`detectImageMode()` returns `blocks` unless `GEARBOX_GHOST` opts in. Baked PNGs live in `src/ui/mascot-png.ts`; `bun run scripts/ghost-preview.ts` previews the parametric engine (splash + all faces + the in-flow state crops). **Boo is animated but deliberately calm** on the blocks path (`AnimatedGhost` in `Mascot.tsx`): one shared, unhurried 240ms tick (leaf-local `useTick`, never lifted to App root); talk + overlays advance at half that (~480ms). There is NO idle bob/float and NO splash sparkle — motion is a quiet sign of life, not fidgeting (the splash just blinks every ~6s; in-flow only the state-meaningful overlay/talk moves). `GEARBOX_NO_MOTION=1` freezes to frame 0. `/ghost [mood]` cycles the skin (`skinToCfg` maps it to a cfg; `shades` is the cool face + shades accessory).\n\n**Layout: fullscreen by default; inline is opt-in.** **Fullscreen is the default** (alt-screen frame + virtualized scroll region + scrollbar + mouse wheel scroll); `--inline`, `GEARBOX_INLINE=1`, or `/config inline on` (pref `fullscreen: false`) opts into inline mode. `GEARBOX_FULLSCREEN=1` or `--fullscreen` forces fullscreen explicitly. The decision lives in `cli.tsx` (`wantsFullscreen`). Grabbing the mouse for wheel-scroll is exactly what disables native terminal selection, so in fullscreen mode text selection requires the terminal's modifier (e.g. Option-drag in Ghostty). **Inline mode** (the plain `Transcript` component): no alt-screen, no mouse grab — native click-drag selection / scrollback / copy all work with no modifier. The transcript is a **virtualized line buffer**: `src/ui/lines.ts` (`itemsToLines`) flattens items into styled `Line`s (markdown→lines, wrapping, diffs) — INVARIANT: every line ≤ width (tested), so nothing overflows. **Streaming perf**: flattening the markdown-heavy `assistant`/`user` items is super-linear with their length, so `staticItemLines` memoizes per item in a `WeakMap` keyed by object reference (unchanged items keep identity across renders, so only the changing tail re-parses — history is free; running tools are not cached since their spinner animates). On the producer side, assistant **text deltas are coalesced** on a ~45ms flush timer in `App.tsx`'s `onEvent` (mirroring the tool-stream coalescer), so streaming re-renders at ~22fps instead of per-token — both together stop the auto-scroll jitter that grew with reply length. `finishAssistant`/the turn `finally` flush any buffered text before marking done or on interrupt. In fullscreen, `App` renders only the visible window via `Viewport` (`src/ui/components/Viewport.tsx`) at a computed `transcriptHeight = rows − header − footer` (footer over-estimated so the frame never exceeds the screen; alt-screen clips, so under-filling is safe). Fullscreen scroll: mouse wheel (SGR mouse reporting enabled in `cli.tsx`; parsed off raw stdin in `App` since Ink doesn't model mouse — buttons 64/65) and PgUp/PgDn; new output re-pins to the bottom (`atBottomRef`); a scrollbar sits on the right. (In fullscreen, mouse reporting means text selection needs the terminal's modifier, e.g. Option-drag in Ghostty — which is why inline is now the default.) The virtualized buffer replaced an earlier flex/overflow fullscreen that corrupted on tall output. Chrome spans full width; prose wraps ≤100 cols. The plain `Transcript` component is the inline-fallback renderer. `scripts/gen-mascot.ts` still bakes the PNGs + baked sprites (`mascot-sprite.ts` `GHOSTS`) — but those now feed **only the opt-in kitty/iTerm image path** (`image.ts`); the default blocks path renders the parametric engine instead. The splash scales to the terminal (big=2×/mini=1×/none by rows×cols, in `App.tsx`). The inline/working presence is the compact **state ghost** (see below) — a native-resolution head crop so Boo never dominates the transcript.\n\nCommands are grouped in `/help` (models · conversation · accounts · save · modes · settings · other) and `src/commands.ts` carries plain-language descriptions: /model [name] (fuzzy — \"haiku\"; `/model auto` routes, `/model all` lists every provider) /effort [fast|balanced|max] /prefer [kind model] (remember a confirmed routing preference for a task type) /clear /resume /retry /compact /context /memory /ask &lt;q&gt; (answer questions about Gearbox itself from its bundled docs via a cheap routed model; plain meta-questions auto-route here with a visible affordance) /account (unified: list/add/login/use/rm/refresh — `/accounts` and `/login` are hidden aliases; `/account refresh` re-discovers each account's real callable models) /cost /copy /export [file] /plan /yolo /theme /config (theme·vim·notify·inline; `/vim` is a hidden alias) /init /keys /help /exit. **Hidden** (work but not listed): /accounts /login /vim /ghost. **Removed:** /cwd (the working dir now shows in `/context`). `formatModelList` shows usable models first and collapses no-key providers to a one-line count.\n\n**Command panel (fullscreen only).** Big info-dump commands open a dismissable, Esc-closable overlay instead of dumping into the transcript (`Panel.tsx` + pure `panel.ts`, wired in `App.tsx`): `/help` `/keys` `/context` `/cost` `/memory` are scrollable static dumps (reuse `itemsToLines` + `Viewport`); `/account` and `/model` are interactive lists (↑↓ select · ⏎ acts — they just dispatch the equivalent `/account <n>` / `/model <id>` command and close), and `/model` has type-to-filter (127 Foundry models). The panel replaces the transcript Viewport region while open and takes precedence over `welcome`; the key handler is a branch in `useInput` placed after ⌃C so Esc closes the panel rather than interrupting a turn. Short confirmations (`model → haiku`, `remembered`, `✓ added`, errors) stay inline. Inline mode keeps the old inline printing (no alt-screen to overlay). `openInfoPanel` returns false inline so callers fall back to `push`.\n\n**Accounts: reliability by design.** Every subscription, API key, and cloud credential is meant to work all the time. Switching is **by name only** (a stable unique `slug` per account, e.g. `claude-work`; positional numbers are gone — removing an account never repoints another). Each account carries a cached **health** state (`✓ ready · ⚠ expired · ✗ invalid · ⏳ limited · — unknown`) refreshed at natural touchpoints (boot sweep, opening `/account`, on switch, on live failure) — never by background polling; probes are timeout-bounded. A turn runs through a **failover pool**: `resolve.rank(model)` returns every account that can serve the model's family (cross-provider — Claude can fall Anthropic key → Bedrock → Vertex), health-sorted; `agent/failover.ts` tries them best-first and, on a credential-class failure **before any output** (expired/invalid/no-credit/rate-limited), transparently advances to the next and tells the user which account ran. A real (network/model) error or any failure after output streamed does NOT churn the pool. When the pool is exhausted, one consolidated error names each account, why it failed, and the one command to fix it. Expired subscriptions get one-step re-login: `/account login <name>` (and CLI failures name that exact command). `/account add <paste>` runs the credential **sniffer** (`accounts/sniff.ts`) — paste an API key, an AWS access key or credentials block, a Vertex service-account JSON, an Azure endpoint, or a Vercel gateway key, and it identifies the provider, fills the gaps interactively, and live-tests it.\n\n**Permission gate:** `write_file`/`edit_file`/`run_shell` block on a confirm before mutating. Broker: `src/permission.ts` (`requestPermission` in the tools; `setPermissionHandler` installed by `App`; no handler → allow, so tests/headless are unchanged). Decisions: **once** (1), **always** (2, grants that kind for the session), **all/yolo** (a, auto-approves everything until toggled), **deny** (3/esc). YOLO is also toggled by `/yolo` or started with `--yolo`; a `⚡ yolo` badge shows in the status. The `!` prefix is user-initiated so it is NOT gated. Search/nav tools: `search` (ripgrep, Bun-walk fallback) and `glob` (`Bun.Glob`), both read-only (also in plan mode). The working indicator IS Boo now (`components/Working.tsx`): a compact head-crop ghost whose face follows the agent state — thinking (dots) → streaming (talk) → tool (loading dots) → a clean-finish celebrate (party hat + confetti) → error (crying with falling tears). `App.tsx` derives `mascotState` from the `onEvent` stream; the success/error beat **lingers ~1.5s** after the turn (`linger` state — the working line gates on `busy || linger`, since it would otherwise unmount the instant `busy` goes false). Crops are per-state (`stateView`): head (rows 4–14), head+dots (2–14), head+hat (0–14) so overlays outside the head still read. This deliberately supersedes the earlier \"Boo stays on the welcome splash only / in-flow movement reads as noise\" decision — the compact, state-bearing ghost is the point of the design port.\n\n## Conventions\n\n- Runtime: **Bun**. TypeScript + TSX. Run with `bun run src/cli.tsx`.\n- UI: **Ink** (React for terminals) + **@inkjs/ui**. Keep it calm and beautiful: restrained palette (one accent), generous spacing, consistent glyphs. The look lives in `src/ui/theme.ts` — change colors/glyphs there, not inline.\n- Open + free: MIT, no paid dependencies, no hosted backend, no telemetry. The only cost is the user's own model calls on their own keys.\n- Tools must be safe by default: confirm or sandbox anything destructive; never `rm -rf` or write outside the workspace without intent.\n\n## Run it\n\n```bash\nbun install\n# set at least one key:\nexport ANTHROPIC_API_KEY=... # or OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY / DEEPSEEK_API_KEY\nbun run src/cli.tsx # or: bun start\n```\n\nWith no key it launches in demo mode (a scripted transcript) so the UI still runs.\n\n## Test\n\n```bash\nbun test # render tests + agent-loop tests; no API key needed\nbun run typecheck # tsc --noEmit\n```"
145652
+ text: "# Gearbox — project guide\n\nGearbox is a multi-provider coding harness for the terminal: a beautiful, simple terminal agent that reads/writes code and runs commands, talking to any provider (Anthropic, OpenAI, Google, DeepSeek) through one clean loop.\n\n**The point of the project:** intelligent per-task *model routing* — automatically picking the right model for each task across every provider and account you pay for. Basic routing is live (`RoutingSelector` — classify → quality bar → cheapest winner); the richer engine (shadow-eval, credit/limit penalties, confidence display) layers on top of the same seam. See `DESIGN.md` for the full vision and `experiments/FINDINGS.md` for the validation behind it.\n\n## The one rule that matters\n\n**Keep the routing seam clean.** The agent must never hardcode a model. It asks a `ModelSelector` for the model to use. `RoutingSelector` is the live default (classify task → filter by quality bar → cheapest winner); `FixedSelector` is used only when a model is explicitly pinned (`--model` flag or `/model <name>`). Concretely:\n\n- `src/model/selector.ts` — the seam. `select(task) => ModelChoice` (now carrying an optional `Backend` so the runner can dispatch in-loop vs a subscription seat). Do not bypass it.\n- `src/model/router.ts` — `RoutingSelector` is **account-aware**: it scores `(model, account)` PAIRS, not just models. Candidates = in-loop registry models × the accounts that serve them + flat-rate subscription **seats** (`providers.subscriptionSeats()`, a seat mirrors a canonical model but runs via the vendor binary). Flow: classify → quality bar → context fit → global/`/prefer` preference filter → score → return `{model, reason, backend}`. A seat is ~free until its rate limit, so it wins by default and fails over to metered API as the window fills. `SubscriptionPinSelector`/`FixedSelector` are hard pins (explicit `/account use` or `/model`) that beat auto-routing.\n- `src/model/scoring.ts` — the PURE scorer: `score = costEst + scarcity + switchPenalty + limitPenalty + apiThrottlePenalty − planBonus`, argmin tie-broken deterministically. No I/O; fixture-tested. Every account-state term degrades safely — and where a provider exposes nothing, we ESTIMATE rather than go blind: live API rate-limit headers (`src/model/rate-headers.ts`, parsed from each response → `apiThrottle`, gentle near-empty penalty) and a self-declared budget − tracked spend (`/budget`, an estimated balance feeding scarcity). A missing signal with no estimate is still neutral (no errors per provider).\n- `src/model/routing-context.ts` — `buildRoutingContext()`: the per-turn account-state snapshot (balance where exposed, subscription rate headroom = min over the 5h/weekly windows) read from disk-cached `usage.json`. No network on the hot path; balances refreshed in the background (App effect).\n- `src/model/profiles.ts` — the data corpus: quality, cost, latency, tokenizer calibration, **and the per-model effort vocabulary** (`efforts`) per the provider research. Routing reads this; effort is clamped/omitted against the chosen model's set, never sent unsupported.\n- `src/providers.ts` — maps a provider+model id to an AI SDK model instance. Already multi-provider. Adding a model is data, not code.\n- Every model call captures token usage (`src/agent/run.ts`) so the cost engine has data. Do not drop usage.\n- The UI consumes a normalized `AgentEvent` stream (`src/agent/events.ts`), never the AI SDK's raw types. This decouples the UI from the provider layer and from routing.\n\nIf you find yourself writing `anthropic('claude-...')` anywhere outside `providers.ts`, stop — route it through the selector.\n\n## Layout\n\n```\nsrc/\n cli.tsx entry point; renders the Ink app; picks RoutingSelector by default\n config.ts minimal config (default model, provider from env)\n providers.ts provider+model id -> AI SDK model (multi-provider; contextWindow per model)\n commands.ts slash-command metadata + pure helpers (fuzzy model match, /help, model list)\n tools.ts read / write / edit / list / search / glob / run_shell (AI SDK tools)\n model/\n selector.ts THE ROUTING SEAM — ModelSelector + ModelChoice.backend + FixedSelector (pinned model)\n router.ts RoutingSelector (account-aware): scores (model, account) pairs incl. subscription seats; SubscriptionPinSelector\n scoring.ts PURE scorer: cost + scarcity + limit − plan bonus; deterministic, fixture-tested\n routing-context.ts per-turn account-state snapshot (balance + subscription rate headroom) from usage.json\n cooldown.ts reactive-failover support: classify a failure + park an exhausted account so the router routes around it\n profiles.ts model corpus: quality (SWE-bench), cost ($/Mtok), latency, tokenizer calibration, per-model effort vocab\n tokens.ts calibrated token counting (js-tiktoken × per-model calibration factor)\n preferences.ts persist /prefer kind model choices to ~/.gearbox/routing-preferences.json\n reasoning.ts reasoning/thinking config helpers\n context/\n builder.ts context engine: system + memory + repo map + retrieved files + curated history\n retrieve.ts BM25 lexical retrieval — top-K relevant files for a prompt (no model call)\n repomap.ts repo structure summary for the system prompt\n memory.ts project memory (GEARBOX.md / CLAUDE.md loaded into context)\n compact.ts context compaction (/compact)\n accounts/\n types.ts Account + AuthMethod types (API key, AWS, Azure, Vertex, CLI, OpenAI-compat)\n store.ts accounts.json persistence (~/.gearbox/accounts.json)\n catalog.ts provider catalog (known providers, env vars, labels)\n detect.ts auto-detect env creds + cloud credentials\n onboard.ts interactive add/test account flows; addByPastedKey routes through sniff.ts\n sniff.ts pure credential sniffer: paste anything (key / AWS block / SA JSON / Azure URL / gateway key) → {kind, provider, fields, missing}\n resolve.ts credential resolution (Account → ResolvedCreds) + rank(model) → ordered failover pool (cross-provider, health-sorted)\n discover.ts per-account model discovery (Azure deployments / Foundry / gateway /models) → account.models; catalog defaultModels are seeds, not callable ids\n health.ts account health: classifyError (provider error → state), checkHealth (cached probe, timeout-bounded), recordHealth; no background polling\n usage.ts per-account spend ledger + rate-limit snapshots + balance tracking\n balance.ts provider balance fetch helpers\n help/\n ask.ts /ask corpus: bundled docs + generated command reference, system prompt, meta-question auto-detect\n agent/\n events.ts AgentEvent — normalized stream the UI consumes\n run.ts real agent loop (AI SDK streamText -> AgentEvent), abort-aware; runCompletion = tool-less grounded answer (used by /ask); returns a structured failure (for failover)\n failover.ts runWithFailover: run a turn over the ranked account pool; on a credential failure before output, advance to the next; clear exhaustion errors\n cli-backend.ts claude/codex CLI subprocess backend (for Pro/Max subscriptions)\n mock.ts scripted demo stream (runs with no API key; used by tests)\n ui/\n theme.ts colors + glyphs (the look)\n input.ts pure key→action reducer for the composer (tested)\n history.ts pure ↑/↓ prompt-history nav (tested)\n net.ts background online probe; status bar shows ⚠ offline when down\n useTerminalSize.ts reactive width on resize (everything reflows)\n git.ts current branch for the status line\n App.tsx the Ink app: state, useInput dispatch, commands, turns\n components/ Banner, Transcript, Composer, CommandPalette, StatusBar, PermissionPrompt, Panel\n panel.ts dismissable command-panel model + pure helpers (clamp/window/filter); tested\ntest/ pure-logic + render tests (ink-testing-library); no keys\nDESIGN.md full product vision (routing, requirements, UX)\nexperiments/ prototypes that validated the architecture\n```\n\nThe composer is custom (Ink `useInput` + `src/ui/input.ts`), not a third-party widget — full control over the cursor, ↑/↓ history, and esc-to-interrupt, with no focus/remount fragility. **Multi-line**: ⌃J (or shift/alt+⏎) inserts a newline, ⏎ submits; ↑/↓ move between lines and fall through to history at the top/bottom line; bracketed paste (enabled in `cli.tsx`) inserts multi-line text literally (CR normalized, paste markers stripped) instead of submitting per line. `caretPos()` is the shared line/col helper. **Readline editing** (all pure in `input.ts`, tested): ⌃U/⌃K kill to line start/end, ⌃W / ⌥⌫ kill word, ⌃D forward-delete, ⌥/⌃ + ←→ word-jump, ⌃A/⌃E line home/end. Keys: ⏎ send · ⌃J newline · ↑↓ line/history · ← → cursor · ⌥←→ word · tab complete @file · **shift+tab cycles mode (normal · auto-accept · plan)** · ⌃Y copy last reply · esc interrupt · ⌃c quit. `/keys` shows the cheatsheet.\n\n**Modes & effort.** Three input modes cycled by shift+tab (`App.tsx` `cycleMode`): **normal** (asks before writes/edits/shell), **auto-accept** (file writes/edits apply without asking — the permission broker auto-resolves `write`/`edit`; shell still gated; diffs still render), **plan** (read-only). Plus **yolo** (auto-approve everything) via `/yolo`. **Effort tiers** (`/effort fast|balanced|max`, or `setEffort`) pin the model through the routing seam (fast→haiku, balanced/max→sonnet) — the active mode + `⚡effort` show as badges in the `StatusBar`. **Click pickers** (fullscreen only): clicking the **model** or **effort** label in the status bar opens a floating picker above it (↑↓ select · ⏎ apply · esc close), reusing the same `/model`/`/effort` command path. The slash commands remain the keyboard path. The fragile row+column hit-test lives in pure, tested `statusBarHit`/`statusBarLayout` (`StatusBar.tsx`); `App.tsx` only supplies live layout (composer line count, `PALETTE_ROWS`, the rendered model/effort/mode) and toggles `quickPicker` state. Inline mode has no mouse grab, so the labels stay informational there. **Copy**: ⌃Y / `/copy` copies the last reply via OSC 52 (`src/ui/clipboard.ts`, works over SSH); `/export [file]` writes the transcript to Markdown. **Terminal integration** (`src/ui/terminal.ts`): the tab title (OSC 2) reflects working/idle, and a long turn (>8s) rings the bell + fires a desktop notification (macOS) so you can step away.\n\n**More UX affordances.** **Type-ahead**: prompts submitted while busy are queued (`queueRef`, shown as chips) and sent when the turn ends. **⌃C** interrupts a turn → clears the composer → \"press again to quit\" (`cli.tsx` renders with `exitOnCtrlC:false`). **Large pastes** collapse to a `[Pasted N lines]` chip (`pasteStoreRef`), expanded back on submit. **Fuzzy** `@file`/`/command` pickers (`src/ui/fuzzy.ts` — substring-first, then subsequence scored by boundary+contiguity; tested). **Cost**: live `$` estimate in the status bar from per-turn model+tokens (`estimateCost` + per-model pricing in `providers.ts`). **Syntax highlighting** for code blocks (`src/ui/highlight.ts` — lightweight per-line tokenizer → Ink spans, NEVER raw ANSI; used by both `lines.ts` `clipSpans` and `Markdown.tsx`). `?` on an empty composer shows the cheatsheet (`KEYS_HELP`).\n\n**Sessions** (`src/session.ts`): conversations persist per-project under `~/.gearbox/sessions/<slug>/` (`GEARBOX_HOME` overrides). Each record holds provider-neutral `messages` + the UI `items` + **per-turn `{model, usage, at}`** (routing/cost data — the record is deliberately not single-model). `gearbox --continue`/`-c` resumes the latest; `/resume [n]` lists/loads in-app; `/clear` starts a fresh session. Prompt history persists across runs (`history.json`). Saving is best-effort (never crashes the app); skipped in demo mode.\n\nFeatures: full markdown via **marked** (parse, `marked.lexer`) + **Ink** (render) in `Markdown.tsx` — headings, bold/italic/inline-code, tables, ordered+nested lists, blockquotes, code blocks. NO foreign ANSI in Ink (cli-highlight/marked-terminal were tried and removed — they corrupt Ink's width/wrapping; render marked's token tree as Ink elements instead). Markdown gets a `width` prop (threaded App→Transcript→Markdown) for table/rule sizing. Colored diffs under edits (`src/diff.ts`, edit/write tools return `{summary,diff}`), plan mode (read-only tools + plan prompt; `/plan` or shift+tab), `!cmd` runs a shell command directly (`src/shell.ts`), `@file` mentions (fuzzy picker `src/ui/mention.ts`+`files.ts`; expanded into the model message on send), live \"working · Ns\" timer.\n\n**Boo (the mascot).** A pixel ghost, now **parametric** (`src/ui/ghost/engine.ts`, ported from a Claude Design handoff). A 20×20 pixel sprite composited from composable layers — body (palette) + face (eyes/mouth) + accessory + persona + a frame-driven overlay (tears/dots/confetti/Z's/sparkle/hearts) — then FOLDED into half-block cells (`▀`/`▄`, top px → `t`/glyph color, bottom px → `b`/bg). `renderGhost(cfg)` is the source of truth for the **default blocks path**; it's pure + memoized. The data: 13 faces (`FACES`), 9 palettes (`PALETTES`), 6 accessories, 9 personas (personas/accessories ported but not yet surfaced in the live UI). Ink `color`/`backgroundColor` props only, NEVER raw ANSI (corrupts Ink's width math). PNG paths are **opt-in** via `GEARBOX_GHOST`:\n\n- `GEARBOX_GHOST=kitty` — real PNG via kitty graphics Unicode placeholders (`U+10EEEE`, fg encodes image id, diacritics encode row/col; PNGs transmitted once in `cli.tsx`). NOTE: the placeholder protocol is young and mis-rendered (squished) in Ghostty during testing — kept opt-in until that's solved.\n- `GEARBOX_GHOST=iterm` — OSC 1337 splash banner (iTerm2/WezTerm).\n\n`detectImageMode()` returns `blocks` unless `GEARBOX_GHOST` opts in. Baked PNGs live in `src/ui/mascot-png.ts`; `bun run scripts/ghost-preview.ts` previews the parametric engine (splash + all faces + the in-flow state crops). **Boo is animated but deliberately calm** on the blocks path (`AnimatedGhost` in `Mascot.tsx`): one shared, unhurried 240ms tick (leaf-local `useTick`, never lifted to App root); talk + overlays advance at half that (~480ms). There is NO idle bob/float and NO splash sparkle — motion is a quiet sign of life, not fidgeting (the splash just blinks every ~6s; in-flow only the state-meaningful overlay/talk moves). `GEARBOX_NO_MOTION=1` freezes to frame 0. `/ghost [mood]` cycles the skin (`skinToCfg` maps it to a cfg; `shades` is the cool face + shades accessory).\n\n**Layout: fullscreen by default; inline is opt-in.** **Fullscreen is the default** (alt-screen frame + virtualized scroll region + scrollbar + mouse wheel scroll); `--inline`, `GEARBOX_INLINE=1`, or `/config inline on` (pref `fullscreen: false`) opts into inline mode. `GEARBOX_FULLSCREEN=1` or `--fullscreen` forces fullscreen explicitly. The decision lives in `cli.tsx` (`wantsFullscreen`). Grabbing the mouse for wheel-scroll is exactly what disables native terminal selection, so in fullscreen mode text selection requires the terminal's modifier (e.g. Option-drag in Ghostty). **Inline mode** (the plain `Transcript` component): no alt-screen, no mouse grab — native click-drag selection / scrollback / copy all work with no modifier. The transcript is a **virtualized line buffer**: `src/ui/lines.ts` (`itemsToLines`) flattens items into styled `Line`s (markdown→lines, wrapping, diffs) — INVARIANT: every line ≤ width (tested), so nothing overflows. **Streaming perf**: flattening the markdown-heavy `assistant`/`user` items is super-linear with their length, so `staticItemLines` memoizes per item in a `WeakMap` keyed by object reference (unchanged items keep identity across renders, so only the changing tail re-parses — history is free; running tools are not cached since their spinner animates). On the producer side, assistant **text deltas are coalesced** on a ~45ms flush timer in `App.tsx`'s `onEvent` (mirroring the tool-stream coalescer), so streaming re-renders at ~22fps instead of per-token — both together stop the auto-scroll jitter that grew with reply length. `finishAssistant`/the turn `finally` flush any buffered text before marking done or on interrupt. In fullscreen, `App` renders only the visible window via `Viewport` (`src/ui/components/Viewport.tsx`) at a computed `transcriptHeight = rows − header − footer` (footer over-estimated so the frame never exceeds the screen; alt-screen clips, so under-filling is safe). Fullscreen scroll: mouse wheel (SGR mouse reporting enabled in `cli.tsx`; parsed off raw stdin in `App` since Ink doesn't model mouse — buttons 64/65) and PgUp/PgDn; new output re-pins to the bottom (`atBottomRef`); a scrollbar sits on the right. (In fullscreen, mouse reporting means text selection needs the terminal's modifier, e.g. Option-drag in Ghostty — which is why inline is now the default.) The virtualized buffer replaced an earlier flex/overflow fullscreen that corrupted on tall output. Chrome spans full width; prose wraps ≤100 cols. The plain `Transcript` component is the inline-fallback renderer. `scripts/gen-mascot.ts` still bakes the PNGs + baked sprites (`mascot-sprite.ts` `GHOSTS`) — but those now feed **only the opt-in kitty/iTerm image path** (`image.ts`); the default blocks path renders the parametric engine instead. The splash scales to the terminal (big=2×/mini=1×/none by rows×cols, in `App.tsx`). The inline/working presence is the compact **state ghost** (see below) — a native-resolution head crop so Boo never dominates the transcript.\n\nCommands are grouped in `/help` (models · conversation · accounts · save · modes · settings · other) and `src/commands.ts` carries plain-language descriptions: /model [name] (fuzzy — \"haiku\"; `/model auto` routes, `/model all` lists every provider) /effort [fast|balanced|max] /prefer [kind model] (remember a confirmed routing preference for a task type) /clear /resume /retry /compact /context /memory /ask &lt;q&gt; (answer questions about Gearbox itself from its bundled docs via a cheap routed model; plain meta-questions auto-route here with a visible affordance) /account (unified: list/add/login/use/rm/refresh — `/accounts` and `/login` are hidden aliases; `/account refresh` re-discovers each account's real callable models) /cost (fullscreen: TOGGLES a persistent usage strip above the composer — context % · subscription 5h/7d headroom · session spend — stays until toggled off, persisted via `prefs.statusPinned`, doesn't capture input; `StatusStrip.tsx`. inline: one-shot card) /copy /export [file] /plan /yolo /theme /config (theme·vim·notify·inline; `/vim` is a hidden alias) /init /keys /help /exit. **Hidden** (work but not listed): /accounts /login /vim /ghost. **Removed:** /cwd (the working dir now shows in `/context`). `formatModelList` shows usable models first and collapses no-key providers to a one-line count.\n\n**Command panel (fullscreen only).** Big info-dump commands open a dismissable, Esc-closable overlay instead of dumping into the transcript (`Panel.tsx` + pure `panel.ts`, wired in `App.tsx`): `/help` `/keys` `/context` `/cost` `/memory` are scrollable static dumps (reuse `itemsToLines` + `Viewport`); `/account` and `/model` are interactive lists (↑↓ select · ⏎ acts — they just dispatch the equivalent `/account <n>` / `/model <id>` command and close), and `/model` has type-to-filter (127 Foundry models). The panel replaces the transcript Viewport region while open and takes precedence over `welcome`; the key handler is a branch in `useInput` placed after ⌃C so Esc closes the panel rather than interrupting a turn. Short confirmations (`model → haiku`, `remembered`, `✓ added`, errors) stay inline. Inline mode keeps the old inline printing (no alt-screen to overlay). `openInfoPanel` returns false inline so callers fall back to `push`.\n\n**Accounts: reliability by design.** Every subscription, API key, and cloud credential is meant to work all the time. Switching is **by name only** (a stable unique `slug` per account, e.g. `claude-work`; positional numbers are gone — removing an account never repoints another). Each account carries a cached **health** state (`✓ ready · ⚠ expired · ✗ invalid · ⏳ limited · — unknown`) refreshed at natural touchpoints (boot sweep, opening `/account`, on switch, on live failure) — never by background polling; probes are timeout-bounded. A turn runs through a **failover pool**: `resolve.rank(model)` returns every account that can serve the model's family (cross-provider — Claude can fall Anthropic key → Bedrock → Vertex), health-sorted; `agent/failover.ts` tries them best-first and, on a credential-class failure **before any output** (expired/invalid/no-credit/rate-limited), transparently advances to the next and tells the user which account ran. A real (network/model) error or any failure after output streamed does NOT churn the pool. When the pool is exhausted, one consolidated error names each account, why it failed, and the one command to fix it. Expired subscriptions get one-step re-login: `/account login <name>` (and CLI failures name that exact command). `/account add <paste>` runs the credential **sniffer** (`accounts/sniff.ts`) — paste an API key, an AWS access key or credentials block, a Vertex service-account JSON, an Azure endpoint, or a Vercel gateway key, and it identifies the provider, fills the gaps interactively, and live-tests it.\n\n**Permission gate:** `write_file`/`edit_file`/`run_shell` block on a confirm before mutating. Broker: `src/permission.ts` (`requestPermission` in the tools; `setPermissionHandler` installed by `App`; no handler → allow, so tests/headless are unchanged). Decisions: **once** (1), **always** (2, grants that kind for the session), **all/yolo** (a, auto-approves everything until toggled), **deny** (3/esc). YOLO is also toggled by `/yolo` or started with `--yolo`; a `⚡ yolo` badge shows in the status. The `!` prefix is user-initiated so it is NOT gated. Search/nav tools: `search` (ripgrep, Bun-walk fallback) and `glob` (`Bun.Glob`), both read-only (also in plan mode). The working indicator IS Boo now (`components/Working.tsx`): a compact head-crop ghost whose face follows the agent state — thinking (dots) → streaming (talk) → tool (loading dots) → a clean-finish celebrate (party hat + confetti) → error (crying with falling tears). `App.tsx` derives `mascotState` from the `onEvent` stream; the success/error beat **lingers ~1.5s** after the turn (`linger` state — the working line gates on `busy || linger`, since it would otherwise unmount the instant `busy` goes false). Crops are per-state (`stateView`): head (rows 4–14), head+dots (2–14), head+hat (0–14) so overlays outside the head still read. This deliberately supersedes the earlier \"Boo stays on the welcome splash only / in-flow movement reads as noise\" decision — the compact, state-bearing ghost is the point of the design port.\n\n## Conventions\n\n- Runtime: **Bun**. TypeScript + TSX. Run with `bun run src/cli.tsx`.\n- UI: **Ink** (React for terminals) + **@inkjs/ui**. Keep it calm and beautiful: restrained palette (one accent), generous spacing, consistent glyphs. The look lives in `src/ui/theme.ts` — change colors/glyphs there, not inline.\n- Open + free: MIT, no paid dependencies, no hosted backend, no telemetry. The only cost is the user's own model calls on their own keys.\n- Tools must be safe by default: confirm or sandbox anything destructive; never `rm -rf` or write outside the workspace without intent.\n\n## Run it\n\n```bash\nbun install\n# set at least one key:\nexport ANTHROPIC_API_KEY=... # or OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY / DEEPSEEK_API_KEY\nbun run src/cli.tsx # or: bun start\n```\n\nWith no key it launches in demo mode (a scripted transcript) so the UI still runs.\n\n## Test\n\n```bash\nbun test # render tests + agent-loop tests; no API key needed\nbun run typecheck # tsc --noEmit\n```"
145522
145653
  },
145523
145654
  {
145524
145655
  file: "DESIGN.md",
@@ -146251,6 +146382,21 @@ async function runVerification(commands, opts) {
146251
146382
  }
146252
146383
  return results;
146253
146384
  }
146385
+ function nextStepFor(failures, changedFiles) {
146386
+ if (!failures.length)
146387
+ return changedFiles.length ? "commit changes" : "/context";
146388
+ const joined = failures.join(`
146389
+ `);
146390
+ if (/\bTS1185\b|merge conflict|conflict marker|<<<<<<<|>>>>>>>/i.test(joined)) {
146391
+ const m2 = joined.match(/([\w./-]+\.(?:ts|tsx|js|jsx|mjs|cjs))[(:]/);
146392
+ return m2 ? `resolve the conflict in ${m2[1]}` : "resolve the merge conflict";
146393
+ }
146394
+ const errFiles = [...joined.matchAll(/([\w./-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs))[(:]/g)].map((x2) => x2[1]);
146395
+ if (errFiles.length && errFiles.every((f3) => !changedFiles.some((c) => c === f3 || c.endsWith(f3) || f3.endsWith(c)))) {
146396
+ return "likely predates your change — check the repo state";
146397
+ }
146398
+ return "/retry";
146399
+ }
146254
146400
 
146255
146401
  // src/init.ts
146256
146402
  function readJson2(path) {
@@ -146626,7 +146772,7 @@ function gitBranch() {
146626
146772
 
146627
146773
  // src/ui/App.tsx
146628
146774
  init_proc();
146629
- var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
146775
+ var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
146630
146776
  import { basename as basename3, extname, resolve as resolve12 } from "node:path";
146631
146777
  import { existsSync as existsSync11, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
146632
146778
  import { writeFile as fsWriteFile } from "node:fs/promises";
@@ -146651,27 +146797,27 @@ function transcriptMarkdown(items) {
146651
146797
  else if (it.kind === "assistant")
146652
146798
  out.push("## Gearbox", "", it.text, "");
146653
146799
  else if (it.kind === "tool")
146654
- out.push(`> \`${it.name}\` ${it.arg}${it.summary ? " " + it.summary : ""}`, "");
146800
+ out.push(`> \`${it.name}\` ${it.arg}${it.summary ? " · " + it.summary : ""}`, "");
146655
146801
  else if (it.kind === "notice")
146656
146802
  out.push(`_${it.text}_`, "");
146657
146803
  else if (it.kind === "accounts") {
146658
146804
  out.push("**accounts**", "", `current: ${it.view.current}`);
146659
146805
  for (const r2 of it.view.rows)
146660
- out.push(`- ${r2.name} (${r2.type}) ${r2.status} /account ${r2.alias}`);
146806
+ out.push(`- ${r2.name} (${r2.type}) · ${r2.status} · /account ${r2.alias}`);
146661
146807
  out.push("");
146662
146808
  } else if (it.kind === "usage") {
146663
146809
  out.push("**usage · spend & limits**", "");
146664
146810
  for (const a of it.view.subscriptions) {
146665
146811
  const limits = (a.limits ?? []).map((l) => `${l.label} ${l.pct}%`).join(" · ");
146666
- out.push(`- ${a.name} (subscription) ${a.turns} turns${limits ? ` · ${limits}` : ""}`);
146812
+ out.push(`- ${a.name} (subscription) · ${a.turns} turns${limits ? ` · ${limits}` : ""}`);
146667
146813
  }
146668
146814
  for (const a of it.view.apiKeys)
146669
- out.push(`- ${a.name} (API key) ${a.spend} · ${a.turns} turns · ${a.tok}`);
146815
+ out.push(`- ${a.name} (API key) · ${a.spend} · ${a.turns} turns · ${a.tok}`);
146670
146816
  out.push(`- total API spend ${it.view.totalApiSpend}`, "");
146671
146817
  } else if (it.kind === "context") {
146672
146818
  out.push("**context · what's loaded**", "");
146673
146819
  for (const r2 of it.view.rows)
146674
- out.push(`- ${r2.label.trim()} ${r2.display.trim()}`);
146820
+ out.push(`- ${r2.label.trim()} · ${r2.display.trim()}`);
146675
146821
  out.push(`- total ${it.view.total.trim()}${it.view.windowPct != null ? ` (${it.view.windowPct}% of ${it.view.windowLabel})` : ""}`, "");
146676
146822
  } else if (it.kind === "error")
146677
146823
  out.push(`**error:** ${it.text}`, "");
@@ -146681,7 +146827,7 @@ function transcriptMarkdown(items) {
146681
146827
  }
146682
146828
  function friendlyError(msg) {
146683
146829
  if (isNetworkError(msg))
146684
- return `can't reach the provider you appear to be offline. Check your connection, then /retry.`;
146830
+ return `can't reach the provider · you appear to be offline. Check your connection, then /retry.`;
146685
146831
  return msg;
146686
146832
  }
146687
146833
  function firstPath(text2) {
@@ -146776,16 +146922,16 @@ function ActivityRail({ items, width }) {
146776
146922
  const toolText = tools2.map((t2) => `${t2.status === "running" ? spin2 : t2.status === "err" ? "!" : "✓"} ${t2.name.replace(/_file$/, "").replace("run_shell", "shell")}`).join(" · ");
146777
146923
  const checkText = checks4.map((c) => `${c.ok ? "✓" : "!"} ${c.command}`).join(" · ");
146778
146924
  const line = [model ? model.model : null, phase ? phase.label : null, toolText || null, checkText || null].filter(Boolean).join(" · ");
146779
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146925
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146780
146926
  paddingX: 1,
146781
146927
  marginTop: 1,
146782
146928
  width,
146783
146929
  children: [
146784
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146930
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146785
146931
  color: color.accentDim,
146786
146932
  children: "activity "
146787
146933
  }, undefined, false, undefined, this),
146788
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146934
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146789
146935
  color: color.faint,
146790
146936
  children: line.slice(0, Math.max(width - 10, 20))
146791
146937
  }, undefined, false, undefined, this)
@@ -146795,31 +146941,31 @@ function ActivityRail({ items, width }) {
146795
146941
  function SetupSplash({ state, width, skin, splashSize }) {
146796
146942
  const detected = state.importable.length + state.cloudImportable.length;
146797
146943
  const panelWidth = Math.min(Math.max(width - 4, 30), 58);
146798
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146944
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146799
146945
  flexDirection: "column",
146800
146946
  alignItems: "center",
146801
146947
  children: [
146802
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
146948
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(MascotSplash, {
146803
146949
  skin,
146804
146950
  size: splashSize
146805
146951
  }, undefined, false, undefined, this),
146806
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146952
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146807
146953
  marginTop: 1,
146808
146954
  flexDirection: "column",
146809
146955
  alignItems: "center",
146810
146956
  children: [
146811
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146957
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146812
146958
  color: color.accent,
146813
146959
  bold: true,
146814
146960
  children: "gearbox"
146815
146961
  }, undefined, false, undefined, this),
146816
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146962
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146817
146963
  color: color.dim,
146818
146964
  children: "one terminal · every model you already pay for"
146819
146965
  }, undefined, false, undefined, this)
146820
146966
  ]
146821
146967
  }, undefined, true, undefined, this),
146822
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146968
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146823
146969
  marginTop: 2,
146824
146970
  width: panelWidth,
146825
146971
  borderStyle: "round",
@@ -146828,18 +146974,18 @@ function SetupSplash({ state, width, skin, splashSize }) {
146828
146974
  paddingY: 1,
146829
146975
  flexDirection: "column",
146830
146976
  children: [
146831
- detected > 0 ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
146977
+ detected > 0 ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
146832
146978
  children: [
146833
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146979
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146834
146980
  children: [
146835
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146981
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146836
146982
  color: color.ok,
146837
146983
  children: [
146838
146984
  glyph.on,
146839
146985
  " "
146840
146986
  ]
146841
146987
  }, undefined, true, undefined, this),
146842
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
146988
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146843
146989
  color: color.text,
146844
146990
  children: [
146845
146991
  detected,
@@ -146850,75 +146996,75 @@ function SetupSplash({ state, width, skin, splashSize }) {
146850
146996
  }, undefined, true, undefined, this)
146851
146997
  ]
146852
146998
  }, undefined, true, undefined, this),
146853
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
146999
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146854
147000
  marginTop: 1,
146855
147001
  children: [
146856
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147002
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146857
147003
  color: color.accent,
146858
147004
  children: "/account import"
146859
147005
  }, undefined, false, undefined, this),
146860
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147006
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146861
147007
  color: color.dim,
146862
147008
  children: " connect automatically"
146863
147009
  }, undefined, false, undefined, this)
146864
147010
  ]
146865
147011
  }, undefined, true, undefined, this),
146866
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147012
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146867
147013
  marginTop: 1,
146868
147014
  children: [
146869
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147015
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146870
147016
  color: color.faint,
146871
147017
  children: "or add a different key: "
146872
147018
  }, undefined, false, undefined, this),
146873
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147019
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146874
147020
  color: color.accent,
146875
147021
  children: "/account add <api-key>"
146876
147022
  }, undefined, false, undefined, this)
146877
147023
  ]
146878
147024
  }, undefined, true, undefined, this)
146879
147025
  ]
146880
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
147026
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
146881
147027
  children: [
146882
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147028
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146883
147029
  color: color.dim,
146884
147030
  children: "paste or type a key to get started"
146885
147031
  }, undefined, false, undefined, this),
146886
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147032
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146887
147033
  marginTop: 1,
146888
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147034
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146889
147035
  color: color.accent,
146890
147036
  children: "/account add <api-key>"
146891
147037
  }, undefined, false, undefined, this)
146892
147038
  }, undefined, false, undefined, this)
146893
147039
  ]
146894
147040
  }, undefined, true, undefined, this),
146895
- (state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147041
+ (state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146896
147042
  marginTop: 2,
146897
147043
  flexDirection: "column",
146898
147044
  children: [
146899
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147045
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146900
147046
  color: color.faint,
146901
147047
  children: "subscriptions detected"
146902
147048
  }, undefined, false, undefined, this),
146903
- state.hasClaudeCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147049
+ state.hasClaudeCli && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146904
147050
  children: [
146905
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147051
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146906
147052
  color: color.accent,
146907
147053
  children: "/account add claude"
146908
147054
  }, undefined, false, undefined, this),
146909
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147055
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146910
147056
  color: color.faint,
146911
147057
  children: " Claude Pro / Max"
146912
147058
  }, undefined, false, undefined, this)
146913
147059
  ]
146914
147060
  }, undefined, true, undefined, this),
146915
- state.hasCodexCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147061
+ state.hasCodexCli && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146916
147062
  children: [
146917
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147063
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146918
147064
  color: color.accent,
146919
147065
  children: "/account add codex"
146920
147066
  }, undefined, false, undefined, this),
146921
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147067
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146922
147068
  color: color.faint,
146923
147069
  children: " ChatGPT Plus"
146924
147070
  }, undefined, false, undefined, this)
@@ -146928,9 +147074,9 @@ function SetupSplash({ state, width, skin, splashSize }) {
146928
147074
  }, undefined, true, undefined, this)
146929
147075
  ]
146930
147076
  }, undefined, true, undefined, this),
146931
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
147077
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
146932
147078
  marginTop: 1,
146933
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
147079
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
146934
147080
  color: color.faint,
146935
147081
  children: "/onboard · /account · /help"
146936
147082
  }, undefined, false, undefined, this)
@@ -147015,6 +147161,25 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147015
147161
  };
147016
147162
  const panelMaxScrollRef = import_react26.useRef(0);
147017
147163
  const panelAccountSlugsRef = import_react26.useRef([]);
147164
+ const [statusPinned, setStatusPinnedState] = import_react26.useState(() => Boolean(loadPrefs().statusPinned));
147165
+ const setStatusPinned = (v) => {
147166
+ setStatusPinnedState(v);
147167
+ updatePrefs({ statusPinned: v });
147168
+ };
147169
+ const currentUsageView = () => {
147170
+ const accounts = listAccounts();
147171
+ const resolve13 = (id) => {
147172
+ const a = getAccount(id);
147173
+ if (a) {
147174
+ const bin = a.auth.kind === "cli" ? a.auth.binary : undefined;
147175
+ return { name: accountName(a), kind: a.exec === "cli" ? "sub" : "api", balanceExposed: a.exec !== "cli" && balanceExposed(a.provider), limitNote: a.exec === "cli" ? `${bin === "codex" ? "Codex" : "Claude"} CLI hasn't reported quota windows yet` : undefined };
147176
+ }
147177
+ if (id === "unknown")
147178
+ return { name: "(unattributed)", kind: "api" };
147179
+ return { name: id, kind: "api" };
147180
+ };
147181
+ return buildUsageView(estimateCost(sessionRef.current.turns), resolve13, Date.now(), accounts.map((a) => a.id));
147182
+ };
147018
147183
  const buildPanelModelRows = (cur) => modelRegistry().filter((m2) => providerAvailable(m2.provider)).map((m2) => ({ id: m2.id, label: m2.label, provider: m2.provider, current: m2.id === cur }));
147019
147184
  const openInfoPanel = (title, item) => {
147020
147185
  if (!fullscreen)
@@ -147152,7 +147317,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147152
147317
  } catch {}
147153
147318
  }
147154
147319
  if (learned)
147155
- notice(`loaded the real model list for ${learned} account${learned === 1 ? "" : "s"} /model to see them`);
147320
+ notice(`loaded the real model list for ${learned} account${learned === 1 ? "" : "s"} · /model to see them`);
147156
147321
  })();
147157
147322
  }, []);
147158
147323
  import_react26.useEffect(() => {
@@ -147622,7 +147787,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147622
147787
  rows2.push(" no named subscription models exposed yet");
147623
147788
  for (const m2 of models)
147624
147789
  rows2.push(` ${m2.id === currentId ? glyph.on : glyph.off} ${m2.label}`);
147625
- rows2.push("", `API models are hidden while a subscription is active /account off returns to API routing
147790
+ rows2.push("", `API models are hidden while a subscription is active · /account off returns to API routing
147626
147791
  tip: /model haiku (or any API model name) switches directly and leaves the subscription`);
147627
147792
  return rows2.join(`
147628
147793
  `);
@@ -147635,7 +147800,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147635
147800
  const exact = matches2.find((m3) => m3.label.toLowerCase() === q || m3.id.toLowerCase() === q);
147636
147801
  const m2 = exact ?? (matches2.length === 1 ? matches2[0] : undefined);
147637
147802
  if (!m2)
147638
- return { ok: false, message: `"${query}" matches ${matches2.map((x2) => x2.label).join(", ")} be more specific` };
147803
+ return { ok: false, message: `"${query}" matches ${matches2.map((x2) => x2.label).join(", ")} · be more specific` };
147639
147804
  return { ok: true, modelId: m2.id, label: m2.label };
147640
147805
  };
147641
147806
  const setActiveCliModelId = (modelId) => {
@@ -147757,12 +147922,12 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147757
147922
  if (exact.length === 1)
147758
147923
  return { account: exact[0].a };
147759
147924
  if (exact.length > 1)
147760
- return { error: `"${query}" matches ${exact.map(({ a }) => accountName(a)).join(", ")} use the full alias` };
147925
+ return { error: `"${query}" matches ${exact.map(({ a }) => accountName(a)).join(", ")} · use the full alias` };
147761
147926
  const fuzzy = accounts.map((a) => ({ a, aliases: [...accountAliases(a)] })).filter(({ aliases }) => aliases.some((x2) => x2.includes(q)));
147762
147927
  if (fuzzy.length === 1)
147763
147928
  return { account: fuzzy[0].a };
147764
147929
  if (fuzzy.length > 1)
147765
- return { error: `"${query}" matches ${fuzzy.map(({ a }) => accountName(a)).join(", ")} use the full alias` };
147930
+ return { error: `"${query}" matches ${fuzzy.map(({ a }) => accountName(a)).join(", ")} · use the full alias` };
147766
147931
  return { error: `no account matching "${query}"` };
147767
147932
  };
147768
147933
  const buildAccountView = (accounts, activeCliId, importable, statuses) => {
@@ -147823,7 +147988,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
147823
147988
  const _cliEffortRaw = normalizeEffort(effortRef.current, efforts);
147824
147989
  if (_cliEffortRaw === null && effortRef.current !== "medium") {
147825
147990
  const { level: nearest } = clampEffort(effortRef.current, efforts);
147826
- const hint = efforts.length ? ` try /effort ${nearest}` : "";
147991
+ const hint = efforts.length ? ` · try /effort ${nearest}` : "";
147827
147992
  throw new Error(`effort "${effortRef.current}" is not supported by ${label ?? binary} (supports: ${efforts.join(", ") || "none"}${hint})`);
147828
147993
  }
147829
147994
  const cliEffort = _cliEffortRaw ?? undefined;
@@ -147875,12 +148040,12 @@ ${map4}
147875
148040
  if (isAsk) {
147876
148041
  const docs = loadGearboxDocs();
147877
148042
  if (!docs) {
147878
- onEvent({ type: "error", message: "Gearbox docs aren't bundled with this install can't answer from them." });
148043
+ onEvent({ type: "error", message: "Gearbox docs aren't bundled with this install · can't answer from them." });
147879
148044
  return { messages, usage: { inputTokens: 0, outputTokens: 0 } };
147880
148045
  }
147881
148046
  const choice3 = new RoutingSelector().select({ prompt, kind: "search" });
147882
148047
  if (choice3.backend?.kind === "cli") {
147883
- onEvent({ type: "error", message: "/ask needs an API-key account it can't run on a subscription. Add one with /account add <key>." });
148048
+ onEvent({ type: "error", message: "/ask needs an API-key account · it can't run on a subscription. Add one with /account add <key>." });
147884
148049
  return { messages, usage: { inputTokens: 0, outputTokens: 0 } };
147885
148050
  }
147886
148051
  routedRef.current = { model: choice3.model, reason: choice3.reason };
@@ -147981,7 +148146,7 @@ ${map4}
147981
148146
  if (_effortRaw === null && effortRef.current !== "medium") {
147982
148147
  const supported = effortLevels(choice3.model);
147983
148148
  const { level: nearest } = clampEffort(effortRef.current, supported);
147984
- const hint = supported.length ? ` try /effort ${nearest}` : "";
148149
+ const hint = supported.length ? ` · try /effort ${nearest}` : "";
147985
148150
  throw new Error(`effort "${effortRef.current}" is not supported by ${choice3.model.label} (supports: ${supported.join(", ") || "none"}${hint})`);
147986
148151
  }
147987
148152
  const r2 = await runTask({ model: choice3.model, messages: ctx, onEvent, signal, plan, system, creds, effort: _effortRaw ?? undefined, deferTerminal: true });
@@ -148042,9 +148207,9 @@ ${map4}
148042
148207
  return `compacted ${res.summarizedTurns} earlier turn${res.summarizedTurns > 1 ? "s" : ""} · ~${savedStr} tokens freed`;
148043
148208
  }, []);
148044
148209
  const MODE_NOTE = {
148045
- normal: "normal mode I'll ask before writes, edits, and shell",
148046
- "auto-accept": "auto-accept edits file writes/edits apply without asking (shell still gated)",
148047
- plan: "plan mode read-only; I'll propose a plan before changing anything"
148210
+ normal: "normal mode · I'll ask before writes, edits, and shell",
148211
+ "auto-accept": "auto-accept edits · file writes/edits apply without asking (shell still gated)",
148212
+ plan: "plan mode · read-only; I'll propose a plan before changing anything"
148048
148213
  };
148049
148214
  const setModeTo = (next) => {
148050
148215
  modeRef.current = next;
@@ -148076,8 +148241,8 @@ ${map4}
148076
148241
  effortRef.current = level;
148077
148242
  setEffortState(level);
148078
148243
  if (!allowed.length)
148079
- return ` effort reset to ${level} (no reasoning support)`;
148080
- return ` effort clamped: ${prev} → ${level} (${prev} not supported)`;
148244
+ return ` · effort reset to ${level} (no reasoning support)`;
148245
+ return ` · effort clamped: ${prev} → ${level} (${prev} not supported)`;
148081
148246
  };
148082
148247
  const setEffort = (raw) => {
148083
148248
  const target = effortTarget();
@@ -148092,7 +148257,7 @@ ${map4}
148092
148257
  }
148093
148258
  effortRef.current = level;
148094
148259
  setEffortState(level);
148095
- notice(`effort: ${level} ${target.label}`);
148260
+ notice(`effort: ${level} · ${target.label}`);
148096
148261
  };
148097
148262
  const runInteractive = (cmd, cmdArgs, env3) => {
148098
148263
  try {
@@ -148103,7 +148268,7 @@ ${map4}
148103
148268
  process.stdout.write("\x1B[?1049l");
148104
148269
  process.stdout.write("\x1B[?2004l\x1B[?25h");
148105
148270
  process.stdout.write(`
148106
- → running \`${cmd} ${cmdArgs.join(" ")}\` follow the prompts…
148271
+ → running \`${cmd} ${cmdArgs.join(" ")}\` · follow the prompts…
148107
148272
 
148108
148273
  `);
148109
148274
  const r2 = nodeSpawnSync2(cmd, cmdArgs, { stdio: ["inherit", "inherit", "inherit"], ...env3 ? { env: env3 } : {} });
@@ -148406,7 +148571,7 @@ ${fetched.join(`
148406
148571
  const changed = uniq2([...changedFiles]);
148407
148572
  const doneChecks = uniq2(checks4);
148408
148573
  const failed = uniq2(failures).slice(0, 4);
148409
- const next = failed.length ? "/retry" : changed.length && !doneChecks.length ? "run tests" : changed.length ? "commit changes" : "/context";
148574
+ const next = failed.length ? nextStepFor(failed, changed) : changed.length && !doneChecks.length ? "run tests" : changed.length ? "commit changes" : "/context";
148410
148575
  if (changed.length || doneChecks.length || failed.length) {
148411
148576
  push({ kind: "summary", id: idRef.current++, changed, checks: doneChecks, failures: failed, next });
148412
148577
  }
@@ -148526,7 +148691,7 @@ ${fetched.join(`
148526
148691
  return;
148527
148692
  }
148528
148693
  if (a && a.exec !== "cli") {
148529
- notice(`${accountName(a)} is an API-key account nothing to re-login. Use /account ${accountSlug(a)} to switch to it, or /account add ${a.provider} <key> to replace the key.`);
148694
+ notice(`${accountName(a)} is an API-key account · nothing to re-login. Use /account ${accountSlug(a)} to switch to it, or /account add ${a.provider} <key> to replace the key.`);
148530
148695
  return;
148531
148696
  }
148532
148697
  signInCli(arg2);
@@ -148560,13 +148725,13 @@ ${fetched.join(`
148560
148725
  }
148561
148726
  const rows2 = sessions.slice(0, 10).map((s2, i2) => ` ${i2 + 1}. ${new Date(s2.updatedAt).toLocaleString()} · ${s2.title || "(untitled)"} (${s2.items.length} msgs)`).join(`
148562
148727
  `);
148563
- notice(`resume a session /resume <n>:
148728
+ notice(`resume a session · /resume <n>:
148564
148729
  ` + rows2);
148565
148730
  return;
148566
148731
  }
148567
148732
  const pick3 = (resumeListRef.current.length ? resumeListRef.current : sessions)[parseInt(arg, 10) - 1];
148568
148733
  if (!pick3) {
148569
- notice(`no session ${arg} /resume to list`);
148734
+ notice(`no session ${arg} · /resume to list`);
148570
148735
  return;
148571
148736
  }
148572
148737
  loadInto(pick3);
@@ -148590,7 +148755,7 @@ ${fetched.join(`
148590
148755
  setEffort(arg);
148591
148756
  } else {
148592
148757
  const target = effortTarget();
148593
- notice(target?.efforts.length ? `effort: ${effortRef.current} ${target.label} supports ${target.efforts.join(", ")}` : "the active model does not expose reasoning efforts");
148758
+ notice(target?.efforts.length ? `effort: ${effortRef.current} · ${target.label} supports ${target.efforts.join(", ")}` : "the active model does not expose reasoning efforts");
148594
148759
  }
148595
148760
  return;
148596
148761
  }
@@ -148627,7 +148792,7 @@ ${fetched.join(`
148627
148792
  const on = vimRef.current === "off";
148628
148793
  setVim(on ? "insert" : "off");
148629
148794
  updatePrefs({ vim: on });
148630
- notice(on ? "vim mode on esc for normal, i to insert" : "vim mode off");
148795
+ notice(on ? "vim mode on · esc for normal, i to insert" : "vim mode off");
148631
148796
  return;
148632
148797
  }
148633
148798
  case "config": {
@@ -148646,10 +148811,10 @@ ${fetched.join(`
148646
148811
  if (key === "vim") {
148647
148812
  setVim(on ? "insert" : "off");
148648
148813
  updatePrefs({ vim: on });
148649
- notice(on ? "vim mode on esc for normal, i to insert" : "vim mode off");
148814
+ notice(on ? "vim mode on · esc for normal, i to insert" : "vim mode off");
148650
148815
  } else if (key === "inline") {
148651
148816
  updatePrefs({ fullscreen: !on });
148652
- notice(`inline mode ${on ? "on" : "off"} restart gearbox to apply`);
148817
+ notice(`inline mode ${on ? "on" : "off"} · restart gearbox to apply`);
148653
148818
  } else if (key === "notify") {
148654
148819
  notifyRef.current = on;
148655
148820
  updatePrefs({ notify: on });
@@ -148664,7 +148829,7 @@ ${fetched.join(`
148664
148829
  const next = !isYolo();
148665
148830
  setYolo(next);
148666
148831
  setYoloState(next);
148667
- notice(next ? "yolo mode ON all file writes and shell commands run without asking" : "yolo mode off back to asking before writes/edits/shell");
148832
+ notice(next ? "yolo mode ON · all file writes and shell commands run without asking" : "yolo mode off · back to asking before writes/edits/shell");
148668
148833
  return;
148669
148834
  }
148670
148835
  case "ghost": {
@@ -148673,7 +148838,7 @@ ${fetched.join(`
148673
148838
  if (arg) {
148674
148839
  const found = SKINS.find((s2) => s2 === arg.toLowerCase());
148675
148840
  if (!found) {
148676
- notice(`unknown mood: ${arg} try ${SKINS.join(", ")}`);
148841
+ notice(`unknown mood: ${arg} · try ${SKINS.join(", ")}`);
148677
148842
  return;
148678
148843
  }
148679
148844
  next = found;
@@ -148717,7 +148882,7 @@ ${fetched.join(`
148717
148882
  if (!arg || arg.toLowerCase() === "all") {
148718
148883
  const routing2 = selectorRef.current instanceof RoutingSelector;
148719
148884
  const activeSub = activeCliRef.current;
148720
- const mode3 = activeSub ? `now: ${activeSub.binary} subscription${activeCliModelRef.current ? ` · ${cliModelLabel(activeCliModelRef.current)}` : ""} /account off for API routing` : routing2 ? "now: routing on Gearbox picks per task" : `now: pinned to ${currentId ?? "one model"} /model auto to route`;
148885
+ const mode3 = activeSub ? `now: ${activeSub.binary} subscription${activeCliModelRef.current ? ` · ${cliModelLabel(activeCliModelRef.current)}` : ""} · /account off for API routing` : routing2 ? "now: routing on · Gearbox picks per task" : `now: pinned to ${currentId ?? "one model"} · /model auto to route`;
148721
148886
  const list2 = activeSub ? formatCliModelList(activeSub.binary, activeCliModelRef.current ?? null) : formatModelList(currentId, arg.toLowerCase() === "all");
148722
148887
  notice(list2 + `
148723
148888
 
@@ -148727,7 +148892,7 @@ ${fetched.join(`
148727
148892
  if (arg.toLowerCase() === "auto" || arg.toLowerCase() === "route") {
148728
148893
  if (activeCliRef.current) {
148729
148894
  setActiveCliModelId(undefined);
148730
- notice(`subscription model cleared ${activeCliRef.current.binary} will use its default. /account off for API routing`);
148895
+ notice(`subscription model cleared · ${activeCliRef.current.binary} will use its default. /account off for API routing`);
148731
148896
  return;
148732
148897
  }
148733
148898
  const left = leaveSubscription();
@@ -148735,7 +148900,7 @@ ${fetched.join(`
148735
148900
  setLastPick(null);
148736
148901
  routedRef.current = null;
148737
148902
  updatePrefs({ pinnedModel: undefined });
148738
- notice("routing on Gearbox now picks the model per task (the cheapest that can do the job)" + left);
148903
+ notice("routing on · Gearbox now picks the model per task (the cheapest that can do the job)" + left);
148739
148904
  return;
148740
148905
  }
148741
148906
  {
@@ -148752,7 +148917,7 @@ ${fetched.join(`
148752
148917
  updatePrefs({ pinnedModel: r3.modelId });
148753
148918
  const newSpec2 = findModel(r3.modelId);
148754
148919
  const effortSuffix2 = applyEffortClamp(newSpec2 ? effortLevels(newSpec2) : []);
148755
- notice(`${r3.message} pinned (left subscription).${left}${effortSuffix2}`);
148920
+ notice(`${r3.message} · pinned (left subscription).${left}${effortSuffix2}`);
148756
148921
  const kind = classify(lastPromptRef.current ?? "").replace("code", "code");
148757
148922
  push({ kind: "preference", id: idRef.current++, text: `Remember ${r3.modelId} for ${kind} tasks?`, acceptCommand: `/prefer ${kind} ${r3.modelId}` });
148758
148923
  return;
@@ -148769,7 +148934,7 @@ ${fetched.join(`
148769
148934
  updatePrefs({ pinnedModel: r3.modelId });
148770
148935
  const newSpec2 = findModel(r3.modelId);
148771
148936
  const effortSuffix2 = applyEffortClamp(newSpec2 ? effortLevels(newSpec2) : []);
148772
- notice(`${r3.message} pinned (left subscription).${left}${effortSuffix2}`);
148937
+ notice(`${r3.message} · pinned (left subscription).${left}${effortSuffix2}`);
148773
148938
  const kind = classify(lastPromptRef.current ?? "").replace("code", "code");
148774
148939
  push({ kind: "preference", id: idRef.current++, text: `Remember ${r3.modelId} for ${kind} tasks?`, acceptCommand: `/prefer ${kind} ${r3.modelId}` });
148775
148940
  return;
@@ -148780,7 +148945,7 @@ ${fetched.join(`
148780
148945
  setActiveCliModelId(cr.modelId);
148781
148946
  const newCliModel = cliModelChoices(cli.binary).find((m2) => m2.id === cr.modelId);
148782
148947
  const effortSuffix = applyEffortClamp(newCliModel?.efforts ?? []);
148783
- notice(`subscription model → ${cr.label} using ${cli.binary}; tools and permissions still owned by the subscription${effortSuffix}`);
148948
+ notice(`subscription model → ${cr.label} · using ${cli.binary}; tools and permissions still owned by the subscription${effortSuffix}`);
148784
148949
  return;
148785
148950
  }
148786
148951
  const r2 = resolveModelSwitch(arg);
@@ -148792,11 +148957,20 @@ ${fetched.join(`
148792
148957
  updatePrefs({ pinnedModel: r2.modelId });
148793
148958
  const newSpec = findModel(r2.modelId);
148794
148959
  const effortSuffix = applyEffortClamp(newSpec ? effortLevels(newSpec) : []);
148795
- notice(`${r2.message} pinned (persists across sessions). /model auto to route per task again.${left}${effortSuffix}`);
148960
+ notice(`${r2.message} · pinned (persists across sessions). /model auto to route per task again.${left}${effortSuffix}`);
148796
148961
  const kind = classify(lastPromptRef.current ?? "").replace("code", "code");
148797
148962
  push({ kind: "preference", id: idRef.current++, text: `Remember ${r2.modelId} for ${kind} tasks?`, acceptCommand: `/prefer ${kind} ${r2.modelId}` });
148798
148963
  } else {
148799
- notice(r2.message);
148964
+ const wanted = modelRegistry().find((m2) => [m2.id, m2.label].some((s2) => s2.toLowerCase() === arg.toLowerCase() || s2.toLowerCase().includes(arg.toLowerCase())));
148965
+ const fam = wanted && /claude|anthropic/i.test(`${wanted.provider} ${wanted.id}`) ? "claude-cli" : wanted && /openai|gpt|^o\d/i.test(`${wanted.provider} ${wanted.id}`) ? "codex-cli" : null;
148966
+ const sub = fam ? listAccounts().find((a) => a.provider === fam && a.enabled) : null;
148967
+ if (sub && wanted) {
148968
+ notice(`${r2.message}
148969
+
148970
+ you have a ${accountName(sub)} subscription · use it with /account ${accountSlug(sub)}, or add a key: /account add ${wanted.provider} <key>`);
148971
+ } else {
148972
+ notice(r2.message);
148973
+ }
148800
148974
  }
148801
148975
  }
148802
148976
  return;
@@ -148826,7 +149000,7 @@ ${fetched.join(`
148826
149000
  const keys2 = Object.keys(b);
148827
149001
  notice(keys2.length ? `budgets (estimates remaining = budget − tracked spend):
148828
149002
  ` + keys2.map((k) => ` ${k}: $${b[k].amountUSD} ${b[k].period}`).join(`
148829
- `) : "no budgets set. /budget <provider|account> <amount> [monthly|total] lets routing estimate remaining credit for providers that don't expose a balance");
149003
+ `) : "no budgets set. /budget <provider|account> <amount> [monthly|total] · lets routing estimate remaining credit for providers that don't expose a balance");
148830
149004
  return;
148831
149005
  }
148832
149006
  const [target, amountRaw, periodRaw] = parts;
@@ -148853,7 +149027,7 @@ ${fetched.join(`
148853
149027
  }
148854
149028
  const facts = loadFacts().trim();
148855
149029
  const it = { kind: "notice", id: idRef.current++, text: facts ? `remembered facts:
148856
- ` + facts : "no remembered facts yet add one with #<note> or /memory <note>" };
149030
+ ` + facts : "no remembered facts yet · add one with #<note> or /memory <note>" };
148857
149031
  if (openInfoPanel("memory", it))
148858
149032
  return;
148859
149033
  echo(text2);
@@ -148870,7 +149044,7 @@ ${fetched.join(`
148870
149044
  })();
148871
149045
  if (!m2) {
148872
149046
  echo(text2);
148873
- notice(`no model available add a provider first
149047
+ notice(`no model available · add a provider first
148874
149048
 
148875
149049
  ` + onboardingSummary(onboardingState));
148876
149050
  return;
@@ -148887,7 +149061,7 @@ ${fetched.join(`
148887
149061
  echo(text2);
148888
149062
  const sel = selectorRef.current;
148889
149063
  if (!sel.explain) {
148890
- notice("routing is off a model or subscription is pinned. Use /model auto to route per task, then /why.");
149064
+ notice("routing is off · a model or subscription is pinned. Use /model auto to route per task, then /why.");
148891
149065
  return;
148892
149066
  }
148893
149067
  try {
@@ -149011,7 +149185,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149011
149185
  const withHealth = listAccounts();
149012
149186
  pushAccounts(buildAccountView(withHealth, activeCliRef.current?.id ?? null, importableEnvCreds(), statuses));
149013
149187
  } catch (e2) {
149014
- notice(`couldn't check subscription accounts ${e2?.message ?? String(e2)}`);
149188
+ notice(`couldn't check subscription accounts · ${e2?.message ?? String(e2)}`);
149015
149189
  pushAccounts(buildAccountView(listAccounts(), activeCliRef.current?.id ?? null, importableEnvCreds(), accountStatusCacheRef.current));
149016
149190
  }
149017
149191
  })();
@@ -149065,7 +149239,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149065
149239
  cliSessionRef.current = undefined;
149066
149240
  setActiveCli(null);
149067
149241
  updatePrefs({ activeAccount: null });
149068
- notice("left the subscription back to your API keys");
149242
+ notice("left the subscription · back to your API keys");
149069
149243
  return;
149070
149244
  }
149071
149245
  if (subL === "login") {
@@ -149128,13 +149302,13 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149128
149302
  notice(res.message);
149129
149303
  return;
149130
149304
  }
149131
- notice(`${res.message} testing…`);
149305
+ notice(`${res.message} · testing…`);
149132
149306
  const t2 = await testAccount(res.account);
149133
149307
  notice(t2.ok ? `✓ added · ${t2.message}` : `added, but the key test failed: ${t2.message}`);
149134
149308
  const d = await discoverModels(res.account);
149135
149309
  if (d.models.length) {
149136
149310
  putAccount({ ...res.account, models: d.models });
149137
- notice(`found ${d.models.length} model${d.models.length === 1 ? "" : "s"} on this account /model to pick one`);
149311
+ notice(`found ${d.models.length} model${d.models.length === 1 ? "" : "s"} on this account · /model to pick one`);
149138
149312
  } else if (d.note) {
149139
149313
  notice(d.note);
149140
149314
  }
@@ -149166,7 +149340,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149166
149340
  const keys2 = importableEnvCreds();
149167
149341
  const cloud = importableCloudCreds();
149168
149342
  if (!keys2.length && !cloud.length) {
149169
- notice("nothing to import no new provider keys or cloud creds found");
149343
+ notice("nothing to import · no new provider keys or cloud creds found");
149170
149344
  return;
149171
149345
  }
149172
149346
  for (const c of keys2)
@@ -149181,7 +149355,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149181
149355
  (async () => {
149182
149356
  const targets = listAccounts().filter((a) => a.enabled && a.exec !== "cli");
149183
149357
  if (!targets.length) {
149184
- notice("no API/cloud accounts to refresh /account add to add one");
149358
+ notice("no API/cloud accounts to refresh · /account add to add one");
149185
149359
  return;
149186
149360
  }
149187
149361
  notice(`refreshing models for ${targets.length} account${targets.length === 1 ? "" : "s"}…`);
@@ -149209,6 +149383,12 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149209
149383
  }
149210
149384
  case "cost":
149211
149385
  case "usage": {
149386
+ if (fullscreen) {
149387
+ const on = !statusPinned;
149388
+ setStatusPinned(on);
149389
+ notice(on ? "usage pinned above the composer — /cost to hide" : "usage hidden");
149390
+ return;
149391
+ }
149212
149392
  const accounts = listAccounts();
149213
149393
  const resolve13 = (id) => {
149214
149394
  const a = getAccount(id);
@@ -149250,7 +149430,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149250
149430
  case "compact": {
149251
149431
  echo(text2);
149252
149432
  if (busyRef.current) {
149253
- notice("busy try /compact once the current turn finishes");
149433
+ notice("busy · try /compact once the current turn finishes");
149254
149434
  return;
149255
149435
  }
149256
149436
  setBusy(true);
@@ -149274,7 +149454,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149274
149454
  case "init":
149275
149455
  if (busyRef.current) {
149276
149456
  echo(text2);
149277
- notice("busy try /init again once the current turn finishes");
149457
+ notice("busy · try /init again once the current turn finishes");
149278
149458
  return;
149279
149459
  }
149280
149460
  echo(text2);
@@ -149306,7 +149486,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149306
149486
  default: {
149307
149487
  echo(text2);
149308
149488
  const near = matchCommands(`/${name31}`).filter((c) => c.name !== `/${name31}`)[0];
149309
- notice(near ? `no /${name31} command did you mean ${near.name}? (/help for all)` : `no /${name31} command type /help to see what's available`);
149489
+ notice(near ? `no /${name31} command · did you mean ${near.name}? (/help for all)` : `no /${name31} command · type /help to see what's available`);
149310
149490
  return;
149311
149491
  }
149312
149492
  }
@@ -149335,7 +149515,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149335
149515
  const cmd = text2.slice(1).trim();
149336
149516
  echo(text2);
149337
149517
  if (!cmd) {
149338
- notice("run a shell command with !<command> e.g. !git status");
149518
+ notice("run a shell command with !<command> · e.g. !git status");
149339
149519
  return;
149340
149520
  }
149341
149521
  const id = idRef.current++;
@@ -149378,7 +149558,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149378
149558
  if (busyRef.current) {
149379
149559
  queueRef.current.push(text2);
149380
149560
  setQueued([...queueRef.current]);
149381
- notice(`queued (${queueRef.current.length}) sends when the current turn finishes`);
149561
+ notice(`queued (${queueRef.current.length}) · sends when the current turn finishes`);
149382
149562
  return;
149383
149563
  }
149384
149564
  if (looksLikeGearboxQuestion(text2)) {
@@ -149420,7 +149600,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149420
149600
  msgRef.current = ms.slice(0, mi);
149421
149601
  curAsstRef.current = null;
149422
149602
  setEdit({ value: userText, cursor: userText.length });
149423
- notice("rewound the last turn edit and resend");
149603
+ notice("rewound the last turn · edit and resend");
149424
149604
  };
149425
149605
  use_input_default((input, key) => {
149426
149606
  if (/\[<\d+;\d+;\d+[Mm]/.test(input))
@@ -149791,6 +149971,11 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149791
149971
  footer += 1;
149792
149972
  if (quickPicker && quickRows.length)
149793
149973
  footer += quickPickerLimit + 2;
149974
+ const stripView = statusPinned ? currentUsageView() : null;
149975
+ const stripSub = stripView ? stripView.subscriptions.find((s2) => s2.name === activeCli?.label) ?? stripView.subscriptions[0] ?? null : null;
149976
+ const stripApi = stripView ? stripView.apiKeys[0] ?? null : null;
149977
+ if (statusPinned)
149978
+ footer += 2 + (ctxPct != null ? 1 : 0) + (stripSub ? Math.max(1, stripSub.limits?.length ?? 1) : 0) + (stripApi?.spend ? 1 : 0) + 1;
149794
149979
  const HEADER = 3;
149795
149980
  const transcriptHeight = Math.max(1, rows - HEADER - footer);
149796
149981
  const maxScroll = Math.max(0, lines.length - transcriptHeight);
@@ -149821,87 +150006,87 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149821
150006
  if (atBottomRef.current)
149822
150007
  setScrollTop(maxScroll);
149823
150008
  }, [lines.length, maxScroll]);
149824
- const hero = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150009
+ const hero = /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149825
150010
  flexDirection: "column",
149826
150011
  alignItems: "center",
149827
- children: setupRequired ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SetupSplash, {
150012
+ children: setupRequired ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(SetupSplash, {
149828
150013
  state: onboardingState,
149829
150014
  width,
149830
150015
  skin: ghostSkin,
149831
150016
  splashSize
149832
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
150017
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
149833
150018
  children: [
149834
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
150019
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(MascotSplash, {
149835
150020
  skin: ghostSkin,
149836
150021
  size: splashSize
149837
150022
  }, undefined, false, undefined, this),
149838
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150023
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149839
150024
  marginTop: 1,
149840
150025
  children: [
149841
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150026
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149842
150027
  color: color.dim,
149843
150028
  children: "talk or type "
149844
150029
  }, undefined, false, undefined, this),
149845
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150030
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149846
150031
  color: color.faint,
149847
150032
  children: [
149848
150033
  glyph.bullet,
149849
150034
  " "
149850
150035
  ]
149851
150036
  }, undefined, true, undefined, this),
149852
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150037
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149853
150038
  color: color.accentDim,
149854
150039
  children: "/"
149855
150040
  }, undefined, false, undefined, this),
149856
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150041
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149857
150042
  color: color.dim,
149858
150043
  children: "commands "
149859
150044
  }, undefined, false, undefined, this),
149860
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150045
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149861
150046
  color: color.accentDim,
149862
150047
  children: "@"
149863
150048
  }, undefined, false, undefined, this),
149864
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150049
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149865
150050
  color: color.dim,
149866
150051
  children: "files "
149867
150052
  }, undefined, false, undefined, this),
149868
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150053
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149869
150054
  color: color.accentDim,
149870
150055
  children: "!"
149871
150056
  }, undefined, false, undefined, this),
149872
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150057
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149873
150058
  color: color.dim,
149874
150059
  children: "shell"
149875
150060
  }, undefined, false, undefined, this)
149876
150061
  ]
149877
150062
  }, undefined, true, undefined, this),
149878
- firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150063
+ firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149879
150064
  marginTop: 1,
149880
150065
  flexDirection: "column",
149881
150066
  alignItems: "center",
149882
150067
  children: [
149883
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150068
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149884
150069
  color: color.faint,
149885
150070
  children: [
149886
150071
  "new here? press ",
149887
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150072
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149888
150073
  color: color.accent,
149889
150074
  children: "?"
149890
150075
  }, undefined, false, undefined, this),
149891
150076
  " for shortcuts · ",
149892
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150077
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149893
150078
  color: color.accent,
149894
150079
  children: "shift+tab"
149895
150080
  }, undefined, false, undefined, this),
149896
150081
  " cycles modes · ",
149897
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150082
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149898
150083
  color: color.accent,
149899
150084
  children: "⌃Y"
149900
150085
  }, undefined, false, undefined, this),
149901
150086
  " copies the last reply"
149902
150087
  ]
149903
150088
  }, undefined, true, undefined, this),
149904
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150089
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149905
150090
  color: color.faint,
149906
150091
  children: "/config inline on for terminal scrollback · /keys for shortcuts"
149907
150092
  }, undefined, false, undefined, this)
@@ -149910,17 +150095,17 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149910
150095
  ]
149911
150096
  }, undefined, true, undefined, this)
149912
150097
  }, undefined, false, undefined, this);
149913
- const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150098
+ const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149914
150099
  flexDirection: "column",
149915
150100
  children: [
149916
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
150101
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(CommandPalette, {
149917
150102
  draft: edit2.value,
149918
150103
  selected: selectedPalette,
149919
150104
  limit: 7,
149920
150105
  rows: pickerRows,
149921
150106
  width
149922
150107
  }, undefined, false, undefined, this),
149923
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(FilePalette, {
150108
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(FilePalette, {
149924
150109
  matches: fileMatches,
149925
150110
  selected: selectedPalette,
149926
150111
  limit: 5,
@@ -149928,24 +150113,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149928
150113
  }, undefined, false, undefined, this)
149929
150114
  ]
149930
150115
  }, undefined, true, undefined, this) : null;
149931
- const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150116
+ const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149932
150117
  flexDirection: "column",
149933
150118
  marginTop: 1,
149934
150119
  children: [
149935
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150120
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149936
150121
  paddingX: 1,
149937
150122
  children: [
149938
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150123
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149939
150124
  color: color.accent,
149940
150125
  children: quickPicker === "model" ? "model" : "effort"
149941
150126
  }, undefined, false, undefined, this),
149942
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150127
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149943
150128
  color: color.faint,
149944
150129
  children: " · ↑↓ select · ⏎ apply · esc close"
149945
150130
  }, undefined, false, undefined, this)
149946
150131
  ]
149947
150132
  }, undefined, true, undefined, this),
149948
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
150133
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(CommandPalette, {
149949
150134
  draft: "",
149950
150135
  selected: Math.min(quickPickerIndex, quickRows.length - 1),
149951
150136
  limit: quickPickerLimit,
@@ -149954,10 +150139,10 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149954
150139
  }, undefined, false, undefined, this)
149955
150140
  ]
149956
150141
  }, undefined, true, undefined, this) : null;
149957
- const composerJsx = perm ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PermissionPrompt, {
150142
+ const composerJsx = perm ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(PermissionPrompt, {
149958
150143
  req: perm,
149959
150144
  width
149960
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Composer, {
150145
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Composer, {
149961
150146
  value: edit2.value,
149962
150147
  cursor: edit2.cursor,
149963
150148
  selectionAnchor: edit2.selectionAnchor,
@@ -149967,9 +150152,9 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149967
150152
  width,
149968
150153
  vim
149969
150154
  }, undefined, false, undefined, this);
149970
- const footerJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
150155
+ const footerJsx = /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
149971
150156
  children: [
149972
- busy || linger ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Working, {
150157
+ busy || linger ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Working, {
149973
150158
  state: mascotState,
149974
150159
  skin: ghostSkin,
149975
150160
  verb,
@@ -149984,15 +150169,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
149984
150169
  linger: linger && !busy,
149985
150170
  width
149986
150171
  }, undefined, false, undefined, this) : null,
149987
- busy ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ActivityRail, {
150172
+ busy ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(ActivityRail, {
149988
150173
  items,
149989
150174
  width
149990
150175
  }, undefined, false, undefined, this) : null,
149991
- queued.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150176
+ queued.length ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
149992
150177
  paddingX: 1,
149993
150178
  marginTop: 1,
149994
150179
  flexDirection: "column",
149995
- children: queued.map((q, i2) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150180
+ children: queued.map((q, i2) => /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
149996
150181
  color: color.faint,
149997
150182
  children: [
149998
150183
  "↳ queued: ",
@@ -150000,11 +150185,11 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150000
150185
  ]
150001
150186
  }, i2, true, undefined, this))
150002
150187
  }, undefined, false, undefined, this) : null,
150003
- mode2 !== "normal" ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150188
+ mode2 !== "normal" ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150004
150189
  paddingX: 1,
150005
150190
  marginTop: 1,
150006
150191
  children: [
150007
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150192
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150008
150193
  color: color.accent,
150009
150194
  children: [
150010
150195
  glyph.notice,
@@ -150012,7 +150197,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150012
150197
  mode2 === "plan" ? "plan mode" : "auto-accept edits"
150013
150198
  ]
150014
150199
  }, undefined, true, undefined, this),
150015
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150200
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150016
150201
  color: color.faint,
150017
150202
  children: [
150018
150203
  " · ",
@@ -150022,14 +150207,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150022
150207
  }, undefined, true, undefined, this)
150023
150208
  ]
150024
150209
  }, undefined, true, undefined, this) : null,
150025
- search ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150210
+ search ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150026
150211
  paddingX: 1,
150027
150212
  children: [
150028
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150213
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150029
150214
  color: color.accent,
150030
150215
  children: "(reverse-i-search)"
150031
150216
  }, undefined, false, undefined, this),
150032
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150217
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150033
150218
  color: color.text,
150034
150219
  children: [
150035
150220
  "`",
@@ -150037,15 +150222,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150037
150222
  "`: "
150038
150223
  ]
150039
150224
  }, undefined, true, undefined, this),
150040
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150225
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150041
150226
  color: color.dim,
150042
150227
  children: searchHistory(historyRef.current, search.q, search.idx) ?? (search.q ? "(no match)" : "")
150043
150228
  }, undefined, false, undefined, this)
150044
150229
  ]
150045
150230
  }, undefined, true, undefined, this) : null,
150046
- copiedNotice ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150231
+ copiedNotice ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150047
150232
  paddingX: 1,
150048
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
150233
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
150049
150234
  color: color.ok,
150050
150235
  children: [
150051
150236
  glyph.notice,
@@ -150055,7 +150240,16 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150055
150240
  }, undefined, true, undefined, this)
150056
150241
  }, undefined, false, undefined, this) : null,
150057
150242
  quickPickerJsx,
150058
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusBar, {
150243
+ statusPinned ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(StatusStrip, {
150244
+ ctxPct,
150245
+ tokens,
150246
+ contextWindow: model?.contextWindow ?? null,
150247
+ cost: estimateCost(sessionRef.current.turns),
150248
+ sub: stripSub,
150249
+ api: stripApi,
150250
+ width
150251
+ }, undefined, false, undefined, this) : null,
150252
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(StatusBar, {
150059
150253
  model: modelLabel,
150060
150254
  branch,
150061
150255
  routing,
@@ -150069,7 +150263,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150069
150263
  effort: displayEffort,
150070
150264
  online
150071
150265
  }, undefined, false, undefined, this),
150072
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150266
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150073
150267
  height: PALETTE_ROWS,
150074
150268
  flexDirection: "column",
150075
150269
  children: paletteJsx
@@ -150077,26 +150271,26 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150077
150271
  composerJsx
150078
150272
  ]
150079
150273
  }, undefined, true, undefined, this);
150080
- const inlineFooterJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
150274
+ const inlineFooterJsx = /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
150081
150275
  children: [
150082
150276
  paletteJsx,
150083
150277
  composerJsx
150084
150278
  ]
150085
150279
  }, undefined, true, undefined, this);
150086
150280
  if (fullscreen) {
150087
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150281
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150088
150282
  flexDirection: "column",
150089
150283
  width,
150090
150284
  height: rows,
150091
150285
  children: [
150092
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
150286
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Banner, {
150093
150287
  model: modelLabel,
150094
150288
  cwd: basename3(process.cwd()),
150095
150289
  width
150096
150290
  }, undefined, false, undefined, this),
150097
- panel ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150291
+ panel ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150098
150292
  paddingX: 1,
150099
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Panel, {
150293
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Panel, {
150100
150294
  panel,
150101
150295
  width: panelW,
150102
150296
  height: transcriptHeight,
@@ -150105,14 +150299,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150105
150299
  currentModelId: panelCurrentModel,
150106
150300
  staticLines: panelStaticLines
150107
150301
  }, undefined, false, undefined, this)
150108
- }, undefined, false, undefined, this) : welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150302
+ }, undefined, false, undefined, this) : welcome ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150109
150303
  height: transcriptHeight,
150110
150304
  flexDirection: "column",
150111
150305
  justifyContent: "center",
150112
150306
  children: hero
150113
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150307
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150114
150308
  paddingX: 1,
150115
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Viewport, {
150309
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Viewport, {
150116
150310
  lines,
150117
150311
  scrollTop: effScroll,
150118
150312
  height: transcriptHeight,
@@ -150124,24 +150318,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150124
150318
  ]
150125
150319
  }, undefined, true, undefined, this);
150126
150320
  }
150127
- const banner = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
150321
+ const banner = /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Banner, {
150128
150322
  model: modelLabel,
150129
150323
  cwd: basename3(process.cwd()),
150130
150324
  width
150131
150325
  }, undefined, false, undefined, this);
150132
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150326
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150133
150327
  flexDirection: "column",
150134
150328
  width,
150135
150329
  children: [
150136
- welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
150330
+ welcome ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(jsx_dev_runtime14.Fragment, {
150137
150331
  children: [
150138
150332
  banner,
150139
- /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
150333
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
150140
150334
  marginTop: 1,
150141
150335
  children: hero
150142
150336
  }, undefined, false, undefined, this)
150143
150337
  ]
150144
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Transcript, {
150338
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Transcript, {
150145
150339
  items,
150146
150340
  width,
150147
150341
  header: banner,
@@ -150155,7 +150349,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
150155
150349
  // src/cli.tsx
150156
150350
  init_providers();
150157
150351
  init_permission();
150158
- var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
150352
+ var jsx_dev_runtime15 = __toESM(require_jsx_dev_runtime(), 1);
150159
150353
  process.env.LANG = process.env.LANG || "en_US.UTF-8";
150160
150354
  process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
150161
150355
  var VERSION16 = "0.1.32";
@@ -150653,7 +150847,7 @@ if (fullscreen)
150653
150847
  process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
150654
150848
  if (mouse)
150655
150849
  process.stdout.write("\x1B[?1000h\x1B[?1002h\x1B[?1006h");
150656
- var app = render_default(/* @__PURE__ */ jsx_dev_runtime14.jsxDEV(App2, {
150850
+ var app = render_default(/* @__PURE__ */ jsx_dev_runtime15.jsxDEV(App2, {
150657
150851
  selector,
150658
150852
  fullscreen,
150659
150853
  resumeId