gearbox-code 0.1.35 → 0.1.37
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 +438 -105
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -142624,6 +142624,7 @@ function updatePrefs(patch) {
|
|
|
142624
142624
|
|
|
142625
142625
|
// src/config.ts
|
|
142626
142626
|
init_providers();
|
|
142627
|
+
init_store();
|
|
142627
142628
|
var config2 = {
|
|
142628
142629
|
defaultModelId: process.env.GEARBOX_MODEL ?? "claude-sonnet-4-6",
|
|
142629
142630
|
maxSteps: Number(process.env.GEARBOX_MAX_STEPS ?? 24)
|
|
@@ -142636,7 +142637,9 @@ function pickDefaultModel(preferredId) {
|
|
|
142636
142637
|
return modelRegistry().find((m2) => providerAvailable(m2.provider));
|
|
142637
142638
|
}
|
|
142638
142639
|
function anyProviderAvailable() {
|
|
142639
|
-
|
|
142640
|
+
if (modelRegistry().some((m2) => providerAvailable(m2.provider)))
|
|
142641
|
+
return true;
|
|
142642
|
+
return listAccounts().some((a) => a.enabled && a.exec === "cli");
|
|
142640
142643
|
}
|
|
142641
142644
|
|
|
142642
142645
|
// src/model/selector.ts
|
|
@@ -142790,6 +142793,206 @@ class RoutingSelector {
|
|
|
142790
142793
|
init_reasoning();
|
|
142791
142794
|
init_providers();
|
|
142792
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
|
+
|
|
142793
142996
|
// src/agent/run.ts
|
|
142794
142997
|
init_dist26();
|
|
142795
142998
|
init_providers();
|
|
@@ -144697,7 +144900,7 @@ bun run typecheck
|
|
|
144697
144900
|
},
|
|
144698
144901
|
{
|
|
144699
144902
|
file: "CLAUDE.md",
|
|
144700
|
-
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```"
|
|
144701
144904
|
},
|
|
144702
144905
|
{
|
|
144703
144906
|
file: "DESIGN.md",
|
|
@@ -145709,7 +145912,7 @@ function gitBranch() {
|
|
|
145709
145912
|
|
|
145710
145913
|
// src/ui/App.tsx
|
|
145711
145914
|
init_proc();
|
|
145712
|
-
var
|
|
145915
|
+
var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
|
|
145713
145916
|
import { basename as basename3, extname, resolve as resolve12 } from "node:path";
|
|
145714
145917
|
import { existsSync as existsSync11, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
|
|
145715
145918
|
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
@@ -145848,16 +146051,16 @@ function ActivityRail({ items, width }) {
|
|
|
145848
146051
|
const toolText = tools2.map((t2) => `${t2.status === "running" ? spin2 : t2.status === "err" ? "!" : "✓"} ${t2.name.replace(/_file$/, "").replace("run_shell", "shell")}`).join(" · ");
|
|
145849
146052
|
const checkText = checks4.map((c) => `${c.ok ? "✓" : "!"} ${c.command}`).join(" · ");
|
|
145850
146053
|
const line = [model ? model.model : null, phase ? phase.label : null, toolText || null, checkText || null].filter(Boolean).join(" · ");
|
|
145851
|
-
return /* @__PURE__ */
|
|
146054
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145852
146055
|
paddingX: 1,
|
|
145853
146056
|
marginTop: 1,
|
|
145854
146057
|
width,
|
|
145855
146058
|
children: [
|
|
145856
|
-
/* @__PURE__ */
|
|
146059
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145857
146060
|
color: color.accentDim,
|
|
145858
146061
|
children: "activity "
|
|
145859
146062
|
}, undefined, false, undefined, this),
|
|
145860
|
-
/* @__PURE__ */
|
|
146063
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145861
146064
|
color: color.faint,
|
|
145862
146065
|
children: line.slice(0, Math.max(width - 10, 20))
|
|
145863
146066
|
}, undefined, false, undefined, this)
|
|
@@ -145867,31 +146070,31 @@ function ActivityRail({ items, width }) {
|
|
|
145867
146070
|
function SetupSplash({ state, width, skin, splashSize }) {
|
|
145868
146071
|
const detected = state.importable.length + state.cloudImportable.length;
|
|
145869
146072
|
const panelWidth = Math.min(Math.max(width - 4, 30), 58);
|
|
145870
|
-
return /* @__PURE__ */
|
|
146073
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145871
146074
|
flexDirection: "column",
|
|
145872
146075
|
alignItems: "center",
|
|
145873
146076
|
children: [
|
|
145874
|
-
/* @__PURE__ */
|
|
146077
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
|
|
145875
146078
|
skin,
|
|
145876
146079
|
size: splashSize
|
|
145877
146080
|
}, undefined, false, undefined, this),
|
|
145878
|
-
/* @__PURE__ */
|
|
146081
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145879
146082
|
marginTop: 1,
|
|
145880
146083
|
flexDirection: "column",
|
|
145881
146084
|
alignItems: "center",
|
|
145882
146085
|
children: [
|
|
145883
|
-
/* @__PURE__ */
|
|
146086
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145884
146087
|
color: color.accent,
|
|
145885
146088
|
bold: true,
|
|
145886
146089
|
children: "gearbox"
|
|
145887
146090
|
}, undefined, false, undefined, this),
|
|
145888
|
-
/* @__PURE__ */
|
|
146091
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145889
146092
|
color: color.dim,
|
|
145890
146093
|
children: "one terminal · every model you already pay for"
|
|
145891
146094
|
}, undefined, false, undefined, this)
|
|
145892
146095
|
]
|
|
145893
146096
|
}, undefined, true, undefined, this),
|
|
145894
|
-
/* @__PURE__ */
|
|
146097
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145895
146098
|
marginTop: 2,
|
|
145896
146099
|
width: panelWidth,
|
|
145897
146100
|
borderStyle: "round",
|
|
@@ -145900,18 +146103,18 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
145900
146103
|
paddingY: 1,
|
|
145901
146104
|
flexDirection: "column",
|
|
145902
146105
|
children: [
|
|
145903
|
-
detected > 0 ? /* @__PURE__ */
|
|
146106
|
+
detected > 0 ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
145904
146107
|
children: [
|
|
145905
|
-
/* @__PURE__ */
|
|
146108
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145906
146109
|
children: [
|
|
145907
|
-
/* @__PURE__ */
|
|
146110
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145908
146111
|
color: color.ok,
|
|
145909
146112
|
children: [
|
|
145910
146113
|
glyph.on,
|
|
145911
146114
|
" "
|
|
145912
146115
|
]
|
|
145913
146116
|
}, undefined, true, undefined, this),
|
|
145914
|
-
/* @__PURE__ */
|
|
146117
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145915
146118
|
color: color.text,
|
|
145916
146119
|
children: [
|
|
145917
146120
|
detected,
|
|
@@ -145922,75 +146125,75 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
145922
146125
|
}, undefined, true, undefined, this)
|
|
145923
146126
|
]
|
|
145924
146127
|
}, undefined, true, undefined, this),
|
|
145925
|
-
/* @__PURE__ */
|
|
146128
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145926
146129
|
marginTop: 1,
|
|
145927
146130
|
children: [
|
|
145928
|
-
/* @__PURE__ */
|
|
146131
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145929
146132
|
color: color.accent,
|
|
145930
146133
|
children: "/account import"
|
|
145931
146134
|
}, undefined, false, undefined, this),
|
|
145932
|
-
/* @__PURE__ */
|
|
146135
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145933
146136
|
color: color.dim,
|
|
145934
146137
|
children: " connect automatically"
|
|
145935
146138
|
}, undefined, false, undefined, this)
|
|
145936
146139
|
]
|
|
145937
146140
|
}, undefined, true, undefined, this),
|
|
145938
|
-
/* @__PURE__ */
|
|
146141
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145939
146142
|
marginTop: 1,
|
|
145940
146143
|
children: [
|
|
145941
|
-
/* @__PURE__ */
|
|
146144
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145942
146145
|
color: color.faint,
|
|
145943
146146
|
children: "or add a different key: "
|
|
145944
146147
|
}, undefined, false, undefined, this),
|
|
145945
|
-
/* @__PURE__ */
|
|
146148
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145946
146149
|
color: color.accent,
|
|
145947
146150
|
children: "/account add <api-key>"
|
|
145948
146151
|
}, undefined, false, undefined, this)
|
|
145949
146152
|
]
|
|
145950
146153
|
}, undefined, true, undefined, this)
|
|
145951
146154
|
]
|
|
145952
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */
|
|
146155
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
145953
146156
|
children: [
|
|
145954
|
-
/* @__PURE__ */
|
|
146157
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145955
146158
|
color: color.dim,
|
|
145956
146159
|
children: "paste or type a key to get started"
|
|
145957
146160
|
}, undefined, false, undefined, this),
|
|
145958
|
-
/* @__PURE__ */
|
|
146161
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145959
146162
|
marginTop: 1,
|
|
145960
|
-
children: /* @__PURE__ */
|
|
146163
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145961
146164
|
color: color.accent,
|
|
145962
146165
|
children: "/account add <api-key>"
|
|
145963
146166
|
}, undefined, false, undefined, this)
|
|
145964
146167
|
}, undefined, false, undefined, this)
|
|
145965
146168
|
]
|
|
145966
146169
|
}, undefined, true, undefined, this),
|
|
145967
|
-
(state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */
|
|
146170
|
+
(state.hasClaudeCli || state.hasCodexCli) && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145968
146171
|
marginTop: 2,
|
|
145969
146172
|
flexDirection: "column",
|
|
145970
146173
|
children: [
|
|
145971
|
-
/* @__PURE__ */
|
|
146174
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145972
146175
|
color: color.faint,
|
|
145973
146176
|
children: "subscriptions detected"
|
|
145974
146177
|
}, undefined, false, undefined, this),
|
|
145975
|
-
state.hasClaudeCli && /* @__PURE__ */
|
|
146178
|
+
state.hasClaudeCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145976
146179
|
children: [
|
|
145977
|
-
/* @__PURE__ */
|
|
146180
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145978
146181
|
color: color.accent,
|
|
145979
146182
|
children: "/account add claude"
|
|
145980
146183
|
}, undefined, false, undefined, this),
|
|
145981
|
-
/* @__PURE__ */
|
|
146184
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145982
146185
|
color: color.faint,
|
|
145983
146186
|
children: " Claude Pro / Max"
|
|
145984
146187
|
}, undefined, false, undefined, this)
|
|
145985
146188
|
]
|
|
145986
146189
|
}, undefined, true, undefined, this),
|
|
145987
|
-
state.hasCodexCli && /* @__PURE__ */
|
|
146190
|
+
state.hasCodexCli && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
145988
146191
|
children: [
|
|
145989
|
-
/* @__PURE__ */
|
|
146192
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145990
146193
|
color: color.accent,
|
|
145991
146194
|
children: "/account add codex"
|
|
145992
146195
|
}, undefined, false, undefined, this),
|
|
145993
|
-
/* @__PURE__ */
|
|
146196
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
145994
146197
|
color: color.faint,
|
|
145995
146198
|
children: " ChatGPT Plus"
|
|
145996
146199
|
}, undefined, false, undefined, this)
|
|
@@ -146000,9 +146203,9 @@ function SetupSplash({ state, width, skin, splashSize }) {
|
|
|
146000
146203
|
}, undefined, true, undefined, this)
|
|
146001
146204
|
]
|
|
146002
146205
|
}, undefined, true, undefined, this),
|
|
146003
|
-
/* @__PURE__ */
|
|
146206
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
146004
146207
|
marginTop: 1,
|
|
146005
|
-
children: /* @__PURE__ */
|
|
146208
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
146006
146209
|
color: color.faint,
|
|
146007
146210
|
children: "/onboard · /account · /help"
|
|
146008
146211
|
}, undefined, false, undefined, this)
|
|
@@ -146077,6 +146280,22 @@ function App2({ selector: initialSelector, runner, fullscreen = false, resumeId
|
|
|
146077
146280
|
quickPickerIndexRef.current = n;
|
|
146078
146281
|
setQuickPickerIndexState(n);
|
|
146079
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
|
+
};
|
|
146080
146299
|
const setSearch = (s2) => {
|
|
146081
146300
|
searchRef.current = s2;
|
|
146082
146301
|
setSearchState(s2);
|
|
@@ -147399,10 +147618,14 @@ ${fetched.join(`
|
|
|
147399
147618
|
loadInto(pick3);
|
|
147400
147619
|
return;
|
|
147401
147620
|
}
|
|
147402
|
-
case "help":
|
|
147621
|
+
case "help": {
|
|
147622
|
+
const it = { kind: "notice", id: idRef.current++, text: helpText() };
|
|
147623
|
+
if (openInfoPanel("help", it))
|
|
147624
|
+
return;
|
|
147403
147625
|
echo(text2);
|
|
147404
|
-
|
|
147626
|
+
push(it);
|
|
147405
147627
|
return;
|
|
147628
|
+
}
|
|
147406
147629
|
case "plan":
|
|
147407
147630
|
echo(text2);
|
|
147408
147631
|
togglePlan();
|
|
@@ -147437,10 +147660,14 @@ ${fetched.join(`
|
|
|
147437
147660
|
fsWriteFile(file5, transcriptMarkdown(itemsRef.current)).then(() => notice(`exported transcript → ${file5}`)).catch((e2) => notice(`couldn't write ${file5}: ${e2?.message ?? String(e2)}`));
|
|
147438
147661
|
return;
|
|
147439
147662
|
}
|
|
147440
|
-
case "keys":
|
|
147663
|
+
case "keys": {
|
|
147664
|
+
const it = { kind: "notice", id: idRef.current++, text: KEYS_HELP };
|
|
147665
|
+
if (openInfoPanel("keyboard shortcuts", it))
|
|
147666
|
+
return;
|
|
147441
147667
|
echo(text2);
|
|
147442
|
-
|
|
147668
|
+
push(it);
|
|
147443
147669
|
return;
|
|
147670
|
+
}
|
|
147444
147671
|
case "vim": {
|
|
147445
147672
|
echo(text2);
|
|
147446
147673
|
const on = vimRef.current === "off";
|
|
@@ -147528,6 +147755,10 @@ ${fetched.join(`
|
|
|
147528
147755
|
return;
|
|
147529
147756
|
}
|
|
147530
147757
|
case "model":
|
|
147758
|
+
if ((!arg || arg.toLowerCase() === "all") && !activeCliRef.current && fullscreen) {
|
|
147759
|
+
setPanel({ kind: "models", title: "models · ⏎ to pin", index: 0, filter: "" });
|
|
147760
|
+
return;
|
|
147761
|
+
}
|
|
147531
147762
|
echo(text2);
|
|
147532
147763
|
if (!arg || arg.toLowerCase() === "all") {
|
|
147533
147764
|
const routing2 = selectorRef.current instanceof RoutingSelector;
|
|
@@ -147634,18 +147865,21 @@ ${fetched.join(`
|
|
|
147634
147865
|
return;
|
|
147635
147866
|
}
|
|
147636
147867
|
case "memory": {
|
|
147637
|
-
echo(text2);
|
|
147638
147868
|
if (arg) {
|
|
147869
|
+
echo(text2);
|
|
147639
147870
|
notice(appendFact(arg) ? "remembered" : "couldn't save that note");
|
|
147640
147871
|
return;
|
|
147641
147872
|
}
|
|
147642
147873
|
const facts = loadFacts().trim();
|
|
147643
|
-
notice
|
|
147644
|
-
` + facts : "no remembered facts yet — add one with #<note> or /memory <note>"
|
|
147874
|
+
const it = { kind: "notice", id: idRef.current++, text: facts ? `remembered facts:
|
|
147875
|
+
` + facts : "no remembered facts yet — add one with #<note> or /memory <note>" };
|
|
147876
|
+
if (openInfoPanel("memory", it))
|
|
147877
|
+
return;
|
|
147878
|
+
echo(text2);
|
|
147879
|
+
push(it);
|
|
147645
147880
|
return;
|
|
147646
147881
|
}
|
|
147647
147882
|
case "context": {
|
|
147648
|
-
echo(text2);
|
|
147649
147883
|
const m2 = (() => {
|
|
147650
147884
|
try {
|
|
147651
147885
|
return selectorRef.current.select({ prompt: "" }).model;
|
|
@@ -147654,13 +147888,18 @@ ${fetched.join(`
|
|
|
147654
147888
|
}
|
|
147655
147889
|
})();
|
|
147656
147890
|
if (!m2) {
|
|
147891
|
+
echo(text2);
|
|
147657
147892
|
notice(`no model available — add a provider first
|
|
147658
147893
|
|
|
147659
147894
|
` + onboardingSummary(onboardingState));
|
|
147660
147895
|
return;
|
|
147661
147896
|
}
|
|
147662
147897
|
const { sections } = buildContext({ history: msgRef.current, userText: lastPromptRef.current || "(your next message)", model: m2, plan: modeRef.current === "plan" });
|
|
147663
|
-
|
|
147898
|
+
const it = { kind: "context", id: idRef.current++, view: buildContextView(sections, m2.contextWindow, process.cwd()) };
|
|
147899
|
+
if (openInfoPanel("context", it))
|
|
147900
|
+
return;
|
|
147901
|
+
echo(text2);
|
|
147902
|
+
push(it);
|
|
147664
147903
|
return;
|
|
147665
147904
|
}
|
|
147666
147905
|
case "onboard": {
|
|
@@ -147724,6 +147963,10 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147724
147963
|
}
|
|
147725
147964
|
case "accounts":
|
|
147726
147965
|
case "account": {
|
|
147966
|
+
if (!arg.trim() && fullscreen) {
|
|
147967
|
+
setPanel({ kind: "accounts", title: "accounts · ⏎ to switch", index: 0 });
|
|
147968
|
+
return;
|
|
147969
|
+
}
|
|
147727
147970
|
echo(text2);
|
|
147728
147971
|
const parts = arg.split(/\s+/).filter(Boolean);
|
|
147729
147972
|
const subL = (parts[0] || "").toLowerCase();
|
|
@@ -147972,7 +148215,6 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147972
148215
|
}
|
|
147973
148216
|
case "cost":
|
|
147974
148217
|
case "usage": {
|
|
147975
|
-
echo(text2);
|
|
147976
148218
|
const accounts = listAccounts();
|
|
147977
148219
|
const resolve13 = (id) => {
|
|
147978
148220
|
const a = getAccount(id);
|
|
@@ -147992,9 +148234,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
147992
148234
|
const session = estimateCost(sessionRef.current.turns);
|
|
147993
148235
|
const withBalance = accounts.filter((a) => a.exec !== "cli" && balanceExposed(a.provider));
|
|
147994
148236
|
if (!withBalance.length) {
|
|
147995
|
-
|
|
148237
|
+
const it = { kind: "usage", id: idRef.current++, view: buildUsageView(session, resolve13, Date.now(), accounts.map((a) => a.id)) };
|
|
148238
|
+
if (openInfoPanel("cost", it))
|
|
148239
|
+
return;
|
|
148240
|
+
echo(text2);
|
|
148241
|
+
push(it);
|
|
147996
148242
|
return;
|
|
147997
148243
|
}
|
|
148244
|
+
echo(text2);
|
|
147998
148245
|
notice("checking balances…");
|
|
147999
148246
|
(async () => {
|
|
148000
148247
|
for (const a of withBalance) {
|
|
@@ -148233,6 +148480,66 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148233
148480
|
notice("press ⌃C again to quit");
|
|
148234
148481
|
return;
|
|
148235
148482
|
}
|
|
148483
|
+
if (panelRef.current) {
|
|
148484
|
+
const p = panelRef.current;
|
|
148485
|
+
if (key.escape) {
|
|
148486
|
+
setPanel(null);
|
|
148487
|
+
return;
|
|
148488
|
+
}
|
|
148489
|
+
if (p.kind === "static") {
|
|
148490
|
+
if (input === "q") {
|
|
148491
|
+
setPanel(null);
|
|
148492
|
+
return;
|
|
148493
|
+
}
|
|
148494
|
+
const max2 = panelMaxScrollRef.current;
|
|
148495
|
+
const page = Math.max(1, panelBodyHeight(viewportHeightRef.current) - 1);
|
|
148496
|
+
if (key.upArrow)
|
|
148497
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll - 1, max2) });
|
|
148498
|
+
else if (key.downArrow)
|
|
148499
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll + 1, max2) });
|
|
148500
|
+
else if (key.pageUp)
|
|
148501
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll - page, max2) });
|
|
148502
|
+
else if (key.pageDown)
|
|
148503
|
+
setPanel({ ...p, scroll: clampScroll(p.scroll + page, max2) });
|
|
148504
|
+
return;
|
|
148505
|
+
}
|
|
148506
|
+
if (p.kind === "accounts") {
|
|
148507
|
+
const nums = panelAccountNumbersRef.current;
|
|
148508
|
+
const n = nums.length;
|
|
148509
|
+
if (key.upArrow)
|
|
148510
|
+
setPanel({ ...p, index: clampIndex(p.index - 1, n) });
|
|
148511
|
+
else if (key.downArrow)
|
|
148512
|
+
setPanel({ ...p, index: clampIndex(p.index + 1, n) });
|
|
148513
|
+
else if (key.return) {
|
|
148514
|
+
const num = nums[clampIndex(p.index, n)];
|
|
148515
|
+
setPanel(null);
|
|
148516
|
+
if (num)
|
|
148517
|
+
handleCommand(`/account ${num}`);
|
|
148518
|
+
} else if (/^[1-9]$/.test(input)) {
|
|
148519
|
+
const k = Number(input);
|
|
148520
|
+
if (k <= n) {
|
|
148521
|
+
setPanel(null);
|
|
148522
|
+
handleCommand(`/account ${k}`);
|
|
148523
|
+
}
|
|
148524
|
+
}
|
|
148525
|
+
return;
|
|
148526
|
+
}
|
|
148527
|
+
const rows2 = filterModelRows(buildPanelModelRows(), p.filter);
|
|
148528
|
+
if (key.upArrow)
|
|
148529
|
+
setPanel({ ...p, index: clampIndex(p.index - 1, rows2.length) });
|
|
148530
|
+
else if (key.downArrow)
|
|
148531
|
+
setPanel({ ...p, index: clampIndex(p.index + 1, rows2.length) });
|
|
148532
|
+
else if (key.return) {
|
|
148533
|
+
const r2 = rows2[clampIndex(p.index, rows2.length)];
|
|
148534
|
+
setPanel(null);
|
|
148535
|
+
if (r2)
|
|
148536
|
+
handleCommand(`/model ${r2.id}`);
|
|
148537
|
+
} else if (key.backspace || key.delete)
|
|
148538
|
+
setPanel(backspaceFilter(p));
|
|
148539
|
+
else if (input && !key.ctrl && !key.meta && !key.tab && input.length === 1 && input >= " ")
|
|
148540
|
+
setPanel(appendFilter(p, input));
|
|
148541
|
+
return;
|
|
148542
|
+
}
|
|
148236
148543
|
if (key.ctrl && input === "r") {
|
|
148237
148544
|
const cur = searchRef.current;
|
|
148238
148545
|
setSearch(cur ? { q: cur.q, idx: cur.idx + 1 } : { q: "", idx: 0 });
|
|
@@ -148488,91 +148795,106 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148488
148795
|
paletteRowsLiveRef.current = PALETTE_ROWS;
|
|
148489
148796
|
maxScrollRef.current = maxScroll;
|
|
148490
148797
|
scrollTopRef.current = effScroll;
|
|
148798
|
+
const panelW = width - 2;
|
|
148799
|
+
const panelInnerW = Math.max(4, panelW - 2);
|
|
148800
|
+
const panelCurrentModel = loadPrefs().pinnedModel ?? null;
|
|
148801
|
+
let panelStaticLines;
|
|
148802
|
+
let panelAccountView;
|
|
148803
|
+
let panelModels;
|
|
148804
|
+
if (panel?.kind === "static") {
|
|
148805
|
+
panelStaticLines = itemsToLines(panel.items, panelInnerW);
|
|
148806
|
+
panelMaxScrollRef.current = Math.max(0, panelStaticLines.length - panelBodyHeight(transcriptHeight));
|
|
148807
|
+
} else if (panel?.kind === "accounts") {
|
|
148808
|
+
panelAccountView = buildAccountView(listAccounts(), activeCliRef.current?.id ?? null, importableEnvCreds(), accountStatusCacheRef.current);
|
|
148809
|
+
panelAccountNumbersRef.current = panelAccountView.rows.map((r2) => r2.number);
|
|
148810
|
+
} else if (panel?.kind === "models") {
|
|
148811
|
+
panelModels = buildPanelModelRows(panelCurrentModel);
|
|
148812
|
+
}
|
|
148491
148813
|
import_react26.useEffect(() => {
|
|
148492
148814
|
if (atBottomRef.current)
|
|
148493
148815
|
setScrollTop(maxScroll);
|
|
148494
148816
|
}, [lines.length, maxScroll]);
|
|
148495
|
-
const hero = /* @__PURE__ */
|
|
148817
|
+
const hero = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148496
148818
|
flexDirection: "column",
|
|
148497
148819
|
alignItems: "center",
|
|
148498
|
-
children: setupRequired ? /* @__PURE__ */
|
|
148820
|
+
children: setupRequired ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SetupSplash, {
|
|
148499
148821
|
state: onboardingState,
|
|
148500
148822
|
width,
|
|
148501
148823
|
skin: ghostSkin,
|
|
148502
148824
|
splashSize
|
|
148503
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
148825
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148504
148826
|
children: [
|
|
148505
|
-
/* @__PURE__ */
|
|
148827
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MascotSplash, {
|
|
148506
148828
|
skin: ghostSkin,
|
|
148507
148829
|
size: splashSize
|
|
148508
148830
|
}, undefined, false, undefined, this),
|
|
148509
|
-
/* @__PURE__ */
|
|
148831
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148510
148832
|
marginTop: 1,
|
|
148511
148833
|
children: [
|
|
148512
|
-
/* @__PURE__ */
|
|
148834
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148513
148835
|
color: color.dim,
|
|
148514
148836
|
children: "talk or type "
|
|
148515
148837
|
}, undefined, false, undefined, this),
|
|
148516
|
-
/* @__PURE__ */
|
|
148838
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148517
148839
|
color: color.faint,
|
|
148518
148840
|
children: [
|
|
148519
148841
|
glyph.bullet,
|
|
148520
148842
|
" "
|
|
148521
148843
|
]
|
|
148522
148844
|
}, undefined, true, undefined, this),
|
|
148523
|
-
/* @__PURE__ */
|
|
148845
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148524
148846
|
color: color.accentDim,
|
|
148525
148847
|
children: "/"
|
|
148526
148848
|
}, undefined, false, undefined, this),
|
|
148527
|
-
/* @__PURE__ */
|
|
148849
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148528
148850
|
color: color.dim,
|
|
148529
148851
|
children: "commands "
|
|
148530
148852
|
}, undefined, false, undefined, this),
|
|
148531
|
-
/* @__PURE__ */
|
|
148853
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148532
148854
|
color: color.accentDim,
|
|
148533
148855
|
children: "@"
|
|
148534
148856
|
}, undefined, false, undefined, this),
|
|
148535
|
-
/* @__PURE__ */
|
|
148857
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148536
148858
|
color: color.dim,
|
|
148537
148859
|
children: "files "
|
|
148538
148860
|
}, undefined, false, undefined, this),
|
|
148539
|
-
/* @__PURE__ */
|
|
148861
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148540
148862
|
color: color.accentDim,
|
|
148541
148863
|
children: "!"
|
|
148542
148864
|
}, undefined, false, undefined, this),
|
|
148543
|
-
/* @__PURE__ */
|
|
148865
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148544
148866
|
color: color.dim,
|
|
148545
148867
|
children: "shell"
|
|
148546
148868
|
}, undefined, false, undefined, this)
|
|
148547
148869
|
]
|
|
148548
148870
|
}, undefined, true, undefined, this),
|
|
148549
|
-
firstRunRef.current ? /* @__PURE__ */
|
|
148871
|
+
firstRunRef.current ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148550
148872
|
marginTop: 1,
|
|
148551
148873
|
flexDirection: "column",
|
|
148552
148874
|
alignItems: "center",
|
|
148553
148875
|
children: [
|
|
148554
|
-
/* @__PURE__ */
|
|
148876
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148555
148877
|
color: color.faint,
|
|
148556
148878
|
children: [
|
|
148557
148879
|
"new here? press ",
|
|
148558
|
-
/* @__PURE__ */
|
|
148880
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148559
148881
|
color: color.accent,
|
|
148560
148882
|
children: "?"
|
|
148561
148883
|
}, undefined, false, undefined, this),
|
|
148562
148884
|
" for shortcuts · ",
|
|
148563
|
-
/* @__PURE__ */
|
|
148885
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148564
148886
|
color: color.accent,
|
|
148565
148887
|
children: "shift+tab"
|
|
148566
148888
|
}, undefined, false, undefined, this),
|
|
148567
148889
|
" cycles modes · ",
|
|
148568
|
-
/* @__PURE__ */
|
|
148890
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148569
148891
|
color: color.accent,
|
|
148570
148892
|
children: "⌃Y"
|
|
148571
148893
|
}, undefined, false, undefined, this),
|
|
148572
148894
|
" copies the last reply"
|
|
148573
148895
|
]
|
|
148574
148896
|
}, undefined, true, undefined, this),
|
|
148575
|
-
/* @__PURE__ */
|
|
148897
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148576
148898
|
color: color.faint,
|
|
148577
148899
|
children: "/config inline on for terminal scrollback · /keys for shortcuts"
|
|
148578
148900
|
}, undefined, false, undefined, this)
|
|
@@ -148581,17 +148903,17 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148581
148903
|
]
|
|
148582
148904
|
}, undefined, true, undefined, this)
|
|
148583
148905
|
}, undefined, false, undefined, this);
|
|
148584
|
-
const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */
|
|
148906
|
+
const paletteJsx = pickerRows.length || cmdMatches.length || fileMatches.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148585
148907
|
flexDirection: "column",
|
|
148586
148908
|
children: [
|
|
148587
|
-
/* @__PURE__ */
|
|
148909
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
|
|
148588
148910
|
draft: edit2.value,
|
|
148589
148911
|
selected: selectedPalette,
|
|
148590
148912
|
limit: 7,
|
|
148591
148913
|
rows: pickerRows,
|
|
148592
148914
|
width
|
|
148593
148915
|
}, undefined, false, undefined, this),
|
|
148594
|
-
/* @__PURE__ */
|
|
148916
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(FilePalette, {
|
|
148595
148917
|
matches: fileMatches,
|
|
148596
148918
|
selected: selectedPalette,
|
|
148597
148919
|
limit: 5,
|
|
@@ -148599,24 +148921,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148599
148921
|
}, undefined, false, undefined, this)
|
|
148600
148922
|
]
|
|
148601
148923
|
}, undefined, true, undefined, this) : null;
|
|
148602
|
-
const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */
|
|
148924
|
+
const quickPickerJsx = quickPicker && quickRows.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148603
148925
|
flexDirection: "column",
|
|
148604
148926
|
marginTop: 1,
|
|
148605
148927
|
children: [
|
|
148606
|
-
/* @__PURE__ */
|
|
148928
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148607
148929
|
paddingX: 1,
|
|
148608
148930
|
children: [
|
|
148609
|
-
/* @__PURE__ */
|
|
148931
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148610
148932
|
color: color.accent,
|
|
148611
148933
|
children: quickPicker === "model" ? "model" : "effort"
|
|
148612
148934
|
}, undefined, false, undefined, this),
|
|
148613
|
-
/* @__PURE__ */
|
|
148935
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148614
148936
|
color: color.faint,
|
|
148615
148937
|
children: " · ↑↓ select · ⏎ apply · esc close"
|
|
148616
148938
|
}, undefined, false, undefined, this)
|
|
148617
148939
|
]
|
|
148618
148940
|
}, undefined, true, undefined, this),
|
|
148619
|
-
/* @__PURE__ */
|
|
148941
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CommandPalette, {
|
|
148620
148942
|
draft: "",
|
|
148621
148943
|
selected: Math.min(quickPickerIndex, quickRows.length - 1),
|
|
148622
148944
|
limit: quickPickerLimit,
|
|
@@ -148625,10 +148947,10 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148625
148947
|
}, undefined, false, undefined, this)
|
|
148626
148948
|
]
|
|
148627
148949
|
}, undefined, true, undefined, this) : null;
|
|
148628
|
-
const composerJsx = perm ? /* @__PURE__ */
|
|
148950
|
+
const composerJsx = perm ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(PermissionPrompt, {
|
|
148629
148951
|
req: perm,
|
|
148630
148952
|
width
|
|
148631
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
148953
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Composer, {
|
|
148632
148954
|
value: edit2.value,
|
|
148633
148955
|
cursor: edit2.cursor,
|
|
148634
148956
|
selectionAnchor: edit2.selectionAnchor,
|
|
@@ -148638,9 +148960,9 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148638
148960
|
width,
|
|
148639
148961
|
vim
|
|
148640
148962
|
}, undefined, false, undefined, this);
|
|
148641
|
-
const footerJsx = /* @__PURE__ */
|
|
148963
|
+
const footerJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148642
148964
|
children: [
|
|
148643
|
-
busy || linger ? /* @__PURE__ */
|
|
148965
|
+
busy || linger ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Working, {
|
|
148644
148966
|
state: mascotState,
|
|
148645
148967
|
skin: ghostSkin,
|
|
148646
148968
|
verb,
|
|
@@ -148649,15 +148971,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148649
148971
|
linger: linger && !busy,
|
|
148650
148972
|
width
|
|
148651
148973
|
}, undefined, false, undefined, this) : null,
|
|
148652
|
-
busy ? /* @__PURE__ */
|
|
148974
|
+
busy ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ActivityRail, {
|
|
148653
148975
|
items,
|
|
148654
148976
|
width
|
|
148655
148977
|
}, undefined, false, undefined, this) : null,
|
|
148656
|
-
queued.length ? /* @__PURE__ */
|
|
148978
|
+
queued.length ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148657
148979
|
paddingX: 1,
|
|
148658
148980
|
marginTop: 1,
|
|
148659
148981
|
flexDirection: "column",
|
|
148660
|
-
children: queued.map((q, i2) => /* @__PURE__ */
|
|
148982
|
+
children: queued.map((q, i2) => /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148661
148983
|
color: color.faint,
|
|
148662
148984
|
children: [
|
|
148663
148985
|
"↳ queued: ",
|
|
@@ -148665,11 +148987,11 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148665
148987
|
]
|
|
148666
148988
|
}, i2, true, undefined, this))
|
|
148667
148989
|
}, undefined, false, undefined, this) : null,
|
|
148668
|
-
mode2 !== "normal" ? /* @__PURE__ */
|
|
148990
|
+
mode2 !== "normal" ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148669
148991
|
paddingX: 1,
|
|
148670
148992
|
marginTop: 1,
|
|
148671
148993
|
children: [
|
|
148672
|
-
/* @__PURE__ */
|
|
148994
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148673
148995
|
color: color.accent,
|
|
148674
148996
|
children: [
|
|
148675
148997
|
glyph.notice,
|
|
@@ -148677,7 +148999,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148677
148999
|
mode2 === "plan" ? "plan mode" : "auto-accept edits"
|
|
148678
149000
|
]
|
|
148679
149001
|
}, undefined, true, undefined, this),
|
|
148680
|
-
/* @__PURE__ */
|
|
149002
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148681
149003
|
color: color.faint,
|
|
148682
149004
|
children: [
|
|
148683
149005
|
" · ",
|
|
@@ -148687,14 +149009,14 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148687
149009
|
}, undefined, true, undefined, this)
|
|
148688
149010
|
]
|
|
148689
149011
|
}, undefined, true, undefined, this) : null,
|
|
148690
|
-
search ? /* @__PURE__ */
|
|
149012
|
+
search ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148691
149013
|
paddingX: 1,
|
|
148692
149014
|
children: [
|
|
148693
|
-
/* @__PURE__ */
|
|
149015
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148694
149016
|
color: color.accent,
|
|
148695
149017
|
children: "(reverse-i-search)"
|
|
148696
149018
|
}, undefined, false, undefined, this),
|
|
148697
|
-
/* @__PURE__ */
|
|
149019
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148698
149020
|
color: color.text,
|
|
148699
149021
|
children: [
|
|
148700
149022
|
"`",
|
|
@@ -148702,15 +149024,15 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148702
149024
|
"`: "
|
|
148703
149025
|
]
|
|
148704
149026
|
}, undefined, true, undefined, this),
|
|
148705
|
-
/* @__PURE__ */
|
|
149027
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148706
149028
|
color: color.dim,
|
|
148707
149029
|
children: searchHistory(historyRef.current, search.q, search.idx) ?? (search.q ? "(no match)" : "")
|
|
148708
149030
|
}, undefined, false, undefined, this)
|
|
148709
149031
|
]
|
|
148710
149032
|
}, undefined, true, undefined, this) : null,
|
|
148711
|
-
copiedNotice ? /* @__PURE__ */
|
|
149033
|
+
copiedNotice ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148712
149034
|
paddingX: 1,
|
|
148713
|
-
children: /* @__PURE__ */
|
|
149035
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
|
|
148714
149036
|
color: color.ok,
|
|
148715
149037
|
children: [
|
|
148716
149038
|
glyph.notice,
|
|
@@ -148720,7 +149042,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148720
149042
|
}, undefined, true, undefined, this)
|
|
148721
149043
|
}, undefined, false, undefined, this) : null,
|
|
148722
149044
|
quickPickerJsx,
|
|
148723
|
-
/* @__PURE__ */
|
|
149045
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusBar, {
|
|
148724
149046
|
model: modelLabel,
|
|
148725
149047
|
branch,
|
|
148726
149048
|
routing,
|
|
@@ -148734,7 +149056,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148734
149056
|
effort: displayEffort,
|
|
148735
149057
|
online
|
|
148736
149058
|
}, undefined, false, undefined, this),
|
|
148737
|
-
/* @__PURE__ */
|
|
149059
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148738
149060
|
height: PALETTE_ROWS,
|
|
148739
149061
|
flexDirection: "column",
|
|
148740
149062
|
children: paletteJsx
|
|
@@ -148742,31 +149064,42 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148742
149064
|
composerJsx
|
|
148743
149065
|
]
|
|
148744
149066
|
}, undefined, true, undefined, this);
|
|
148745
|
-
const inlineFooterJsx = /* @__PURE__ */
|
|
149067
|
+
const inlineFooterJsx = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148746
149068
|
children: [
|
|
148747
149069
|
paletteJsx,
|
|
148748
149070
|
composerJsx
|
|
148749
149071
|
]
|
|
148750
149072
|
}, undefined, true, undefined, this);
|
|
148751
149073
|
if (fullscreen) {
|
|
148752
|
-
return /* @__PURE__ */
|
|
149074
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148753
149075
|
flexDirection: "column",
|
|
148754
149076
|
width,
|
|
148755
149077
|
height: rows,
|
|
148756
149078
|
children: [
|
|
148757
|
-
/* @__PURE__ */
|
|
149079
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
|
|
148758
149080
|
model: modelLabel,
|
|
148759
149081
|
cwd: basename3(process.cwd()),
|
|
148760
149082
|
width
|
|
148761
149083
|
}, undefined, false, undefined, this),
|
|
148762
|
-
|
|
149084
|
+
panel ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
149085
|
+
paddingX: 1,
|
|
149086
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Panel, {
|
|
149087
|
+
panel,
|
|
149088
|
+
width: panelW,
|
|
149089
|
+
height: transcriptHeight,
|
|
149090
|
+
accounts: panelAccountView,
|
|
149091
|
+
models: panelModels,
|
|
149092
|
+
currentModelId: panelCurrentModel,
|
|
149093
|
+
staticLines: panelStaticLines
|
|
149094
|
+
}, undefined, false, undefined, this)
|
|
149095
|
+
}, undefined, false, undefined, this) : welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148763
149096
|
height: transcriptHeight,
|
|
148764
149097
|
flexDirection: "column",
|
|
148765
149098
|
justifyContent: "center",
|
|
148766
149099
|
children: hero
|
|
148767
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */
|
|
149100
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148768
149101
|
paddingX: 1,
|
|
148769
|
-
children: /* @__PURE__ */
|
|
149102
|
+
children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Viewport, {
|
|
148770
149103
|
lines,
|
|
148771
149104
|
scrollTop: effScroll,
|
|
148772
149105
|
height: transcriptHeight,
|
|
@@ -148778,24 +149111,24 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148778
149111
|
]
|
|
148779
149112
|
}, undefined, true, undefined, this);
|
|
148780
149113
|
}
|
|
148781
|
-
const banner = /* @__PURE__ */
|
|
149114
|
+
const banner = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Banner, {
|
|
148782
149115
|
model: modelLabel,
|
|
148783
149116
|
cwd: basename3(process.cwd()),
|
|
148784
149117
|
width
|
|
148785
149118
|
}, undefined, false, undefined, this);
|
|
148786
|
-
return /* @__PURE__ */
|
|
149119
|
+
return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148787
149120
|
flexDirection: "column",
|
|
148788
149121
|
width,
|
|
148789
149122
|
children: [
|
|
148790
|
-
welcome ? /* @__PURE__ */
|
|
149123
|
+
welcome ? /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
|
|
148791
149124
|
children: [
|
|
148792
149125
|
banner,
|
|
148793
|
-
/* @__PURE__ */
|
|
149126
|
+
/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
|
|
148794
149127
|
marginTop: 1,
|
|
148795
149128
|
children: hero
|
|
148796
149129
|
}, undefined, false, undefined, this)
|
|
148797
149130
|
]
|
|
148798
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */
|
|
149131
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Transcript, {
|
|
148799
149132
|
items,
|
|
148800
149133
|
width,
|
|
148801
149134
|
header: banner,
|
|
@@ -148809,7 +149142,7 @@ Example: /mcp add github npx -y @modelcontextprotocol/server-github`);
|
|
|
148809
149142
|
// src/cli.tsx
|
|
148810
149143
|
init_providers();
|
|
148811
149144
|
init_permission();
|
|
148812
|
-
var
|
|
149145
|
+
var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
|
|
148813
149146
|
process.env.LANG = process.env.LANG || "en_US.UTF-8";
|
|
148814
149147
|
process.env.LC_ALL = process.env.LC_ALL || "en_US.UTF-8";
|
|
148815
149148
|
var VERSION16 = "0.1.32";
|
|
@@ -149307,7 +149640,7 @@ if (fullscreen)
|
|
|
149307
149640
|
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
|
|
149308
149641
|
if (mouse)
|
|
149309
149642
|
process.stdout.write("\x1B[?1000h\x1B[?1002h\x1B[?1006h");
|
|
149310
|
-
var app = render_default(/* @__PURE__ */
|
|
149643
|
+
var app = render_default(/* @__PURE__ */ jsx_dev_runtime14.jsxDEV(App2, {
|
|
149311
149644
|
selector,
|
|
149312
149645
|
fullscreen,
|
|
149313
149646
|
resumeId
|
package/package.json
CHANGED