compact-agent 1.31.1 → 1.32.0

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.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Command palette catalog — the slash commands exposed via the
3
+ * Space-at-empty quick picker. Hand-curated rather than auto-
4
+ * extracted from handleSlashCommand's switch, because:
5
+ *
6
+ * 1. The switch contains alias arms (/branch → /fork, /quit → /exit,
7
+ * etc.) that would clutter a UI listing.
8
+ * 2. Some commands are internal sentinels (__DICTATE__, __SWARM__)
9
+ * that should never appear to users.
10
+ * 3. Curating gives us a tight description column for each entry —
11
+ * the picker shows label + hint + description, and a hand-
12
+ * written one-liner is more scannable than a parsed comment.
13
+ *
14
+ * Categories mirror /help's grouping so muscle memory transfers.
15
+ */
16
+ export interface CommandEntry {
17
+ command: string;
18
+ description: string;
19
+ category: string;
20
+ }
21
+ export declare const COMMAND_CATALOG: CommandEntry[];
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Command palette catalog — the slash commands exposed via the
3
+ * Space-at-empty quick picker. Hand-curated rather than auto-
4
+ * extracted from handleSlashCommand's switch, because:
5
+ *
6
+ * 1. The switch contains alias arms (/branch → /fork, /quit → /exit,
7
+ * etc.) that would clutter a UI listing.
8
+ * 2. Some commands are internal sentinels (__DICTATE__, __SWARM__)
9
+ * that should never appear to users.
10
+ * 3. Curating gives us a tight description column for each entry —
11
+ * the picker shows label + hint + description, and a hand-
12
+ * written one-liner is more scannable than a parsed comment.
13
+ *
14
+ * Categories mirror /help's grouping so muscle memory transfers.
15
+ */
16
+ export const COMMAND_CATALOG = [
17
+ // ── General ──
18
+ { command: '/help', description: 'Show the full command reference', category: 'General' },
19
+ { command: '/clear', description: 'Reset the conversation (also resets side-channel state)', category: 'General' },
20
+ { command: '/back', description: 'Rewind to before the nth most-recent user turn', category: 'General' },
21
+ { command: '/fork', description: 'Branch the current conversation; previous still resumable', category: 'General' },
22
+ { command: '/btw', description: 'Side question that doesn\'t pollute the main thread', category: 'General' },
23
+ { command: '/editor', description: 'Open $EDITOR for a multi-line prompt', category: 'General' },
24
+ { command: '/history', description: 'Message count + token estimate', category: 'General' },
25
+ { command: '/export', description: 'Export conversation (md/json/txt)', category: 'General' },
26
+ { command: '/walkthrough', description: 'Agent-led tour of compact-agent', category: 'General' },
27
+ { command: '/config', description: 'Reconfigure provider / model / key (re-runs the setup wizard)', category: 'General' },
28
+ { command: '/theme', description: 'Change display mode (full/compact/minimal)', category: 'General' },
29
+ { command: '/palette', description: 'Switch color palette (run /palettes to list)', category: 'General' },
30
+ // ── Model & Provider ──
31
+ { command: '/model', description: 'Switch model (no arg → interactive picker on OpenRouter)', category: 'Model' },
32
+ { command: '/models', description: 'List available models for the current provider', category: 'Model' },
33
+ { command: '/fallback', description: 'Set/show the model auto-retried on cryptic errors', category: 'Model' },
34
+ { command: '/provider', description: 'Show provider info (URL, masked key)', category: 'Model' },
35
+ { command: '/keys', description: 'Multi-key rotation pool (/keys add, status, remove)', category: 'Model' },
36
+ { command: '/route', description: 'Auto-route to a model based on the next message', category: 'Model' },
37
+ // ── Modes ──
38
+ { command: '/mode', description: 'Switch mode (dev/review/tdd/research/plan/debug/architect/hermes/design)', category: 'Modes' },
39
+ { command: '/modes', description: 'List all available modes', category: 'Modes' },
40
+ { command: '/hermes', description: 'Switch to Hermes mode (self-improving learning loop)', category: 'Modes' },
41
+ { command: '/design', description: 'Switch to design mode (Stitch-powered UI work)', category: 'Modes' },
42
+ // ── Session ──
43
+ { command: '/sessions', description: 'List saved sessions', category: 'Session' },
44
+ { command: '/save', description: 'Save current session', category: 'Session' },
45
+ { command: '/resume', description: 'Resume a saved session (accepts id, prefix, or "last")', category: 'Session' },
46
+ { command: '/delete', description: 'Delete a session', category: 'Session' },
47
+ // ── Git ──
48
+ { command: '/commit', description: 'AI-generated commit message', category: 'Git' },
49
+ { command: '/pr', description: 'AI-generated pull request', category: 'Git' },
50
+ { command: '/diff', description: 'Show git diff', category: 'Git' },
51
+ { command: '/log', description: 'Show git log', category: 'Git' },
52
+ // ── Code Quality ──
53
+ { command: '/review', description: 'AI code review (severity-rated findings)', category: 'Code Quality' },
54
+ { command: '/auto-review', description: 'Code review with auto-detected language lens', category: 'Code Quality' },
55
+ { command: '/tdd', description: 'Test-driven workflow (RED → GREEN → REFACTOR)', category: 'Code Quality' },
56
+ { command: '/security-review', description: 'Security-focused audit', category: 'Code Quality' },
57
+ { command: '/audit', description: 'Local-only project health check', category: 'Code Quality' },
58
+ { command: '/verify', description: 'Run tests, fix failures, repeat until green', category: 'Code Quality' },
59
+ { command: '/build-fix', description: 'Auto-detect language + fix build errors', category: 'Code Quality' },
60
+ { command: '/test-coverage', description: 'Analyze coverage, suggest missing tests', category: 'Code Quality' },
61
+ { command: '/refactor', description: 'Dead code detection + cleanup', category: 'Code Quality' },
62
+ { command: '/hunt-silent', description: 'Silent-failure-hunter agent (empty catches, log-and-forget)', category: 'Code Quality' },
63
+ { command: '/explore', description: 'Code-explorer agent (codebase reconnaissance pass)', category: 'Code Quality' },
64
+ { command: '/types', description: 'Type-design-analyzer agent (type system critique)', category: 'Code Quality' },
65
+ { command: '/architect', description: 'Code-architect agent (structural critique)', category: 'Code Quality' },
66
+ { command: '/simplify', description: 'Code-simplifier agent (find + collapse incidental complexity)', category: 'Code Quality' },
67
+ { command: '/e2e', description: 'Generate E2E tests', category: 'Code Quality' },
68
+ // ── Tools & Config ──
69
+ { command: '/tools', description: 'List currently-available tools', category: 'Config' },
70
+ { command: '/perm', description: 'Permission mode (ask/auto/yolo)', category: 'Config' },
71
+ { command: '/sandbox', description: 'OS-native bash sandbox (off/standard/strict)', category: 'Config' },
72
+ { command: '/dry-run', description: 'Toggle dry-run mode (preview tool calls)', category: 'Config' },
73
+ { command: '/thinking', description: 'Toggle thinking/reasoning display', category: 'Config' },
74
+ { command: '/cd', description: 'Change working directory', category: 'Config' },
75
+ { command: '/hooks', description: 'List configured hooks', category: 'Config' },
76
+ // ── Planning ──
77
+ { command: '/plan', description: 'Structured implementation planning', category: 'Planning' },
78
+ { command: '/checkpoint', description: 'Save git-state checkpoint inside this session', category: 'Planning' },
79
+ { command: '/search-first', description: 'Research before coding', category: 'Planning' },
80
+ { command: '/update-docs', description: 'Sync documentation with code', category: 'Planning' },
81
+ // ── Orchestration ──
82
+ { command: '/orchestrate', description: 'Decompose into parallel sub-agents', category: 'Orchestration' },
83
+ { command: '/swarm', description: 'Parallel fan-out: N agents on the same task', category: 'Orchestration' },
84
+ { command: '/multi-plan', description: 'Multi-agent planning', category: 'Orchestration' },
85
+ // ── Skills & Memory ──
86
+ { command: '/skills', description: 'List learned + bundled ECC skills', category: 'Skills' },
87
+ { command: '/skill-show', description: 'Print the full prompt of a specific skill', category: 'Skills' },
88
+ { command: '/ecc-guide', description: 'Browse the bundled ECC corpus', category: 'Skills' },
89
+ { command: '/memory', description: 'MemPalace: status, search, recall, list', category: 'Skills' },
90
+ { command: '/learn', description: 'Extract patterns from this session into instincts', category: 'Skills' },
91
+ // ── Cost & Usage ──
92
+ { command: '/usage', description: 'Token + cost summary', category: 'Cost' },
93
+ { command: '/budget', description: 'Set daily/monthly USD budget', category: 'Cost' },
94
+ // ── Debug ──
95
+ { command: '/debug', description: 'Toggle debug instrumentation + tail event log', category: 'Debug' },
96
+ // ── Voice / Accessibility ──
97
+ { command: '/voice', description: 'Voice config + master switch', category: 'Voice' },
98
+ { command: '/accessibility', description: 'Screen-reader mode, audio cues, destructive-confirm', category: 'Voice' },
99
+ { command: '/dictate', description: 'One-shot record + transcribe', category: 'Voice' },
100
+ // ── Stitch ──
101
+ { command: '/stitch', description: 'Show Stitch config status', category: 'Stitch' },
102
+ { command: '/stitch-config', description: 'Save your Stitch API key', category: 'Stitch' },
103
+ // ── Exit ──
104
+ { command: '/exit', description: 'Quit the REPL', category: 'General' },
105
+ ];
106
+ //# sourceMappingURL=command-palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-palette.js","sourceRoot":"","sources":["../src/command-palette.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -306,6 +306,50 @@ function printResumedHistory(messages, sessionName) {
306
306
  console.log(d(sep));
307
307
  console.log('');
308
308
  }
309
+ /**
310
+ * Render the decorative prompt parts (session timer + mode tag)
311
+ * via direct stdout writes, then call rl.question with ONLY the
312
+ * raw glyph + a trailing space.
313
+ *
314
+ * Why: readline counts every byte of its prompt argument toward
315
+ * cursor-positioning math, including ANSI color escape sequences
316
+ * which take ~10 chars to emit but contribute 0 visible width.
317
+ * When the user's typed input crosses the terminal's wrap boundary,
318
+ * readline does `cursorTo + clearScreenDown + redraw`. If its
319
+ * prompt-width count is off, the redraw lands on the wrong row and
320
+ * the visible prompt prefix gets duplicated mid-line (observed in
321
+ * user testing on Windows ConHost with our themed `[5s] [design] ❯`
322
+ * prompt).
323
+ *
324
+ * The fix: bypass readline's accounting for the decorative parts.
325
+ * Write them directly via process.stdout.write (terminal handles
326
+ * them as cursor-visible output, readline never sees them). Pass
327
+ * only the bare glyph (no ANSI codes) to rl.question. With the
328
+ * prompt argument now byte-accurate to its visible width, the
329
+ * wrap-redraw math is correct.
330
+ *
331
+ * Trade-offs:
332
+ * - If the user edits the line (backspace, Ctrl+U, etc.), the
333
+ * decorative prefix doesn't update — but it doesn't NEED to,
334
+ * it's not part of the input.
335
+ * - Screen readers still see the decorative tags (they're plain
336
+ * stdout output) AND the glyph (as the actual prompt).
337
+ * - In screen-reader mode the caller passes an empty sessionTag
338
+ * and modeTag, so this is effectively a no-op there.
339
+ */
340
+ async function askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph) {
341
+ const decorative = sessionTag + modeTag;
342
+ if (decorative.length > 0) {
343
+ process.stdout.write(decorative);
344
+ }
345
+ // theme.prompt wraps in ANSI codes; we keep the styled glyph for
346
+ // visual continuity, but the rest of readline's prompt argument
347
+ // is now a single short colored string instead of three. The
348
+ // remaining mismatch (color codes around the glyph) is bounded
349
+ // and small enough that wrap math stays correct for typical
350
+ // terminal widths.
351
+ return rl.question(theme.prompt(promptGlyph));
352
+ }
309
353
  /**
310
354
  * Parse slash command respecting quoted strings
311
355
  */
@@ -766,10 +810,20 @@ export function handleSlashCommand(input, config, messages, session, mode) {
766
810
  resetClient();
767
811
  console.log(chalk.green(` Model: ${config.model} (custom)`));
768
812
  }
813
+ return { handled: true };
769
814
  }
770
- else {
771
- console.log(chalk.dim(` Current: ${config.model}`));
815
+ // No args. On OpenRouter, the user wants the interactive
816
+ // picker (catalog + pricing). On other providers we don't
817
+ // have an equivalent catalog endpoint, so keep the legacy
818
+ // "show current" behavior.
819
+ if (/openrouter/i.test(config.provider)) {
820
+ // The picker is async; handleSlashCommand is sync. Use the
821
+ // sentinel-injectPrompt pattern that /dictate + /swarm
822
+ // already use to defer async work to the main REPL loop.
823
+ return { handled: true, injectPrompt: '__PICK_MODEL__' };
772
824
  }
825
+ console.log(chalk.dim(` Current: ${config.model}`));
826
+ console.log(chalk.dim(' (interactive picker is OpenRouter-only — pass a model name explicitly with /model <id>)'));
773
827
  return { handled: true };
774
828
  case '/models':
775
829
  printModelOptions(config);
@@ -2671,10 +2725,17 @@ async function main() {
2671
2725
  'tab', // Shift+Tab cycles perm modes
2672
2726
  'escape', // Esc-Esc rewind at empty prompt
2673
2727
  ',', '.', // Alt+, / Alt+. reasoning effort
2728
+ 'space', // Space at empty prompt → command palette
2674
2729
  // Shifted F-keys carry the Tier-2 and Tier-3 a11y functions. Each
2675
2730
  // is checked alongside key.shift below, so a bare F1 still routes
2676
2731
  // to "status" while Shift+F1 routes to "queued input."
2677
2732
  ]);
2733
+ // Re-entry guard for the picker. The keypress listener can fire
2734
+ // again while the picker is still in alt-screen mode (the user
2735
+ // pressed something the picker handled but we still saw the
2736
+ // 'keypress' event), so we need to make sure we don't open a
2737
+ // second picker on top of the first.
2738
+ let pickerActive = false;
2678
2739
  // Define the hotkey listener as a NAMED, TAGGED function so
2679
2740
  // suppressInputDuringStream() in query.ts can isolate it among stdin's
2680
2741
  // 'keypress' listeners. During streaming we detach readline's own
@@ -2705,6 +2766,12 @@ async function main() {
2705
2766
  return;
2706
2767
  if (name === 'escape' && (shift || ctrl || meta))
2707
2768
  return;
2769
+ // Space is ours ONLY when the input buffer is empty and we're
2770
+ // at a prompt (not mid-streaming). The check happens in the
2771
+ // dedicated branch below; here we just defer if there's any
2772
+ // modifier (Shift+Space, Ctrl+Space, etc. aren't us).
2773
+ if (name === 'space' && (shift || ctrl || meta))
2774
+ return;
2708
2775
  const a = getAccessibilityConfig(config);
2709
2776
  const tts = getTtsConfig(config);
2710
2777
  // Helper: print to stdout (always — picked up by the OS screen reader)
@@ -2877,6 +2944,70 @@ async function main() {
2877
2944
  // Any other shifted F-key: no-op (don't fall through to bare).
2878
2945
  return;
2879
2946
  }
2947
+ // ── Space (bare): command palette at empty prompt ──
2948
+ // Pressing Space when the input buffer is empty opens the
2949
+ // command palette — an alt-screen picker showing every slash
2950
+ // command, arrow-key navigable, type-to-filter, Enter to run.
2951
+ // When the buffer has content (the user is typing a real
2952
+ // message that begins with a space-separated word) the keypress
2953
+ // listener stays out of the way and lets readline handle the
2954
+ // space normally.
2955
+ if (name === 'space') {
2956
+ if (pickerActive)
2957
+ return;
2958
+ const buf = rl.line ?? '';
2959
+ if (buf.length > 0)
2960
+ return; // mid-input — let readline insert the space
2961
+ // Mid-stream space is suppressed by the input guard already;
2962
+ // this listener still fires but we shouldn't open a picker
2963
+ // on top of a streaming turn.
2964
+ const turnCtl = globalThis.__turnAbortCtl;
2965
+ if (turnCtl && !turnCtl.signal.aborted)
2966
+ return;
2967
+ // Open the palette. The picker is async and takes stdin into
2968
+ // raw mode for its lifetime — we fire-and-forget here. On
2969
+ // selection, queue the command and resolve rl.question so the
2970
+ // main loop dispatches it.
2971
+ pickerActive = true;
2972
+ // The space character is already in readline's buffer at
2973
+ // this point (the keypress listener is an observer, not a
2974
+ // gate). Clear it so the prompt is clean once the picker
2975
+ // exits — without this, the selected command would be
2976
+ // appended after the stray space.
2977
+ try {
2978
+ const rlAny = rl;
2979
+ rlAny.line = '';
2980
+ rlAny._refreshLine?.();
2981
+ }
2982
+ catch { /* noop */ }
2983
+ void (async () => {
2984
+ try {
2985
+ const { pick } = await import('./picker.js');
2986
+ const { COMMAND_CATALOG } = await import('./command-palette.js');
2987
+ const items = COMMAND_CATALOG.map((c) => ({
2988
+ label: c.command,
2989
+ hint: c.category,
2990
+ description: c.description,
2991
+ value: c.command,
2992
+ }));
2993
+ const selected = await pick(items, {
2994
+ title: 'compact-agent · command palette',
2995
+ footer: 'type to filter · ↑↓ to navigate · Enter to run · Esc to cancel',
2996
+ });
2997
+ if (selected) {
2998
+ globalThis.__crowcoderQueuedInput = selected + '\n';
2999
+ }
3000
+ }
3001
+ finally {
3002
+ pickerActive = false;
3003
+ try {
3004
+ rl.emit('line', '');
3005
+ }
3006
+ catch { /* noop */ }
3007
+ }
3008
+ })();
3009
+ return;
3010
+ }
2880
3011
  // ── Esc (bare): rewind chord at empty prompt ───────
2881
3012
  // Two bare Esc presses within 500ms at an empty input buffer
2882
3013
  // triggers /back (rewind one user turn). Matches the muscle
@@ -3235,7 +3366,7 @@ async function main() {
3235
3366
  input = next;
3236
3367
  }
3237
3368
  else {
3238
- input = await rl.question(sessionTag + modeTag + theme.prompt(promptGlyph));
3369
+ input = await askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph);
3239
3370
  }
3240
3371
  }
3241
3372
  else {
@@ -3244,7 +3375,7 @@ async function main() {
3244
3375
  // can paste/retype at the prompt.
3245
3376
  console.log(theme.dim(` (queued during last chain: "${queued.trim().slice(0, 80)}${queued.length > 80 ? '…' : ''}")`));
3246
3377
  }
3247
- input = await rl.question(sessionTag + modeTag + theme.prompt(promptGlyph));
3378
+ input = await askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph);
3248
3379
  }
3249
3380
  }
3250
3381
  catch {
@@ -3305,6 +3436,42 @@ async function main() {
3305
3436
  console.log(theme.dim(' [dictate] ') + chalk.white(transcript));
3306
3437
  messages.push({ role: 'user', content: transcript });
3307
3438
  }
3439
+ else if (result.injectPrompt === '__PICK_MODEL__') {
3440
+ // OpenRouter model picker — same sentinel-into-the-REPL
3441
+ // pattern as /dictate + /swarm because handleSlashCommand
3442
+ // is sync but fetching the catalog + showing the picker
3443
+ // is async. Selection sets config.model + saves; no
3444
+ // injectPrompt cascades into runQuery.
3445
+ const { pick } = await import('./picker.js');
3446
+ const { fetchOpenRouterModels, formatPricing } = await import('./openrouter-models.js');
3447
+ console.log(chalk.dim(' Fetching OpenRouter catalog…'));
3448
+ const models = await fetchOpenRouterModels();
3449
+ if (models.length === 0) {
3450
+ console.log(chalk.yellow(' Could not fetch model catalog (network error or rate limit).'));
3451
+ console.log(chalk.dim(' Use /model <id> with a known model name, or check your connection.'));
3452
+ continue;
3453
+ }
3454
+ const items = models.map((m) => ({
3455
+ label: m.id,
3456
+ hint: formatPricing(m),
3457
+ description: m.name !== m.id ? m.name : undefined,
3458
+ value: m.id,
3459
+ }));
3460
+ const selected = await pick(items, {
3461
+ title: `compact-agent · OpenRouter models (current: ${config.model})`,
3462
+ footer: 'type to filter · ↑↓ to navigate · Enter to pick · Esc to cancel · free models float to the top',
3463
+ });
3464
+ if (selected) {
3465
+ config.model = selected;
3466
+ saveConfig(config);
3467
+ resetClient();
3468
+ console.log(chalk.green(` Model: ${config.model}`));
3469
+ }
3470
+ else {
3471
+ console.log(chalk.dim(' Cancelled — model unchanged.'));
3472
+ }
3473
+ continue;
3474
+ }
3308
3475
  else if (result.injectPrompt.startsWith('__SWARM__')) {
3309
3476
  // Swarm dispatch: __SWARM__<agentsCsv>|||<task>. Same sentinel
3310
3477
  // trick as /dictate so the slash handler stays sync; the async