compact-agent 1.31.2 → 1.32.1

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
@@ -351,7 +351,21 @@ async function askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph) {
351
351
  return rl.question(theme.prompt(promptGlyph));
352
352
  }
353
353
  /**
354
- * Parse slash command respecting quoted strings
354
+ * Parse a slash command into (cmd, args).
355
+ *
356
+ * Args normalization: leading + trailing angle brackets are stripped
357
+ * automatically. The /help text uses `<arg>` as placeholder syntax
358
+ * (e.g. `/resume <session-id>`, `/model <name>`), and users routinely
359
+ * paste the placeholder literally — `/resume <abc123>` instead of
360
+ * `/resume abc123`. Stripping at the parser level means every
361
+ * command gets the same forgiving treatment without each handler
362
+ * having to defensively unwrap.
363
+ *
364
+ * Quotes / brackets other than `<>` are intentionally NOT stripped
365
+ * here. Some commands legitimately take quoted strings (e.g.
366
+ * `/article "How to be a vibe coder"`) — those commands should
367
+ * handle their own quote semantics. Only the `<>` placeholder
368
+ * pattern is normalized universally.
355
369
  */
356
370
  function parseSlashCommand(input) {
357
371
  const trimmed = input.trim();
@@ -360,8 +374,14 @@ function parseSlashCommand(input) {
360
374
  return { cmd: trimmed.toLowerCase(), args: '' };
361
375
  }
362
376
  const cmd = trimmed.slice(0, spaceIdx).toLowerCase();
363
- const argsRaw = trimmed.slice(spaceIdx + 1);
364
- // Keep quoted strings intact
377
+ let argsRaw = trimmed.slice(spaceIdx + 1).trim();
378
+ // Strip leading + trailing `<>` if they wrap the entire arg
379
+ // string. Keeps `<>` inside the arg untouched (e.g. paths with
380
+ // angle brackets in regex-style queries) — only the outermost
381
+ // wrap is removed.
382
+ if (argsRaw.startsWith('<') && argsRaw.endsWith('>') && argsRaw.length > 2) {
383
+ argsRaw = argsRaw.slice(1, -1).trim();
384
+ }
365
385
  return { cmd, args: argsRaw };
366
386
  }
367
387
  // ── Slash Commands ────────────────────────────────────────
@@ -810,10 +830,20 @@ export function handleSlashCommand(input, config, messages, session, mode) {
810
830
  resetClient();
811
831
  console.log(chalk.green(` Model: ${config.model} (custom)`));
812
832
  }
833
+ return { handled: true };
813
834
  }
814
- else {
815
- console.log(chalk.dim(` Current: ${config.model}`));
835
+ // No args. On OpenRouter, the user wants the interactive
836
+ // picker (catalog + pricing). On other providers we don't
837
+ // have an equivalent catalog endpoint, so keep the legacy
838
+ // "show current" behavior.
839
+ if (/openrouter/i.test(config.provider)) {
840
+ // The picker is async; handleSlashCommand is sync. Use the
841
+ // sentinel-injectPrompt pattern that /dictate + /swarm
842
+ // already use to defer async work to the main REPL loop.
843
+ return { handled: true, injectPrompt: '__PICK_MODEL__' };
816
844
  }
845
+ console.log(chalk.dim(` Current: ${config.model}`));
846
+ console.log(chalk.dim(' (interactive picker is OpenRouter-only — pass a model name explicitly with /model <id>)'));
817
847
  return { handled: true };
818
848
  case '/models':
819
849
  printModelOptions(config);
@@ -2538,7 +2568,34 @@ export function handleSlashCommand(input, config, messages, session, mode) {
2538
2568
  }
2539
2569
  // ── Main ──────────────────────────────────────────────────
2540
2570
  async function main() {
2541
- const rl = readline.createInterface({ input: stdin, output: stdout });
2571
+ // Slash-command completer: bash-style Tab completion. Triggered
2572
+ // only when the typed line starts with '/' so it doesn't interfere
2573
+ // with regular prose. When the user has typed a unique prefix, Tab
2574
+ // completes to the full command. When multiple commands share the
2575
+ // prefix, readline's default behavior is to print the list of
2576
+ // candidates on the next Tab press — same UX as bash/zsh.
2577
+ //
2578
+ // This is COMPLEMENTARY to the '/' keypress trigger that opens the
2579
+ // alt-screen picker: that's the "I want to browse" path; this is
2580
+ // the "I know what I'm typing, just save me keystrokes" path. The
2581
+ // two coexist because pressing '/' fires the picker only at an
2582
+ // empty buffer — once the user has typed beyond '/', Tab takes
2583
+ // over via this completer.
2584
+ const { COMMAND_CATALOG } = await import('./command-palette.js');
2585
+ const slashCommandNames = COMMAND_CATALOG.map((c) => c.command);
2586
+ const rl = readline.createInterface({
2587
+ input: stdin,
2588
+ output: stdout,
2589
+ completer: (line) => {
2590
+ if (!line.startsWith('/'))
2591
+ return [[], line];
2592
+ const matches = slashCommandNames.filter((c) => c.startsWith(line));
2593
+ // readline contract: return [matches, lineWeMatchedAgainst].
2594
+ // If matches is empty, readline beeps; otherwise it completes
2595
+ // to common prefix or lists candidates.
2596
+ return [matches.length > 0 ? matches : slashCommandNames, line];
2597
+ },
2598
+ });
2542
2599
  // Initialize subsystems
2543
2600
  initHooksDir();
2544
2601
  // First-run ECC install — silent if already installed, silent if resources missing.
@@ -2715,10 +2772,18 @@ async function main() {
2715
2772
  'tab', // Shift+Tab cycles perm modes
2716
2773
  'escape', // Esc-Esc rewind at empty prompt
2717
2774
  ',', '.', // Alt+, / Alt+. reasoning effort
2775
+ 'space', // Space at empty prompt → command palette
2776
+ '/', // / at empty prompt → palette filtered to '/'
2718
2777
  // Shifted F-keys carry the Tier-2 and Tier-3 a11y functions. Each
2719
2778
  // is checked alongside key.shift below, so a bare F1 still routes
2720
2779
  // to "status" while Shift+F1 routes to "queued input."
2721
2780
  ]);
2781
+ // Re-entry guard for the picker. The keypress listener can fire
2782
+ // again while the picker is still in alt-screen mode (the user
2783
+ // pressed something the picker handled but we still saw the
2784
+ // 'keypress' event), so we need to make sure we don't open a
2785
+ // second picker on top of the first.
2786
+ let pickerActive = false;
2722
2787
  // Define the hotkey listener as a NAMED, TAGGED function so
2723
2788
  // suppressInputDuringStream() in query.ts can isolate it among stdin's
2724
2789
  // 'keypress' listeners. During streaming we detach readline's own
@@ -2749,6 +2814,17 @@ async function main() {
2749
2814
  return;
2750
2815
  if (name === 'escape' && (shift || ctrl || meta))
2751
2816
  return;
2817
+ // Space is ours ONLY when the input buffer is empty and we're
2818
+ // at a prompt (not mid-streaming). The check happens in the
2819
+ // dedicated branch below; here we just defer if there's any
2820
+ // modifier (Shift+Space, Ctrl+Space, etc. aren't us).
2821
+ if (name === 'space' && (shift || ctrl || meta))
2822
+ return;
2823
+ // Slash autocomplete: '/' at empty prompt opens the picker
2824
+ // pre-filtered to '/'. Modified variants (Ctrl+/, etc.) are
2825
+ // not ours.
2826
+ if (name === '/' && (shift || ctrl || meta))
2827
+ return;
2752
2828
  const a = getAccessibilityConfig(config);
2753
2829
  const tts = getTtsConfig(config);
2754
2830
  // Helper: print to stdout (always — picked up by the OS screen reader)
@@ -2921,6 +2997,103 @@ async function main() {
2921
2997
  // Any other shifted F-key: no-op (don't fall through to bare).
2922
2998
  return;
2923
2999
  }
3000
+ // ── Space (bare): command palette at empty prompt ──
3001
+ // Pressing Space when the input buffer is empty opens the
3002
+ // command palette — an alt-screen picker showing every slash
3003
+ // command, arrow-key navigable, type-to-filter, Enter to run.
3004
+ // When the buffer has content (the user is typing a real
3005
+ // message that begins with a space-separated word) the keypress
3006
+ // listener stays out of the way and lets readline handle the
3007
+ // space normally.
3008
+ if (name === 'space' || name === '/') {
3009
+ if (pickerActive)
3010
+ return;
3011
+ const buf = rl.line ?? '';
3012
+ // Space is only triggered at an empty buffer (mid-typing
3013
+ // space should insert normally). '/' has a more permissive
3014
+ // trigger: it fires at empty buffer OR at exactly "/"
3015
+ // (which is the state right after the user pressed '/'
3016
+ // and the byte landed in the buffer). Either way, only
3017
+ // empty + single-/ states open the picker; longer buffers
3018
+ // pass through.
3019
+ if (name === 'space' && buf.length > 0)
3020
+ return;
3021
+ if (name === '/' && buf !== '' && buf !== '/')
3022
+ return;
3023
+ // Mid-stream is suppressed by the input guard already;
3024
+ // this listener still fires but we shouldn't open a picker
3025
+ // on top of a streaming turn.
3026
+ const turnCtl = globalThis.__turnAbortCtl;
3027
+ if (turnCtl && !turnCtl.signal.aborted)
3028
+ return;
3029
+ const triggerChar = name === '/' ? '/' : ' ';
3030
+ // Open the palette. The picker is async and takes stdin into
3031
+ // raw mode for its lifetime — we fire-and-forget here.
3032
+ pickerActive = true;
3033
+ // The trigger character is already in readline's buffer at
3034
+ // this point (the keypress listener is an observer, not a
3035
+ // gate). Clear it so the prompt is clean once the picker
3036
+ // exits.
3037
+ try {
3038
+ const rlAny = rl;
3039
+ rlAny.line = '';
3040
+ rlAny._refreshLine?.();
3041
+ }
3042
+ catch { /* noop */ }
3043
+ void (async () => {
3044
+ try {
3045
+ const { pick } = await import('./picker.js');
3046
+ const { COMMAND_CATALOG } = await import('./command-palette.js');
3047
+ const items = COMMAND_CATALOG.map((c) => ({
3048
+ label: c.command,
3049
+ hint: c.category,
3050
+ description: c.description,
3051
+ value: c.command,
3052
+ }));
3053
+ const selected = await pick(items, {
3054
+ title: 'compact-agent · command palette',
3055
+ footer: 'type to filter · ↑↓ to navigate · Enter to run · Esc to cancel',
3056
+ // '/' trigger: pre-fill filter so the user's mental
3057
+ // model ("I typed / and now I see slash commands") is
3058
+ // preserved. Space trigger: blank filter, browse all.
3059
+ initialFilter: name === '/' ? '/' : undefined,
3060
+ });
3061
+ if (selected) {
3062
+ globalThis.__crowcoderQueuedInput = selected + '\n';
3063
+ try {
3064
+ rl.emit('line', '');
3065
+ }
3066
+ catch { /* noop */ }
3067
+ }
3068
+ else if (triggerChar === '/') {
3069
+ // Cancel from '/'-triggered picker: restore the '/'
3070
+ // to the buffer so the user can keep typing the
3071
+ // command manually (e.g. they wanted /model claude-
3072
+ // sonnet-4 directly, not the picker). Don't resolve
3073
+ // rl.question — leave readline waiting for input.
3074
+ try {
3075
+ const rlAny = rl;
3076
+ rlAny.line = '/';
3077
+ rlAny._refreshLine?.();
3078
+ }
3079
+ catch { /* noop */ }
3080
+ }
3081
+ else {
3082
+ // Cancel from space-triggered picker: prompt is clean,
3083
+ // resolve readline with empty so the loop iterates
3084
+ // back to a fresh prompt.
3085
+ try {
3086
+ rl.emit('line', '');
3087
+ }
3088
+ catch { /* noop */ }
3089
+ }
3090
+ }
3091
+ finally {
3092
+ pickerActive = false;
3093
+ }
3094
+ })();
3095
+ return;
3096
+ }
2924
3097
  // ── Esc (bare): rewind chord at empty prompt ───────
2925
3098
  // Two bare Esc presses within 500ms at an empty input buffer
2926
3099
  // triggers /back (rewind one user turn). Matches the muscle
@@ -3349,6 +3522,42 @@ async function main() {
3349
3522
  console.log(theme.dim(' [dictate] ') + chalk.white(transcript));
3350
3523
  messages.push({ role: 'user', content: transcript });
3351
3524
  }
3525
+ else if (result.injectPrompt === '__PICK_MODEL__') {
3526
+ // OpenRouter model picker — same sentinel-into-the-REPL
3527
+ // pattern as /dictate + /swarm because handleSlashCommand
3528
+ // is sync but fetching the catalog + showing the picker
3529
+ // is async. Selection sets config.model + saves; no
3530
+ // injectPrompt cascades into runQuery.
3531
+ const { pick } = await import('./picker.js');
3532
+ const { fetchOpenRouterModels, formatPricing } = await import('./openrouter-models.js');
3533
+ console.log(chalk.dim(' Fetching OpenRouter catalog…'));
3534
+ const models = await fetchOpenRouterModels();
3535
+ if (models.length === 0) {
3536
+ console.log(chalk.yellow(' Could not fetch model catalog (network error or rate limit).'));
3537
+ console.log(chalk.dim(' Use /model <id> with a known model name, or check your connection.'));
3538
+ continue;
3539
+ }
3540
+ const items = models.map((m) => ({
3541
+ label: m.id,
3542
+ hint: formatPricing(m),
3543
+ description: m.name !== m.id ? m.name : undefined,
3544
+ value: m.id,
3545
+ }));
3546
+ const selected = await pick(items, {
3547
+ title: `compact-agent · OpenRouter models (current: ${config.model})`,
3548
+ footer: 'type to filter · ↑↓ to navigate · Enter to pick · Esc to cancel · free models float to the top',
3549
+ });
3550
+ if (selected) {
3551
+ config.model = selected;
3552
+ saveConfig(config);
3553
+ resetClient();
3554
+ console.log(chalk.green(` Model: ${config.model}`));
3555
+ }
3556
+ else {
3557
+ console.log(chalk.dim(' Cancelled — model unchanged.'));
3558
+ }
3559
+ continue;
3560
+ }
3352
3561
  else if (result.injectPrompt.startsWith('__SWARM__')) {
3353
3562
  // Swarm dispatch: __SWARM__<agentsCsv>|||<task>. Same sentinel
3354
3563
  // trick as /dictate so the slash handler stays sync; the async