gearbox-code 0.1.36 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +451 -107
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -142793,6 +142793,206 @@ class RoutingSelector {
|
|
|
142793
142793
|
init_reasoning();
|
|
142794
142794
|
init_providers();
|
|
142795
142795
|
|
|
142796
|
+
// src/ui/panel.ts
|
|
142797
|
+
var clamp2 = (n, lo, hi) => Math.max(lo, Math.min(n, hi));
|
|
142798
|
+
var clampIndex = (i2, count) => count <= 0 ? 0 : clamp2(i2, 0, count - 1);
|
|
142799
|
+
var clampScroll = (s2, max2) => clamp2(s2, 0, Math.max(0, max2));
|
|
142800
|
+
var panelBodyHeight = (height) => Math.max(1, height - 2);
|
|
142801
|
+
function windowStart(index, count, viewH) {
|
|
142802
|
+
if (count <= viewH)
|
|
142803
|
+
return 0;
|
|
142804
|
+
const half = Math.floor(viewH / 2);
|
|
142805
|
+
return clamp2(index - half, 0, count - viewH);
|
|
142806
|
+
}
|
|
142807
|
+
function filterModelRows(rows, filter7) {
|
|
142808
|
+
const q = filter7.trim().toLowerCase();
|
|
142809
|
+
if (!q)
|
|
142810
|
+
return rows;
|
|
142811
|
+
return rows.filter((r2) => r2.label.toLowerCase().includes(q) || r2.id.toLowerCase().includes(q) || r2.provider.toLowerCase().includes(q));
|
|
142812
|
+
}
|
|
142813
|
+
function appendFilter(panel, ch) {
|
|
142814
|
+
return { ...panel, filter: panel.filter + ch, index: 0 };
|
|
142815
|
+
}
|
|
142816
|
+
function backspaceFilter(panel) {
|
|
142817
|
+
return { ...panel, filter: panel.filter.slice(0, -1), index: 0 };
|
|
142818
|
+
}
|
|
142819
|
+
|
|
142820
|
+
// src/ui/components/Panel.tsx
|
|
142821
|
+
var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
|
|
142822
|
+
function accountStateColor3(status) {
|
|
142823
|
+
if (status === "active")
|
|
142824
|
+
return color.ok;
|
|
142825
|
+
if (/not signed in/i.test(status))
|
|
142826
|
+
return color.err;
|
|
142827
|
+
return color.faint;
|
|
142828
|
+
}
|
|
142829
|
+
function Panel({
|
|
142830
|
+
panel,
|
|
142831
|
+
width,
|
|
142832
|
+
height,
|
|
142833
|
+
accounts,
|
|
142834
|
+
models,
|
|
142835
|
+
currentModelId,
|
|
142836
|
+
staticLines
|
|
142837
|
+
}) {
|
|
142838
|
+
const bodyH = panelBodyHeight(height);
|
|
142839
|
+
const innerW = Math.max(4, width - 2);
|
|
142840
|
+
let body = null;
|
|
142841
|
+
let hint = "esc close";
|
|
142842
|
+
if (panel.kind === "static") {
|
|
142843
|
+
const lines = staticLines ?? itemsToLines(panel.items, innerW);
|
|
142844
|
+
const maxScroll = Math.max(0, lines.length - bodyH);
|
|
142845
|
+
const scroll = Math.min(panel.scroll, maxScroll);
|
|
142846
|
+
body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142847
|
+
paddingX: 1,
|
|
142848
|
+
children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Viewport, {
|
|
142849
|
+
lines,
|
|
142850
|
+
scrollTop: scroll,
|
|
142851
|
+
height: bodyH,
|
|
142852
|
+
width: innerW
|
|
142853
|
+
}, undefined, false, undefined, this)
|
|
142854
|
+
}, undefined, false, undefined, this);
|
|
142855
|
+
hint = lines.length > bodyH ? "↑↓ / PgUp PgDn scroll · esc close" : "esc close";
|
|
142856
|
+
} else if (panel.kind === "accounts") {
|
|
142857
|
+
const rows = accounts?.rows ?? [];
|
|
142858
|
+
const idx = clampIndex(panel.index, rows.length);
|
|
142859
|
+
const start = windowStart(idx, rows.length, bodyH);
|
|
142860
|
+
const slice2 = rows.slice(start, start + bodyH);
|
|
142861
|
+
const labelPad = accounts?.labelPad ?? 0;
|
|
142862
|
+
body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142863
|
+
flexDirection: "column",
|
|
142864
|
+
paddingX: 1,
|
|
142865
|
+
children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142866
|
+
color: color.faint,
|
|
142867
|
+
children: "no accounts yet — /account add to add one"
|
|
142868
|
+
}, undefined, false, undefined, this) : slice2.map((r2, i2) => {
|
|
142869
|
+
const sel = start + i2 === idx;
|
|
142870
|
+
return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142871
|
+
backgroundColor: sel ? color.accentBg : undefined,
|
|
142872
|
+
children: [
|
|
142873
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142874
|
+
color: sel ? color.accent : color.faint,
|
|
142875
|
+
children: sel ? "▶ " : " "
|
|
142876
|
+
}, undefined, false, undefined, this),
|
|
142877
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142878
|
+
color: color.text,
|
|
142879
|
+
bold: r2.active,
|
|
142880
|
+
children: r2.name.padEnd(labelPad)
|
|
142881
|
+
}, undefined, false, undefined, this),
|
|
142882
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142883
|
+
color: color.faint,
|
|
142884
|
+
children: [
|
|
142885
|
+
" ",
|
|
142886
|
+
r2.type
|
|
142887
|
+
]
|
|
142888
|
+
}, undefined, true, undefined, this),
|
|
142889
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142890
|
+
color: accountStateColor3(r2.status),
|
|
142891
|
+
children: [
|
|
142892
|
+
" ",
|
|
142893
|
+
r2.status
|
|
142894
|
+
]
|
|
142895
|
+
}, undefined, true, undefined, this),
|
|
142896
|
+
r2.active ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142897
|
+
color: color.ok,
|
|
142898
|
+
children: [
|
|
142899
|
+
" ",
|
|
142900
|
+
glyph.on,
|
|
142901
|
+
" current"
|
|
142902
|
+
]
|
|
142903
|
+
}, undefined, true, undefined, this) : null
|
|
142904
|
+
]
|
|
142905
|
+
}, r2.alias, true, undefined, this);
|
|
142906
|
+
})
|
|
142907
|
+
}, undefined, false, undefined, this);
|
|
142908
|
+
hint = "↑↓ move · ⏎ switch · 1–9 jump · esc close";
|
|
142909
|
+
} else {
|
|
142910
|
+
const rows = filterModelRows(models ?? [], panel.filter);
|
|
142911
|
+
const idx = clampIndex(panel.index, rows.length);
|
|
142912
|
+
const start = windowStart(idx, rows.length, bodyH);
|
|
142913
|
+
const slice2 = rows.slice(start, start + bodyH);
|
|
142914
|
+
body = /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142915
|
+
flexDirection: "column",
|
|
142916
|
+
paddingX: 1,
|
|
142917
|
+
children: rows.length === 0 ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142918
|
+
color: color.faint,
|
|
142919
|
+
children: [
|
|
142920
|
+
"no models match “",
|
|
142921
|
+
panel.filter,
|
|
142922
|
+
"”"
|
|
142923
|
+
]
|
|
142924
|
+
}, undefined, true, undefined, this) : slice2.map((r2, i2) => {
|
|
142925
|
+
const sel = start + i2 === idx;
|
|
142926
|
+
const pinned = r2.id === currentModelId;
|
|
142927
|
+
return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142928
|
+
backgroundColor: sel ? color.accentBg : undefined,
|
|
142929
|
+
children: [
|
|
142930
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142931
|
+
color: sel ? color.accent : color.faint,
|
|
142932
|
+
children: sel ? "▶ " : " "
|
|
142933
|
+
}, undefined, false, undefined, this),
|
|
142934
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142935
|
+
color: pinned ? color.ok : color.text,
|
|
142936
|
+
bold: pinned,
|
|
142937
|
+
children: r2.label.padEnd(22)
|
|
142938
|
+
}, undefined, false, undefined, this),
|
|
142939
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142940
|
+
color: color.faint,
|
|
142941
|
+
children: r2.provider
|
|
142942
|
+
}, undefined, false, undefined, this),
|
|
142943
|
+
pinned ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142944
|
+
color: color.ok,
|
|
142945
|
+
children: [
|
|
142946
|
+
" ",
|
|
142947
|
+
glyph.on,
|
|
142948
|
+
" pinned"
|
|
142949
|
+
]
|
|
142950
|
+
}, undefined, true, undefined, this) : null
|
|
142951
|
+
]
|
|
142952
|
+
}, r2.id, true, undefined, this);
|
|
142953
|
+
})
|
|
142954
|
+
}, undefined, false, undefined, this);
|
|
142955
|
+
hint = `filter: ${panel.filter || "(type to filter)"} · ↑↓ · ⏎ pin · esc close`;
|
|
142956
|
+
}
|
|
142957
|
+
return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142958
|
+
flexDirection: "column",
|
|
142959
|
+
width,
|
|
142960
|
+
height,
|
|
142961
|
+
children: [
|
|
142962
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142963
|
+
width,
|
|
142964
|
+
paddingX: 1,
|
|
142965
|
+
justifyContent: "space-between",
|
|
142966
|
+
children: [
|
|
142967
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142968
|
+
color: color.accent,
|
|
142969
|
+
bold: true,
|
|
142970
|
+
children: panel.title
|
|
142971
|
+
}, undefined, false, undefined, this),
|
|
142972
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142973
|
+
color: color.faint,
|
|
142974
|
+
children: "esc to close"
|
|
142975
|
+
}, undefined, false, undefined, this)
|
|
142976
|
+
]
|
|
142977
|
+
}, undefined, true, undefined, this),
|
|
142978
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142979
|
+
flexDirection: "column",
|
|
142980
|
+
width,
|
|
142981
|
+
height: bodyH,
|
|
142982
|
+
children: body
|
|
142983
|
+
}, undefined, false, undefined, this),
|
|
142984
|
+
/* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
|
|
142985
|
+
width,
|
|
142986
|
+
paddingX: 1,
|
|
142987
|
+
children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
|
|
142988
|
+
color: color.faint,
|
|
142989
|
+
children: hint
|
|
142990
|
+
}, undefined, false, undefined, this)
|
|
142991
|
+
}, undefined, false, undefined, this)
|
|
142992
|
+
]
|
|
142993
|
+
}, undefined, true, undefined, this);
|
|
142994
|
+
}
|
|
142995
|
+
|
|
142796
142996
|
// src/agent/run.ts
|
|
142797
142997
|
init_dist26();
|
|
142798
142998
|
init_providers();
|
|
@@ -144700,7 +144900,7 @@ bun run typecheck
|
|
|
144700
144900
|
},
|
|
144701
144901
|
{
|
|
144702
144902
|
file: "CLAUDE.md",
|
|
144703
|
-
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`. Do not bypass it.\n- `src/model/router.ts` — `RoutingSelector`: classify prompt → quality bar → cost-sort candidates → respect `/prefer` preferences.\n- `src/model/profiles.ts` — the data corpus: quality, cost, latency, tokenizer calibration per model. Routing reads this.\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 interface + FixedSelector (pinned model)\n router.ts RoutingSelector: classify → quality bar → cost-sort → preferences (the live default)\n profiles.ts model corpus: quality (SWE-bench), cost ($/Mtok), latency, tokenizer calibration\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\n resolve.ts credential resolution (Account → ResolvedCreds, fetching secrets on demand)\n discover.ts per-account model discovery (Azure deployments / Foundry / gateway /models) → account.models; catalog defaultModels are seeds, not callable ids\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)\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\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 <q> (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**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```"
|
|
144903
|
+
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`. Do not bypass it.\n- `src/model/router.ts` — `RoutingSelector`: classify prompt → quality bar → cost-sort candidates → respect `/prefer` preferences.\n- `src/model/profiles.ts` — the data corpus: quality, cost, latency, tokenizer calibration per model. Routing reads this.\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 interface + FixedSelector (pinned model)\n router.ts RoutingSelector: classify → quality bar → cost-sort → preferences (the live default)\n profiles.ts model corpus: quality (SWE-bench), cost ($/Mtok), latency, tokenizer calibration\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\n resolve.ts credential resolution (Account → ResolvedCreds, fetching secrets on demand)\n discover.ts per-account model discovery (Azure deployments / Foundry / gateway /models) → account.models; catalog defaultModels are seeds, not callable ids\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)\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 <q> (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**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```"
|
|
144704
144904
|
},
|
|
144705
144905
|
{
|
|
144706
144906
|
file: "DESIGN.md",
|
|
@@ -145712,7 +145912,7 @@ function gitBranch() {
|
|
|
145712
145912
|
|
|
145713
145913
|
// src/ui/App.tsx
|
|
145714
145914
|
init_proc();
|
|
145715
|
-
var
|
|
145915
|
+
var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
|
|
145716
145916
|
import { basename as basename3, extname, resolve as resolve12 } from "node:path";
|
|
145717
145917
|
import { existsSync as existsSync11, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
|
|
145718
145918
|
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
@@ -145851,16 +146051,16 @@ function ActivityRail({ items, width }) {
|
|
|
145851
146051
|
const toolText = tools2.map((t2) => `${t2.status === "running" ? spin2 : t2.status === "err" ? "!" : "✓"} ${t2.name.replace(/_file$/, "").replace("run_shell", "shell")}`).join(" · ");
|
|
145852
146052
|
const checkText = checks4.map((c) => `${c.ok ? "✓" : "!"} ${c.command}`).join(" · ");
|
|
145853
146053
|
const line = [model ? model.model : null, phase ? phase.label : null, toolText || null, checkText || null].filter(Boolean).join(" · ");
|
|
145854
|
-
return /* @__PURE__ */
|
|
146054
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145855
146055
|
paddingX: 1,
|
|
145856
146056
|
marginTop: 1,
|
|
145857
146057
|
width,
|
|
145858
146058
|
children: [
|
|
145859
|
-
/* @__PURE__ */
|
|
146059
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145860
146060
|
color: color.accentDim,
|
|
145861
146061
|
children: "activity "
|
|
145862
146062
|
}, undefined, false, undefined, this),
|
|
145863
|
-
/* @__PURE__ */
|
|
146063
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145864
146064
|
color: color.faint,
|
|
145865
146065
|
children: line.slice(0, Math.max(width - 10, 20))
|
|
145866
146066
|
}, undefined, false, undefined, this)
|
|
@@ -145870,31 +146070,31 @@ function ActivityRail({ items, width }) {
|
|
|
145870
146070
|
function SetupSplash({ state, width, skin, splashSize }) {
|
|
145871
146071
|
const detected = state.importable.length + state.cloudImportable.length;
|
|
145872
146072
|
const panelWidth = Math.min(Math.max(width - 4, 30), 58);
|
|
145873
|
-
return /* @__PURE__ */
|
|
146073
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145874
146074
|
flexDirection: "column",
|
|
145875
146075
|
alignItems: "center",
|
|
145876
146076
|
children: [
|
|
145877
|
-
/* @__PURE__ */
|
|
146077
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
|
|
145878
146078
|
skin,
|
|
145879
146079
|
size: splashSize
|
|
145880
146080
|
}, undefined, false, undefined, this),
|
|
145881
|
-
/* @__PURE__ */
|
|
146081
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145882
146082
|
marginTop: 1,
|
|
145883
146083
|
flexDirection: "column",
|
|
145884
146084
|
alignItems: "center",
|
|
145885
146085
|
children: [
|
|
145886
|
-
/* @__PURE__ */
|
|
146086
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145887
146087
|
color: color.accent,
|
|
145888
146088
|
bold: true,
|
|
145889
146089
|
children: "gearbox"
|
|
145890
146090
|
}, undefined, false, undefined, this),
|
|
145891
|
-
/* @__PURE__ */
|
|
146091
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145892
146092
|
color: color.dim,
|
|
145893
146093
|
children: "one terminal · every model you already pay for"
|
|
145894
146094
|
}, undefined, false, undefined, this)
|
|
145895
146095
|
]
|
|
145896
146096
|
}, undefined, true, undefined, this),
|
|
145897
|
-
/* @__PURE__ */
|
|
146097
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145898
146098
|
marginTop: 2,
|
|
145899
146099
|
width: panelWidth,
|
|
145900
146100
|
borderStyle: "round",
|
|
@@ -145903,18 +146103,18 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
145903
146103
|
paddingY: 1,
|
|
145904
146104
|
flexDirection: "column",
|
|
145905
146105
|
children: [
|
|
145906
|
-
detected > 0 ? /* @__PURE__ */
|
|
146106
|
+
detected > 0 ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
145907
146107
|
children: [
|
|
145908
|
-
/* @__PURE__ */
|
|
146108
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145909
146109
|
children: [
|
|
145910
|
-
/* @__PURE__ */
|
|
146110
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145911
146111
|
color: color.ok,
|
|
145912
146112
|
children: [
|
|
145913
146113
|
glyph.on,
|
|
145914
146114
|
" "
|
|
145915
146115
|
]
|
|
145916
146116
|
}, undefined, true, undefined, this),
|
|
145917
|
-
/* @__PURE__ */
|
|
146117
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145918
146118
|
color: color.text,
|
|
145919
146119
|
children: [
|
|
145920
146120
|
detected,
|
|
@@ -145925,75 +146125,75 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
145925
146125
|
}, undefined, true, undefined, this)
|
|
145926
146126
|
]
|
|
145927
146127
|
}, undefined, true, undefined, this),
|
|
145928
|
-
/* @__PURE__ */
|
|
146128
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145929
146129
|
marginTop: 1,
|
|
145930
146130
|
children: [
|
|
145931
|
-
/* @__PURE__ */
|
|
146131
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145932
146132
|
color: color.accent,
|
|
145933
146133
|
children: "/account import"
|
|
145934
146134
|
}, undefined, false, undefined, this),
|
|
145935
|
-
/* @__PURE__ */
|
|
146135
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145936
146136
|
color: color.dim,
|
|
145937
146137
|
children: " connect automatically"
|
|
145938
146138
|
}, undefined, false, undefined, this)
|
|
145939
146139
|
]
|
|
145940
146140
|
}, undefined, true, undefined, this),
|
|
145941
|
-
/* @__PURE__ */
|
|
146141
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145942
146142
|
marginTop: 1,
|
|
145943
146143
|
children: [
|
|
145944
|
-
/* @__PURE__ */
|
|
146144
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145945
146145
|
color: color.faint,
|
|
145946
146146
|
children: "or add a different key: "
|
|
145947
146147
|
}, undefined, false, undefined, this),
|
|
145948
|
-
/* @__PURE__ */
|
|
146148
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145949
146149
|
color: color.accent,
|
|
145950
146150
|
children: "/account add <api-key>"
|
|
145951
146151
|
}, undefined, false, undefined, this)
|
|
145952
146152
|
]
|
|
145953
146153
|
}, undefined, true, undefined, this)
|
|
145954
146154
|
]
|
|
145955
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */
|
|
146155
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
145956
146156
|
children: [
|
|
145957
|
-
/* @__PURE__ */
|
|
146157
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145958
146158
|
color: color.dim,
|
|
145959
146159
|
children: "paste or type a key to get started"
|
|
145960
146160
|
}, undefined, false, undefined, this),
|
|
145961
|
-
/* @__PURE__ */
|
|
146161
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145962
146162
|
marginTop: 1,
|
|
145963
|
-
children: /* @__PURE__ */
|
|
146163
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145964
146164
|
color: color.accent,
|
|
145965
146165
|
children: "/account add <api-key>"
|
|
145966
146166
|
}, undefined, false, undefined, this)
|
|
145967
146167
|
}, undefined, false, undefined, this)
|
|
145968
146168
|
]
|
|
145969
146169
|
}, undefined, true, undefined, this),
|
|
145970
|
-
(state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */
|
|
146170
|
+
(state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145971
146171
|
marginTop: 2,
|
|
145972
146172
|
flexDirection: "column",
|
|
145973
146173
|
children: [
|
|
145974
|
-
/* @__PURE__ */
|
|
146174
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145975
146175
|
color: color.faint,
|
|
145976
146176
|
children: "subscriptions detected"
|
|
145977
146177
|
}, undefined, false, undefined, this),
|
|
145978
|
-
state.hasClaudeCli && /* @__PURE__ */
|
|
146178
|
+
state.hasClaudeCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145979
146179
|
children: [
|
|
145980
|
-
/* @__PURE__ */
|
|
146180
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145981
146181
|
color: color.accent,
|
|
145982
146182
|
children: "/account add claude"
|
|
145983
146183
|
}, undefined, false, undefined, this),
|
|
145984
|
-
/* @__PURE__ */
|
|
146184
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145985
146185
|
color: color.faint,
|
|
145986
146186
|
children: " Claude Pro / Max"
|
|
145987
146187
|
}, undefined, false, undefined, this)
|
|
145988
146188
|
]
|
|
145989
146189
|
}, undefined, true, undefined, this),
|
|
145990
|
-
state.hasCodexCli && /* @__PURE__ */
|
|
146190
|
+
state.hasCodexCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145991
146191
|
children: [
|
|
145992
|
-
/* @__PURE__ */
|
|
146192
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145993
146193
|
color: color.accent,
|
|
145994
146194
|
children: "/account add codex"
|
|
145995
146195
|
}, undefined, false, undefined, this),
|
|
145996
|
-
/* @__PURE__ */
|
|
146196
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145997
146197
|
color: color.faint,
|
|
145998
146198
|
children: " ChatGPT Plus"
|
|
145999
146199
|
}, undefined, false, undefined, this)
|
|
@@ -146003,9 +146203,9 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
146003
146203
|
}, undefined, true, undefined, this)
|
|
146004
146204
|
]
|
|
146005
146205
|
}, undefined, true, undefined, this),
|
|
146006
|
-
/* @__PURE__ */
|
|
146206
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
146007
146207
|
marginTop: 1,
|
|
146008
|
-
children: /* @__PURE__ */
|
|
146208
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
146009
146209
|
color: color.faint,
|
|
146010
146210
|
children: "/onboard · /account · /help"
|
|
146011
146211
|
}, undefined, false, undefined, this)
|
|
@@ -146080,6 +146280,22 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
|
|
|
146080
146280
|
quickPickerIndexRef.current = n;
|
|
146081
146281
|
setQuickPickerIndexState(n);
|
|
146082
146282
|
};
|
|
146283
|
+
const [panel, setPanelState] = import_react26.useState(null);
|
|
146284
|
+
const panelRef = import_react26.useRef(null);
|
|
146285
|
+
const setPanel = (p) => {
|
|
146286
|
+
panelRef.current = p;
|
|
146287
|
+
setPanelState(p);
|
|
146288
|
+
};
|
|
146289
|
+
const panelMaxScrollRef = import_react26.useRef(0);
|
|
146290
|
+
const panelAccountNumbersRef = import_react26.useRef([]);
|
|
146291
|
+
const buildPanelModelRows = (cur) => modelRegistry().filter((m2) => providerAvailable(m2.provider)).map((m2) => ({ id: m2.id, label: m2.label, provider: m2.provider, current: m2.id === cur }));
|
|
146292
|
+
const openInfoPanel = (title, item) => {
|
|
146293
|
+
if (!fullscreen)
|
|
146294
|
+
return false;
|
|
146295
|
+
atBottomRef.current = true;
|
|
146296
|
+
setPanel({ kind: "static", title, items: [item], scroll: 0 });
|
|
146297
|
+
return true;
|
|
146298
|
+
};
|
|
146083
146299
|
const setSearch = (s2) => {
|
|
146084
146300
|
searchRef.current = s2;
|
|
146085
146301
|
setSearchState(s2);
|
|
@@ -146174,7 +146390,10 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
|
|
|
146174
146390
|
setTimeout(pumpPerm, 0);
|
|
146175
146391
|
};
|
|
146176
146392
|
import_react26.useEffect(() => {
|
|
146177
|
-
|
|
146393
|
+
let acctId = loadPrefs().activeAccount;
|
|
146394
|
+
if (!acctId && buildPanelModelRows().length === 0) {
|
|
146395
|
+
acctId = listAccounts().find((a2) => a2.enabled && a2.exec === "cli")?.id;
|
|
146396
|
+
}
|
|
146178
146397
|
if (!acctId)
|
|
146179
146398
|
return;
|
|
146180
146399
|
const a = getAccount(acctId);
|
|
@@ -146184,6 +146403,7 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
|
|
|
146184
146403
|
if (activeCliModelRef.current && !cliSupportsModel(bin, activeCliModelRef.current))
|
|
146185
146404
|
setActiveCliModelId(undefined);
|
|
146186
146405
|
setActiveCli({ id: a.id, label: bin });
|
|
146406
|
+
updatePrefs({ activeAccount: a.id });
|
|
146187
146407
|
}
|
|
146188
146408
|
}, []);
|
|
146189
146409
|
const discoveryRanRef = import_react26.useRef(false);
|
|
@@ -146455,8 +146675,18 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
|
|
|
146455
146675
|
}
|
|
146456
146676
|
}
|
|
146457
146677
|
}
|
|
146458
|
-
if (delta)
|
|
146459
|
-
|
|
146678
|
+
if (delta) {
|
|
146679
|
+
const p = panelRef.current;
|
|
146680
|
+
if (p) {
|
|
146681
|
+
if (p.kind === "static")
|
|
146682
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll + delta, panelMaxScrollRef.current) });
|
|
146683
|
+
else if (p.kind === "accounts")
|
|
146684
|
+
setPanel({ ...p, index: clampIndex(p.index + delta, panelAccountNumbersRef.current.length) });
|
|
146685
|
+
else
|
|
146686
|
+
setPanel({ ...p, index: clampIndex(p.index + delta, filterModelRows(buildPanelModelRows(), p.filter).length) });
|
|
146687
|
+
} else
|
|
146688
|
+
scrollBy(delta);
|
|
146689
|
+
}
|
|
146460
146690
|
};
|
|
146461
146691
|
stdin.on("data", onData);
|
|
146462
146692
|
return () => {
|
|
@@ -147402,10 +147632,14 @@ ${fetched.join(`
|
|
|
147402
147632
|
loadInto(pick3);
|
|
147403
147633
|
return;
|
|
147404
147634
|
}
|
|
147405
|
-
case "help":
|
|
147635
|
+
case "help": {
|
|
147636
|
+
const it = { kind: "notice", id: idRef.current++, text: helpText() };
|
|
147637
|
+
if (openInfoPanel("help", it))
|
|
147638
|
+
return;
|
|
147406
147639
|
echo(text2);
|
|
147407
|
-
|
|
147640
|
+
push(it);
|
|
147408
147641
|
return;
|
|
147642
|
+
}
|
|
147409
147643
|
case "plan":
|
|
147410
147644
|
echo(text2);
|
|
147411
147645
|
togglePlan();
|
|
@@ -147440,10 +147674,14 @@ ${fetched.join(`
|
|
|
147440
147674
|
fsWriteFile(file5, transcriptMarkdown(itemsRef.current)).then(() => notice(`exported transcript → ${file5}`)).catch((e2) => notice(`couldn't write ${file5}: ${e2?.message ?? String(e2)}`));
|
|
147441
147675
|
return;
|
|
147442
147676
|
}
|
|
147443
|
-
case "keys":
|
|
147677
|
+
case "keys": {
|
|
147678
|
+
const it = { kind: "notice", id: idRef.current++, text: KEYS_HELP };
|
|
147679
|
+
if (openInfoPanel("keyboard shortcuts", it))
|
|
147680
|
+
return;
|
|
147444
147681
|
echo(text2);
|
|
147445
|
-
|
|
147682
|
+
push(it);
|
|
147446
147683
|
return;
|
|
147684
|
+
}
|
|
147447
147685
|
case "vim": {
|
|
147448
147686
|
echo(text2);
|
|
147449
147687
|
const on = vimRef.current === "off";
|
|
@@ -147531,6 +147769,10 @@ ${fetched.join(`
|
|
|
147531
147769
|
return;
|
|
147532
147770
|
}
|
|
147533
147771
|
case "model":
|
|
147772
|
+
if ((!arg || arg.toLowerCase() === "all") && !activeCliRef.current && fullscreen && buildPanelModelRows().length > 0) {
|
|
147773
|
+
setPanel({ kind: "models", title: "models · ⏎ to pin", index: 0, filter: "" });
|
|
147774
|
+
return;
|
|
147775
|
+
}
|
|
147534
147776
|
echo(text2);
|
|
147535
147777
|
if (!arg || arg.toLowerCase() === "all") {
|
|
147536
147778
|
const routing2 = selectorRef.current instanceof RoutingSelector;
|
|
@@ -147637,18 +147879,21 @@ ${fetched.join(`
|
|
|
147637
147879
|
return;
|
|
147638
147880
|
}
|
|
147639
147881
|
case "memory": {
|
|
147640
|
-
echo(text2);
|
|
147641
147882
|
if (arg) {
|
|
147883
|
+
echo(text2);
|
|
147642
147884
|
notice(appendFact(arg) ? "remembered" : "couldn't save that note");
|
|
147643
147885
|
return;
|
|
147644
147886
|
}
|
|
147645
147887
|
const facts = loadFacts().trim();
|
|
147646
|
-
notice
|
|
147647
|
-
` + facts : "no remembered facts yet — add one with #<note> or /memory <note>"
|
|
147888
|
+
const it = { kind: "notice", id: idRef.current++, text: facts ? `remembered facts:
|
|
147889
|
+
` + facts : "no remembered facts yet — add one with #<note> or /memory <note>" };
|
|
147890
|
+
if (openInfoPanel("memory", it))
|
|
147891
|
+
return;
|
|
147892
|
+
echo(text2);
|
|
147893
|
+
push(it);
|
|
147648
147894
|
return;
|
|
147649
147895
|
}
|
|
147650
147896
|
case "context": {
|
|
147651
|
-
echo(text2);
|
|
147652
147897
|
const m2 = (() => {
|
|
147653
147898
|
try {
|
|
147654
147899
|
return selectorRef.current.select({ prompt: "" }).model;
|
|
@@ -147657,13 +147902,18 @@ ${fetched.join(`
|
|
|
147657
147902
|
}
|
|
147658
147903
|
})();
|
|
147659
147904
|
if (!m2) {
|
|
147905
|
+
echo(text2);
|
|
147660
147906
|
notice(`no model available — add a provider first
|
|
147661
147907
|
|
|
147662
147908
|
` + onboardingSummary(onboardingState));
|
|
147663
147909
|
return;
|
|
147664
147910
|
}
|
|
147665
147911
|
const { sections } = buildContext({ history: msgRef.current, userText: lastPromptRef.current || "(your next message)", model: m2, plan: modeRef.current === "plan" });
|
|
147666
|
-
|
|
147912
|
+
const it = { kind: "context", id: idRef.current++, view: buildContextView(sections, m2.contextWindow, process.cwd()) };
|
|
147913
|
+
if (openInfoPanel("context", it))
|
|
147914
|
+
return;
|
|
147915
|
+
echo(text2);
|
|
147916
|
+
push(it);
|
|
147667
147917
|
return;
|
|
147668
147918
|
}
|
|
147669
147919
|
case "onboard": {
|
|
@@ -147727,6 +147977,10 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147727
147977
|
}
|
|
147728
147978
|
case "accounts":
|
|
147729
147979
|
case "account": {
|
|
147980
|
+
if (!arg.trim() && fullscreen) {
|
|
147981
|
+
setPanel({ kind: "accounts", title: "accounts · ⏎ to switch", index: 0 });
|
|
147982
|
+
return;
|
|
147983
|
+
}
|
|
147730
147984
|
echo(text2);
|
|
147731
147985
|
const parts = arg.split(/\s+/).filter(Boolean);
|
|
147732
147986
|
const subL = (parts[0] || "").toLowerCase();
|
|
@@ -147975,7 +148229,6 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147975
148229
|
}
|
|
147976
148230
|
case "cost":
|
|
147977
148231
|
case "usage": {
|
|
147978
|
-
echo(text2);
|
|
147979
148232
|
const accounts = listAccounts();
|
|
147980
148233
|
const resolve13 = (id) => {
|
|
147981
148234
|
const a = getAccount(id);
|
|
@@ -147995,9 +148248,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147995
148248
|
const session = estimateCost(sessionRef.current.turns);
|
|
147996
148249
|
const withBalance = accounts.filter((a) => a.exec !== "cli" && balanceExposed(a.provider));
|
|
147997
148250
|
if (!withBalance.length) {
|
|
147998
|
-
|
|
148251
|
+
const it = { kind: "usage", id: idRef.current++, view: buildUsageView(session, resolve13, Date.now(), accounts.map((a) => a.id)) };
|
|
148252
|
+
if (openInfoPanel("cost", it))
|
|
148253
|
+
return;
|
|
148254
|
+
echo(text2);
|
|
148255
|
+
push(it);
|
|
147999
148256
|
return;
|
|
148000
148257
|
}
|
|
148258
|
+
echo(text2);
|
|
148001
148259
|
notice("checking balances…");
|
|
148002
148260
|
(async () => {
|
|
148003
148261
|
for (const a of withBalance) {
|
|
@@ -148236,6 +148494,66 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148236
148494
|
notice("press ⌃C again to quit");
|
|
148237
148495
|
return;
|
|
148238
148496
|
}
|
|
148497
|
+
if (panelRef.current) {
|
|
148498
|
+
const p = panelRef.current;
|
|
148499
|
+
if (key.escape) {
|
|
148500
|
+
setPanel(null);
|
|
148501
|
+
return;
|
|
148502
|
+
}
|
|
148503
|
+
if (p.kind === "static") {
|
|
148504
|
+
if (input === "q") {
|
|
148505
|
+
setPanel(null);
|
|
148506
|
+
return;
|
|
148507
|
+
}
|
|
148508
|
+
const max2 = panelMaxScrollRef.current;
|
|
148509
|
+
const page = Math.max(1, panelBodyHeight(viewportHeightRef.current) - 1);
|
|
148510
|
+
if (key.upArrow)
|
|
148511
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll - 1, max2) });
|
|
148512
|
+
else if (key.downArrow)
|
|
148513
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll + 1, max2) });
|
|
148514
|
+
else if (key.pageUp)
|
|
148515
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll - page, max2) });
|
|
148516
|
+
else if (key.pageDown)
|
|
148517
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll + page, max2) });
|
|
148518
|
+
return;
|
|
148519
|
+
}
|
|
148520
|
+
if (p.kind === "accounts") {
|
|
148521
|
+
const nums = panelAccountNumbersRef.current;
|
|
148522
|
+
const n = nums.length;
|
|
148523
|
+
if (key.upArrow)
|
|
148524
|
+
setPanel({ ...p, index: clampIndex(p.index - 1, n) });
|
|
148525
|
+
else if (key.downArrow)
|
|
148526
|
+
setPanel({ ...p, index: clampIndex(p.index + 1, n) });
|
|
148527
|
+
else if (key.return) {
|
|
148528
|
+
const num = nums[clampIndex(p.index, n)];
|
|
148529
|
+
setPanel(null);
|
|
148530
|
+
if (num)
|
|
148531
|
+
handleCommand(`/account ${num}`);
|
|
148532
|
+
} else if (/^[1-9]$/.test(input)) {
|
|
148533
|
+
const k = Number(input);
|
|
148534
|
+
if (k <= n) {
|
|
148535
|
+
setPanel(null);
|
|
148536
|
+
handleCommand(`/account ${k}`);
|
|
148537
|
+
}
|
|
148538
|
+
}
|
|
148539
|
+
return;
|
|
148540
|
+
}
|
|
148541
|
+
const rows2 = filterModelRows(buildPanelModelRows(), p.filter);
|
|
148542
|
+
if (key.upArrow)
|
|
148543
|
+
setPanel({ ...p, index: clampIndex(p.index - 1, rows2.length) });
|
|
148544
|
+
else if (key.downArrow)
|
|
148545
|
+
setPanel({ ...p, index: clampIndex(p.index + 1, rows2.length) });
|
|
148546
|
+
else if (key.return) {
|
|
148547
|
+
const r2 = rows2[clampIndex(p.index, rows2.length)];
|
|
148548
|
+
setPanel(null);
|
|
148549
|
+
if (r2)
|
|
148550
|
+
handleCommand(`/model ${r2.id}`);
|
|
148551
|
+
} else if (key.backspace || key.delete)
|
|
148552
|
+
setPanel(backspaceFilter(p));
|
|
148553
|
+
else if (input && !key.ctrl && !key.meta && !key.tab && input.length === 1 && input >= " ")
|
|
148554
|
+
setPanel(appendFilter(p, input));
|
|
148555
|
+
return;
|
|
148556
|
+
}
|
|
148239
148557
|
if (key.ctrl && input === "r") {
|
|
148240
148558
|
const cur = searchRef.current;
|
|
148241
148559
|
setSearch(cur ? { q: cur.q, idx: cur.idx + 1 } : { q: "", idx: 0 });
|
|
@@ -148491,91 +148809,106 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148491
148809
|
paletteRowsLiveRef.current = PALETTE_ROWS;
|
|
148492
148810
|
maxScrollRef.current = maxScroll;
|
|
148493
148811
|
scrollTopRef.current = effScroll;
|
|
148812
|
+
const panelW = width - 2;
|
|
148813
|
+
const panelInnerW = Math.max(4, panelW - 2);
|
|
148814
|
+
const panelCurrentModel = loadPrefs().pinnedModel ?? null;
|
|
148815
|
+
let panelStaticLines;
|
|
148816
|
+
let panelAccountView;
|
|
148817
|
+
let panelModels;
|
|
148818
|
+
if (panel?.kind === "static") {
|
|
148819
|
+
panelStaticLines = itemsToLines(panel.items, panelInnerW);
|
|
148820
|
+
panelMaxScrollRef.current = Math.max(0, panelStaticLines.length - panelBodyHeight(transcriptHeight));
|
|
148821
|
+
} else if (panel?.kind === "accounts") {
|
|
148822
|
+
panelAccountView = buildAccountView(listAccounts(), activeCliRef.current?.id ?? null, importableEnvCreds(), accountStatusCacheRef.current);
|
|
148823
|
+
panelAccountNumbersRef.current = panelAccountView.rows.map((r2) => r2.number);
|
|
148824
|
+
} else if (panel?.kind === "models") {
|
|
148825
|
+
panelModels = buildPanelModelRows(panelCurrentModel);
|
|
148826
|
+
}
|
|
148494
148827
|
import_react26.useEffect(() => {
|
|
148495
148828
|
if (atBottomRef.current)
|
|
148496
148829
|
setScrollTop(maxScroll);
|
|
148497
148830
|
}, [lines.length, maxScroll]);
|
|
148498
|
-
const hero = /* @__PURE__ */
|
|
148831
|
+
const hero = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148499
148832
|
flexDirection: "column",
|
|
148500
148833
|
alignItems: "center",
|
|
148501
|
-
children: setupRequired ? /* @__PURE__ */
|
|
148834
|
+
children: setupRequired ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SetupSplash, {
|
|
148502
148835
|
state: onboardingState,
|
|
148503
148836
|
width,
|
|
148504
148837
|
skin: ghostSkin,
|
|
148505
148838
|
splashSize
|
|
148506
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
148839
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148507
148840
|
children: [
|
|
148508
|
-
/* @__PURE__ */
|
|
148841
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
|
|
148509
148842
|
skin: ghostSkin,
|
|
148510
148843
|
size: splashSize
|
|
148511
148844
|
}, undefined, false, undefined, this),
|
|
148512
|
-
/* @__PURE__ */
|
|
148845
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148513
148846
|
marginTop: 1,
|
|
148514
148847
|
children: [
|
|
148515
|
-
/* @__PURE__ */
|
|
148848
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148516
148849
|
color: color.dim,
|
|
148517
148850
|
children: "talk or type "
|
|
148518
148851
|
}, undefined, false, undefined, this),
|
|
148519
|
-
/* @__PURE__ */
|
|
148852
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148520
148853
|
color: color.faint,
|
|
148521
148854
|
children: [
|
|
148522
148855
|
glyph.bullet,
|
|
148523
148856
|
" "
|
|
148524
148857
|
]
|
|
148525
148858
|
}, undefined, true, undefined, this),
|
|
148526
|
-
/* @__PURE__ */
|
|
148859
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148527
148860
|
color: color.accentDim,
|
|
148528
148861
|
children: "/"
|
|
148529
148862
|
}, undefined, false, undefined, this),
|
|
148530
|
-
/* @__PURE__ */
|
|
148863
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148531
148864
|
color: color.dim,
|
|
148532
148865
|
children: "commands "
|
|
148533
148866
|
}, undefined, false, undefined, this),
|
|
148534
|
-
/* @__PURE__ */
|
|
148867
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148535
148868
|
color: color.accentDim,
|
|
148536
148869
|
children: "@"
|
|
148537
148870
|
}, undefined, false, undefined, this),
|
|
148538
|
-
/* @__PURE__ */
|
|
148871
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148539
148872
|
color: color.dim,
|
|
148540
148873
|
children: "files "
|
|
148541
148874
|
}, undefined, false, undefined, this),
|
|
148542
|
-
/* @__PURE__ */
|
|
148875
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148543
148876
|
color: color.accentDim,
|
|
148544
148877
|
children: "!"
|
|
148545
148878
|
}, undefined, false, undefined, this),
|
|
148546
|
-
/* @__PURE__ */
|
|
148879
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148547
148880
|
color: color.dim,
|
|
148548
148881
|
children: "shell"
|
|
148549
148882
|
}, undefined, false, undefined, this)
|
|
148550
148883
|
]
|
|
148551
148884
|
}, undefined, true, undefined, this),
|
|
148552
|
-
firstRunRef.current ? /* @__PURE__ */
|
|
148885
|
+
firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148553
148886
|
marginTop: 1,
|
|
148554
148887
|
flexDirection: "column",
|
|
148555
148888
|
alignItems: "center",
|
|
148556
148889
|
children: [
|
|
148557
|
-
/* @__PURE__ */
|
|
148890
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148558
148891
|
color: color.faint,
|
|
148559
148892
|
children: [
|
|
148560
148893
|
"new here? press ",
|
|
148561
|
-
/* @__PURE__ */
|
|
148894
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148562
148895
|
color: color.accent,
|
|
148563
148896
|
children: "?"
|
|
148564
148897
|
}, undefined, false, undefined, this),
|
|
148565
148898
|
" for shortcuts · ",
|
|
148566
|
-
/* @__PURE__ */
|
|
148899
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148567
148900
|
color: color.accent,
|
|
148568
148901
|
children: "shift+tab"
|
|
148569
148902
|
}, undefined, false, undefined, this),
|
|
148570
148903
|
" cycles modes · ",
|
|
148571
|
-
/* @__PURE__ */
|
|
148904
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148572
148905
|
color: color.accent,
|
|
148573
148906
|
children: "⌃Y"
|
|
148574
148907
|
}, undefined, false, undefined, this),
|
|
148575
148908
|
" copies the last reply"
|
|
148576
148909
|
]
|
|
148577
148910
|
}, undefined, true, undefined, this),
|
|
148578
|
-
/* @__PURE__ */
|
|
148911
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148579
148912
|
color: color.faint,
|
|
148580
148913
|
children: "/config inline on for terminal scrollback · /keys for shortcuts"
|
|
148581
148914
|
}, undefined, false, undefined, this)
|
|
@@ -148584,17 +148917,17 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148584
148917
|
]
|
|
148585
148918
|
}, undefined, true, undefined, this)
|
|
148586
148919
|
}, undefined, false, undefined, this);
|
|
148587
|
-
const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */
|
|
148920
|
+
const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148588
148921
|
flexDirection: "column",
|
|
148589
148922
|
children: [
|
|
148590
|
-
/* @__PURE__ */
|
|
148923
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
|
|
148591
148924
|
draft: edit2.value,
|
|
148592
148925
|
selected: selectedPalette,
|
|
148593
148926
|
limit: 7,
|
|
148594
148927
|
rows: pickerRows,
|
|
148595
148928
|
width
|
|
148596
148929
|
}, undefined, false, undefined, this),
|
|
148597
|
-
/* @__PURE__ */
|
|
148930
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(FilePalette, {
|
|
148598
148931
|
matches: fileMatches,
|
|
148599
148932
|
selected: selectedPalette,
|
|
148600
148933
|
limit: 5,
|
|
@@ -148602,24 +148935,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148602
148935
|
}, undefined, false, undefined, this)
|
|
148603
148936
|
]
|
|
148604
148937
|
}, undefined, true, undefined, this) : null;
|
|
148605
|
-
const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */
|
|
148938
|
+
const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148606
148939
|
flexDirection: "column",
|
|
148607
148940
|
marginTop: 1,
|
|
148608
148941
|
children: [
|
|
148609
|
-
/* @__PURE__ */
|
|
148942
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148610
148943
|
paddingX: 1,
|
|
148611
148944
|
children: [
|
|
148612
|
-
/* @__PURE__ */
|
|
148945
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148613
148946
|
color: color.accent,
|
|
148614
148947
|
children: quickPicker === "model" ? "model" : "effort"
|
|
148615
148948
|
}, undefined, false, undefined, this),
|
|
148616
|
-
/* @__PURE__ */
|
|
148949
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148617
148950
|
color: color.faint,
|
|
148618
148951
|
children: " · ↑↓ select · ⏎ apply · esc close"
|
|
148619
148952
|
}, undefined, false, undefined, this)
|
|
148620
148953
|
]
|
|
148621
148954
|
}, undefined, true, undefined, this),
|
|
148622
|
-
/* @__PURE__ */
|
|
148955
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
|
|
148623
148956
|
draft: "",
|
|
148624
148957
|
selected: Math.min(quickPickerIndex, quickRows.length - 1),
|
|
148625
148958
|
limit: quickPickerLimit,
|
|
@@ -148628,10 +148961,10 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148628
148961
|
}, undefined, false, undefined, this)
|
|
148629
148962
|
]
|
|
148630
148963
|
}, undefined, true, undefined, this) : null;
|
|
148631
|
-
const composerJsx = perm ? /* @__PURE__ */
|
|
148964
|
+
const composerJsx = perm ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PermissionPrompt, {
|
|
148632
148965
|
req: perm,
|
|
148633
148966
|
width
|
|
148634
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
148967
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Composer, {
|
|
148635
148968
|
value: edit2.value,
|
|
148636
148969
|
cursor: edit2.cursor,
|
|
148637
148970
|
selectionAnchor: edit2.selectionAnchor,
|
|
@@ -148641,9 +148974,9 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148641
148974
|
width,
|
|
148642
148975
|
vim
|
|
148643
148976
|
}, undefined, false, undefined, this);
|
|
148644
|
-
const footerJsx = /* @__PURE__ */
|
|
148977
|
+
const footerJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148645
148978
|
children: [
|
|
148646
|
-
busy || linger ? /* @__PURE__ */
|
|
148979
|
+
busy || linger ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Working, {
|
|
148647
148980
|
state: mascotState,
|
|
148648
148981
|
skin: ghostSkin,
|
|
148649
148982
|
verb,
|
|
@@ -148652,15 +148985,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148652
148985
|
linger: linger && !busy,
|
|
148653
148986
|
width
|
|
148654
148987
|
}, undefined, false, undefined, this) : null,
|
|
148655
|
-
busy ? /* @__PURE__ */
|
|
148988
|
+
busy ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ActivityRail, {
|
|
148656
148989
|
items,
|
|
148657
148990
|
width
|
|
148658
148991
|
}, undefined, false, undefined, this) : null,
|
|
148659
|
-
queued.length ? /* @__PURE__ */
|
|
148992
|
+
queued.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148660
148993
|
paddingX: 1,
|
|
148661
148994
|
marginTop: 1,
|
|
148662
148995
|
flexDirection: "column",
|
|
148663
|
-
children: queued.map((q, i2) => /* @__PURE__ */
|
|
148996
|
+
children: queued.map((q, i2) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148664
148997
|
color: color.faint,
|
|
148665
148998
|
children: [
|
|
148666
148999
|
"↳ queued: ",
|
|
@@ -148668,11 +149001,11 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148668
149001
|
]
|
|
148669
149002
|
}, i2, true, undefined, this))
|
|
148670
149003
|
}, undefined, false, undefined, this) : null,
|
|
148671
|
-
mode2 !== "normal" ? /* @__PURE__ */
|
|
149004
|
+
mode2 !== "normal" ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148672
149005
|
paddingX: 1,
|
|
148673
149006
|
marginTop: 1,
|
|
148674
149007
|
children: [
|
|
148675
|
-
/* @__PURE__ */
|
|
149008
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148676
149009
|
color: color.accent,
|
|
148677
149010
|
children: [
|
|
148678
149011
|
glyph.notice,
|
|
@@ -148680,7 +149013,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148680
149013
|
mode2 === "plan" ? "plan mode" : "auto-accept edits"
|
|
148681
149014
|
]
|
|
148682
149015
|
}, undefined, true, undefined, this),
|
|
148683
|
-
/* @__PURE__ */
|
|
149016
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148684
149017
|
color: color.faint,
|
|
148685
149018
|
children: [
|
|
148686
149019
|
" · ",
|
|
@@ -148690,14 +149023,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148690
149023
|
}, undefined, true, undefined, this)
|
|
148691
149024
|
]
|
|
148692
149025
|
}, undefined, true, undefined, this) : null,
|
|
148693
|
-
search ? /* @__PURE__ */
|
|
149026
|
+
search ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148694
149027
|
paddingX: 1,
|
|
148695
149028
|
children: [
|
|
148696
|
-
/* @__PURE__ */
|
|
149029
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148697
149030
|
color: color.accent,
|
|
148698
149031
|
children: "(reverse-i-search)"
|
|
148699
149032
|
}, undefined, false, undefined, this),
|
|
148700
|
-
/* @__PURE__ */
|
|
149033
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148701
149034
|
color: color.text,
|
|
148702
149035
|
children: [
|
|
148703
149036
|
"`",
|
|
@@ -148705,15 +149038,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148705
149038
|
"`: "
|
|
148706
149039
|
]
|
|
148707
149040
|
}, undefined, true, undefined, this),
|
|
148708
|
-
/* @__PURE__ */
|
|
149041
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148709
149042
|
color: color.dim,
|
|
148710
149043
|
children: searchHistory(historyRef.current, search.q, search.idx) ?? (search.q ? "(no match)" : "")
|
|
148711
149044
|
}, undefined, false, undefined, this)
|
|
148712
149045
|
]
|
|
148713
149046
|
}, undefined, true, undefined, this) : null,
|
|
148714
|
-
copiedNotice ? /* @__PURE__ */
|
|
149047
|
+
copiedNotice ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148715
149048
|
paddingX: 1,
|
|
148716
|
-
children: /* @__PURE__ */
|
|
149049
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148717
149050
|
color: color.ok,
|
|
148718
149051
|
children: [
|
|
148719
149052
|
glyph.notice,
|
|
@@ -148723,7 +149056,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148723
149056
|
}, undefined, true, undefined, this)
|
|
148724
149057
|
}, undefined, false, undefined, this) : null,
|
|
148725
149058
|
quickPickerJsx,
|
|
148726
|
-
/* @__PURE__ */
|
|
149059
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusBar, {
|
|
148727
149060
|
model: modelLabel,
|
|
148728
149061
|
branch,
|
|
148729
149062
|
routing,
|
|
@@ -148737,7 +149070,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148737
149070
|
effort: displayEffort,
|
|
148738
149071
|
online
|
|
148739
149072
|
}, undefined, false, undefined, this),
|
|
148740
|
-
/* @__PURE__ */
|
|
149073
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148741
149074
|
height: PALETTE_ROWS,
|
|
148742
149075
|
flexDirection: "column",
|
|
148743
149076
|
children: paletteJsx
|
|
@@ -148745,31 +149078,42 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148745
149078
|
composerJsx
|
|
148746
149079
|
]
|
|
148747
149080
|
}, undefined, true, undefined, this);
|
|
148748
|
-
const inlineFooterJsx = /* @__PURE__ */
|
|
149081
|
+
const inlineFooterJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148749
149082
|
children: [
|
|
148750
149083
|
paletteJsx,
|
|
148751
149084
|
composerJsx
|
|
148752
149085
|
]
|
|
148753
149086
|
}, undefined, true, undefined, this);
|
|
148754
149087
|
if (fullscreen) {
|
|
148755
|
-
return /* @__PURE__ */
|
|
149088
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148756
149089
|
flexDirection: "column",
|
|
148757
149090
|
width,
|
|
148758
149091
|
height: rows,
|
|
148759
149092
|
children: [
|
|
148760
|
-
/* @__PURE__ */
|
|
149093
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
|
|
148761
149094
|
model: modelLabel,
|
|
148762
149095
|
cwd: basename3(process.cwd()),
|
|
148763
149096
|
width
|
|
148764
149097
|
}, undefined, false, undefined, this),
|
|
148765
|
-
|
|
149098
|
+
panel ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
149099
|
+
paddingX: 1,
|
|
149100
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Panel, {
|
|
149101
|
+
panel,
|
|
149102
|
+
width: panelW,
|
|
149103
|
+
height: transcriptHeight,
|
|
149104
|
+
accounts: panelAccountView,
|
|
149105
|
+
models: panelModels,
|
|
149106
|
+
currentModelId: panelCurrentModel,
|
|
149107
|
+
staticLines: panelStaticLines
|
|
149108
|
+
}, undefined, false, undefined, this)
|
|
149109
|
+
}, undefined, false, undefined, this) : welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148766
149110
|
height: transcriptHeight,
|
|
148767
149111
|
flexDirection: "column",
|
|
148768
149112
|
justifyContent: "center",
|
|
148769
149113
|
children: hero
|
|
148770
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
149114
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148771
149115
|
paddingX: 1,
|
|
148772
|
-
children: /* @__PURE__ */
|
|
149116
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Viewport, {
|
|
148773
149117
|
lines,
|
|
148774
149118
|
scrollTop: effScroll,
|
|
148775
149119
|
height: transcriptHeight,
|
|
@@ -148781,24 +149125,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148781
149125
|
]
|
|
148782
149126
|
}, undefined, true, undefined, this);
|
|
148783
149127
|
}
|
|
148784
|
-
const banner = /* @__PURE__ */
|
|
149128
|
+
const banner = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
|
|
148785
149129
|
model: modelLabel,
|
|
148786
149130
|
cwd: basename3(process.cwd()),
|
|
148787
149131
|
width
|
|
148788
149132
|
}, undefined, false, undefined, this);
|
|
148789
|
-
return /* @__PURE__ */
|
|
149133
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148790
149134
|
flexDirection: "column",
|
|
148791
149135
|
width,
|
|
148792
149136
|
children: [
|
|
148793
|
-
welcome ? /* @__PURE__ */
|
|
149137
|
+
welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148794
149138
|
children: [
|
|
148795
149139
|
banner,
|
|
148796
|
-
/* @__PURE__ */
|
|
149140
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148797
149141
|
marginTop: 1,
|
|
148798
149142
|
children: hero
|
|
148799
149143
|
}, undefined, false, undefined, this)
|
|
148800
149144
|
]
|
|
148801
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */
|
|
149145
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Transcript, {
|
|
148802
149146
|
items,
|
|
148803
149147
|
width,
|
|
148804
149148
|
header: banner,
|
|
@@ -148812,7 +149156,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148812
149156
|
// src/cli.tsx
|
|
148813
149157
|
init_providers();
|
|
148814
149158
|
init_permission();
|
|
148815
|
-
var
|
|
149159
|
+
var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
|
|
148816
149160
|
process.env.LANG = process.env.LANG || "en_US.UTF-8";
|
|
148817
149161
|
process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
|
|
148818
149162
|
var VERSION16 = "0.1.32";
|
|
@@ -149310,7 +149654,7 @@ if (fullscreen)
|
|
|
149310
149654
|
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
|
|
149311
149655
|
if (mouse)
|
|
149312
149656
|
process.stdout.write("\x1B[?1000h\x1B[?1002h\x1B[?1006h");
|
|
149313
|
-
var app = render_default(/* @__PURE__ */
|
|
149657
|
+
var app = render_default(/* @__PURE__ */ jsx_dev_runtime14.jsxDEV(App2, {
|
|
149314
149658
|
selector,
|
|
149315
149659
|
fullscreen,
|
|
149316
149660
|
resumeId
|
package/package.json
CHANGED