compact-agent 1.32.1 → 1.33.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.
- package/dist/animations.d.ts +89 -0
- package/dist/animations.js +177 -0
- package/dist/animations.js.map +1 -0
- package/dist/command-palette.js +2 -1
- package/dist/command-palette.js.map +1 -1
- package/dist/index.js +190 -65
- package/dist/index.js.map +1 -1
- package/dist/inline-suggest.d.ts +25 -0
- package/dist/inline-suggest.js +277 -0
- package/dist/inline-suggest.js.map +1 -0
- package/dist/query.js +17 -4
- package/dist/query.js.map +1 -1
- package/dist/theme.d.ts +32 -5
- package/dist/theme.js +250 -22
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import { buildCommitPrompt, buildPRPrompt, printDiff, printLog } from './git-wor
|
|
|
24
24
|
import { buildReviewPrompt, buildTDDPrompt, buildSecurityReviewPrompt, runAudit, printAuditReport, buildPlanPrompt, buildE2EPrompt, buildBuildFixPrompt, buildEvalPrompt } from './evaluation.js';
|
|
25
25
|
import { printRules } from './rules.js';
|
|
26
26
|
import { buildOrchestrationPrompt } from './orchestration.js';
|
|
27
|
-
import { printBanner as printThemedBanner, theme, sym, formatDuration, installScreenReaderDispatch, uninstallScreenReaderDispatch, setPalette, getPaletteId, listPalettes, isPaletteId, PALETTES } from './theme.js';
|
|
27
|
+
import { printBanner as printThemedBanner, theme, sym, formatDuration, installScreenReaderDispatch, uninstallScreenReaderDispatch, setPalette, getPaletteId, listPalettes, isPaletteId, PALETTES, expandLastThinking } from './theme.js';
|
|
28
28
|
import { saveExport } from './export.js';
|
|
29
29
|
// New feature modules
|
|
30
30
|
import { buildVerifyPrompt, saveCheckpoint, listCheckpoints } from './verification.js';
|
|
@@ -472,7 +472,8 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
472
472
|
console.log(d(' ') + c('/perm-reset') + d(' — clear the per-tool always-allow list'));
|
|
473
473
|
console.log(d(' ') + c('/sandbox [level]') + d(' — OS-native bash sandbox (off / standard / strict)'));
|
|
474
474
|
console.log(d(' ') + c('/dry-run') + d(' — toggle dry-run mode'));
|
|
475
|
-
console.log(d(' ') + c('/thinking') + d(' — toggle thinking
|
|
475
|
+
console.log(d(' ') + c('/thinking') + d(' — toggle thinking display (live + auto-collapse)'));
|
|
476
|
+
console.log(d(' ') + c('/think') + d(' — re-expand the most recent collapsed thinking'));
|
|
476
477
|
console.log(d(' ') + c('/cd <path>') + d(' — change directory'));
|
|
477
478
|
console.log(d(' ') + c('/hooks') + d(' — list configured hooks'));
|
|
478
479
|
console.log(d(' ') + c('/reset-hooks') + d(' — wipe hooks.json and re-seed ECC hooks for current install'));
|
|
@@ -1211,10 +1212,53 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1211
1212
|
console.log(chalk.green(` Show thinking: ${thinkingStatus}`));
|
|
1212
1213
|
if (config.showThinking) {
|
|
1213
1214
|
console.log(chalk.dim(' Model reasoning/chain-of-thought will be displayed when available.'));
|
|
1215
|
+
console.log(chalk.dim(' Streams live in a │-bordered panel, then collapses to a one-liner.'));
|
|
1216
|
+
console.log(chalk.dim(' Re-expand the most recent thinking block with /think.'));
|
|
1214
1217
|
console.log(chalk.dim(' Works with DeepSeek, OpenRouter reasoning models, and others.'));
|
|
1215
1218
|
}
|
|
1216
1219
|
return { handled: true };
|
|
1217
1220
|
}
|
|
1221
|
+
case '/think': {
|
|
1222
|
+
// /think (no args) → re-expand the most recent thinking block
|
|
1223
|
+
// (the one that just collapsed to a one-liner footer)
|
|
1224
|
+
// /think on|off → alias for /thinking (toggles show-thinking)
|
|
1225
|
+
//
|
|
1226
|
+
// Mental model: /thinking is the *setting* (display reasoning
|
|
1227
|
+
// at all? yes/no), /think is the *action* (show me that
|
|
1228
|
+
// reasoning again now). Both names appear in the wild — Claude
|
|
1229
|
+
// Code uses /think, other CLIs use /thinking — so we support
|
|
1230
|
+
// both rather than picking a winner.
|
|
1231
|
+
const sub = (args || '').trim().toLowerCase();
|
|
1232
|
+
if (sub === 'on' || sub === 'off') {
|
|
1233
|
+
const wantOn = sub === 'on';
|
|
1234
|
+
if (config.showThinking !== wantOn) {
|
|
1235
|
+
config.showThinking = wantOn;
|
|
1236
|
+
saveConfig(config);
|
|
1237
|
+
}
|
|
1238
|
+
console.log(chalk.green(` Show thinking: ${wantOn ? chalk.yellow('ON') : chalk.green('OFF')}`));
|
|
1239
|
+
return { handled: true };
|
|
1240
|
+
}
|
|
1241
|
+
if (sub === 'toggle' || sub === '') {
|
|
1242
|
+
// Empty args → expand last thinking. If there is none yet
|
|
1243
|
+
// (no model turn this session has emitted reasoning),
|
|
1244
|
+
// surface a helpful hint instead of silently no-op'ing.
|
|
1245
|
+
const ok = expandLastThinking();
|
|
1246
|
+
if (!ok) {
|
|
1247
|
+
console.log(chalk.dim(' No thinking captured yet this session.'));
|
|
1248
|
+
if (config.showThinking === false) {
|
|
1249
|
+
console.log(chalk.dim(' /thinking is currently OFF — run /thinking to enable.'));
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
console.log(chalk.dim(' The current model may not emit reasoning tokens.'));
|
|
1253
|
+
console.log(chalk.dim(' Try a reasoning model: deepseek-r1, o1-mini, etc.'));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return { handled: true };
|
|
1257
|
+
}
|
|
1258
|
+
console.log(chalk.dim(' /think — re-expand the most recent thinking'));
|
|
1259
|
+
console.log(chalk.dim(' /think on | off — enable/disable thinking display'));
|
|
1260
|
+
return { handled: true };
|
|
1261
|
+
}
|
|
1218
1262
|
case '/cd':
|
|
1219
1263
|
if (args) {
|
|
1220
1264
|
try {
|
|
@@ -2492,11 +2536,17 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
2492
2536
|
saveConfig(config);
|
|
2493
2537
|
// Screen-reader mode is special: install/uninstall the stdout filter
|
|
2494
2538
|
// immediately so the toggle takes effect for the very next log line.
|
|
2539
|
+
// Also flip animations off when SR is on — in-place ANSI repaints
|
|
2540
|
+
// (spinners + collapse transitions) read as a flood of new content
|
|
2541
|
+
// events to NVDA/JAWS and drown out actual response text.
|
|
2495
2542
|
if (field === 'screenReader') {
|
|
2496
2543
|
if (v === 'on')
|
|
2497
2544
|
installScreenReaderDispatch(applyScreenReader);
|
|
2498
2545
|
else
|
|
2499
2546
|
uninstallScreenReaderDispatch();
|
|
2547
|
+
void import('./animations.js').then(({ setAnimationConfig }) => {
|
|
2548
|
+
setAnimationConfig({ screenReader: v === 'on' });
|
|
2549
|
+
});
|
|
2500
2550
|
}
|
|
2501
2551
|
console.log(chalk.green(` ${label}: ${v.toUpperCase()}`));
|
|
2502
2552
|
};
|
|
@@ -2653,6 +2703,20 @@ async function main() {
|
|
|
2653
2703
|
installScreenReaderDispatch(applyScreenReader);
|
|
2654
2704
|
console.log('[notice] screen-reader mode is ON — ANSI colors are stripped for NVDA/JAWS compatibility. Turn off with: /accessibility screen-reader off');
|
|
2655
2705
|
}
|
|
2706
|
+
// ── Animation config ─────────────────────────────────────
|
|
2707
|
+
// Wire the global animation flag now that we know the screen-reader
|
|
2708
|
+
// setting. In-place ANSI repaints (used by tool/thinking spinners and
|
|
2709
|
+
// collapse/settle transitions) generate a flood of new content events
|
|
2710
|
+
// for screen readers, so they're force-off in that mode. Sighted
|
|
2711
|
+
// users get them by default; the CROWCODER_ANIMATIONS=0 env var still
|
|
2712
|
+
// overrides for users who specifically don't want the motion.
|
|
2713
|
+
{
|
|
2714
|
+
const { setAnimationConfig } = await import('./animations.js');
|
|
2715
|
+
setAnimationConfig({
|
|
2716
|
+
enabled: process.env.CROWCODER_ANIMATIONS !== '0',
|
|
2717
|
+
screenReader: config.voice?.accessibility?.screenReader === true,
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2656
2720
|
// Create session
|
|
2657
2721
|
const mode = { current: 'dev' };
|
|
2658
2722
|
const session = createSession(process.cwd(), config.model, config.provider, mode.current);
|
|
@@ -2797,8 +2861,26 @@ async function main() {
|
|
|
2797
2861
|
const hotkeyListener = function hotkeyListener(_str, key) {
|
|
2798
2862
|
if (!key)
|
|
2799
2863
|
return;
|
|
2800
|
-
|
|
2801
|
-
|
|
2864
|
+
// Node's readline emitter sets `key.name` for named keys (tab,
|
|
2865
|
+
// space, escape, f1-f12, letters, etc.) but leaves it
|
|
2866
|
+
// UNDEFINED for many printable ASCII chars including '/', ',',
|
|
2867
|
+
// and '.'. For those keys only `key.sequence` is reliable.
|
|
2868
|
+
// Look up against both — name preferred, sequence as fallback —
|
|
2869
|
+
// so '/' (initial Node REPL parse delivers no name, just
|
|
2870
|
+
// sequence) and the Alt+,/. handlers actually fire.
|
|
2871
|
+
const name = (key.name || '').toLowerCase();
|
|
2872
|
+
const seq = (key.sequence || '');
|
|
2873
|
+
const lookup = name || seq;
|
|
2874
|
+
if (!INTERCEPT.has(lookup))
|
|
2875
|
+
return;
|
|
2876
|
+
// While the command palette / inline-suggest is open it takes
|
|
2877
|
+
// exclusive control of stdin via its own `data` listener. The
|
|
2878
|
+
// hotkey listener must bail entirely — otherwise Esc would
|
|
2879
|
+
// trigger the rewind chord, Tab/F-keys would print status
|
|
2880
|
+
// overlays, and Space/'/' would try to open a second picker on
|
|
2881
|
+
// top of the first. The data-level handler in the picker sees
|
|
2882
|
+
// the bytes first and finishes its work; we just stand down.
|
|
2883
|
+
if (pickerActive)
|
|
2802
2884
|
return;
|
|
2803
2885
|
const shift = !!key.shift;
|
|
2804
2886
|
const meta = !!key.meta;
|
|
@@ -2808,7 +2890,7 @@ async function main() {
|
|
|
2808
2890
|
// - bare ',' or '.' is regular typing; only Alt+,/. is ours
|
|
2809
2891
|
// - bare Tab is completion; only Shift+Tab is ours
|
|
2810
2892
|
// - Shift+Esc / Ctrl+Esc / Alt+Esc aren't ours
|
|
2811
|
-
if ((
|
|
2893
|
+
if ((lookup === ',' || lookup === '.') && !meta)
|
|
2812
2894
|
return;
|
|
2813
2895
|
if (name === 'tab' && !shift)
|
|
2814
2896
|
return;
|
|
@@ -2823,7 +2905,7 @@ async function main() {
|
|
|
2823
2905
|
// Slash autocomplete: '/' at empty prompt opens the picker
|
|
2824
2906
|
// pre-filtered to '/'. Modified variants (Ctrl+/, etc.) are
|
|
2825
2907
|
// not ours.
|
|
2826
|
-
if (
|
|
2908
|
+
if (lookup === '/' && (shift || ctrl || meta))
|
|
2827
2909
|
return;
|
|
2828
2910
|
const a = getAccessibilityConfig(config);
|
|
2829
2911
|
const tts = getTtsConfig(config);
|
|
@@ -2848,7 +2930,7 @@ async function main() {
|
|
|
2848
2930
|
name === 'f11' || name === 'f12' || shift ||
|
|
2849
2931
|
// Productivity bindings (Shift+Tab, Esc, Alt+,/.) work regardless
|
|
2850
2932
|
// of voice state — they touch config / readline, not audio.
|
|
2851
|
-
name === 'tab' || name === 'escape' ||
|
|
2933
|
+
name === 'tab' || name === 'escape' || lookup === ',' || lookup === '.' || lookup === '/';
|
|
2852
2934
|
// F5–F10 (bare) are DICTATION/PLAYBACK hotkeys — they only make
|
|
2853
2935
|
// sense when voice features are enabled. Bail early to avoid
|
|
2854
2936
|
// spurious ffmpeg spawns and "TTS not configured" log lines.
|
|
@@ -2997,15 +3079,26 @@ async function main() {
|
|
|
2997
3079
|
// Any other shifted F-key: no-op (don't fall through to bare).
|
|
2998
3080
|
return;
|
|
2999
3081
|
}
|
|
3000
|
-
// ── Space (bare): command palette at empty prompt ──
|
|
3001
|
-
//
|
|
3002
|
-
//
|
|
3003
|
-
//
|
|
3004
|
-
//
|
|
3005
|
-
//
|
|
3006
|
-
//
|
|
3007
|
-
//
|
|
3008
|
-
|
|
3082
|
+
// ── Space (bare) / '/' (bare): command palette at empty prompt ──
|
|
3083
|
+
// Two distinct UX shapes that share the same trigger guards:
|
|
3084
|
+
//
|
|
3085
|
+
// Space → full-screen browse picker (alt-screen takeover).
|
|
3086
|
+
// User explicitly asked to browse every command, so
|
|
3087
|
+
// a big sortable list with descriptions, category
|
|
3088
|
+
// hints, and a footer is exactly what they want.
|
|
3089
|
+
//
|
|
3090
|
+
// '/' → inline dropdown rendered directly below the
|
|
3091
|
+
// prompt (no alt-screen). The user is in the middle
|
|
3092
|
+
// of typing a command — they need to KEEP seeing
|
|
3093
|
+
// their chat history and the prompt context, not
|
|
3094
|
+
// have a full-screen widget yanked over them. The
|
|
3095
|
+
// dropdown narrows as they type and disappears on
|
|
3096
|
+
// Esc or Backspace-to-empty.
|
|
3097
|
+
//
|
|
3098
|
+
// Both branches share the trigger guards (no buffer content,
|
|
3099
|
+
// no active stream) and the pickerActive interlock that
|
|
3100
|
+
// prevents stacking two pickers.
|
|
3101
|
+
if (name === 'space' || lookup === '/') {
|
|
3009
3102
|
if (pickerActive)
|
|
3010
3103
|
return;
|
|
3011
3104
|
const buf = rl.line ?? '';
|
|
@@ -3018,7 +3111,7 @@ async function main() {
|
|
|
3018
3111
|
// pass through.
|
|
3019
3112
|
if (name === 'space' && buf.length > 0)
|
|
3020
3113
|
return;
|
|
3021
|
-
if (
|
|
3114
|
+
if (lookup === '/' && buf !== '' && buf !== '/')
|
|
3022
3115
|
return;
|
|
3023
3116
|
// Mid-stream is suppressed by the input guard already;
|
|
3024
3117
|
// this listener still fires but we shouldn't open a picker
|
|
@@ -3026,14 +3119,15 @@ async function main() {
|
|
|
3026
3119
|
const turnCtl = globalThis.__turnAbortCtl;
|
|
3027
3120
|
if (turnCtl && !turnCtl.signal.aborted)
|
|
3028
3121
|
return;
|
|
3029
|
-
const
|
|
3030
|
-
//
|
|
3031
|
-
//
|
|
3122
|
+
const isSlash = lookup === '/';
|
|
3123
|
+
// Take the interlock. The async branch sets pickerActive=false
|
|
3124
|
+
// in a finally so any error path still releases it.
|
|
3032
3125
|
pickerActive = true;
|
|
3033
|
-
//
|
|
3034
|
-
//
|
|
3035
|
-
//
|
|
3036
|
-
//
|
|
3126
|
+
// Clear the trigger char from readline's buffer so the prompt
|
|
3127
|
+
// is clean. For '/' we'll re-render it ourselves at the
|
|
3128
|
+
// inline-suggest anchor; for Space we don't need any
|
|
3129
|
+
// character on the prompt because the alt-screen picker
|
|
3130
|
+
// covers everything.
|
|
3037
3131
|
try {
|
|
3038
3132
|
const rlAny = rl;
|
|
3039
3133
|
rlAny.line = '';
|
|
@@ -3042,50 +3136,81 @@ async function main() {
|
|
|
3042
3136
|
catch { /* noop */ }
|
|
3043
3137
|
void (async () => {
|
|
3044
3138
|
try {
|
|
3045
|
-
const { pick } = await import('./picker.js');
|
|
3046
3139
|
const { COMMAND_CATALOG } = await import('./command-palette.js');
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3140
|
+
if (isSlash) {
|
|
3141
|
+
// '/' → inline dropdown below the prompt.
|
|
3142
|
+
const { inlineSuggest } = await import('./inline-suggest.js');
|
|
3143
|
+
const result = await inlineSuggest(rl, COMMAND_CATALOG.map((c) => ({
|
|
3144
|
+
command: c.command,
|
|
3145
|
+
description: c.description,
|
|
3146
|
+
})), '/');
|
|
3147
|
+
if (result.accepted && result.command) {
|
|
3148
|
+
// Trailing space → "fill but don't submit" (Tab
|
|
3149
|
+
// pathway): plant the command in rl.line so the
|
|
3150
|
+
// user can type args before Enter.
|
|
3151
|
+
if (result.command.endsWith(' ')) {
|
|
3152
|
+
const cmd = result.command;
|
|
3153
|
+
try {
|
|
3154
|
+
const rlAny = rl;
|
|
3155
|
+
rlAny.line = cmd;
|
|
3156
|
+
rlAny.cursor = cmd.length;
|
|
3157
|
+
rlAny._refreshLine?.();
|
|
3158
|
+
}
|
|
3159
|
+
catch { /* noop */ }
|
|
3160
|
+
}
|
|
3161
|
+
else {
|
|
3162
|
+
// Enter → submit immediately via the queued-input
|
|
3163
|
+
// sentinel pattern (mirrors how the space picker
|
|
3164
|
+
// submits).
|
|
3165
|
+
globalThis.__crowcoderQueuedInput = result.command + '\n';
|
|
3166
|
+
try {
|
|
3167
|
+
rl.emit('line', '');
|
|
3168
|
+
}
|
|
3169
|
+
catch { /* noop */ }
|
|
3170
|
+
}
|
|
3065
3171
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
rlAny._refreshLine?.();
|
|
3172
|
+
else {
|
|
3173
|
+
// Cancelled. Restore rl.line to whatever the user
|
|
3174
|
+
// had typed so they can keep editing (could be '/',
|
|
3175
|
+
// '/he', or '' if they backspaced all the way out).
|
|
3176
|
+
try {
|
|
3177
|
+
const rlAny = rl;
|
|
3178
|
+
rlAny.line = result.filter;
|
|
3179
|
+
rlAny.cursor = result.filter.length;
|
|
3180
|
+
rlAny._refreshLine?.();
|
|
3181
|
+
}
|
|
3182
|
+
catch { /* noop */ }
|
|
3078
3183
|
}
|
|
3079
|
-
catch { /* noop */ }
|
|
3080
3184
|
}
|
|
3081
3185
|
else {
|
|
3082
|
-
//
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3186
|
+
// Space → full-screen browse picker (unchanged).
|
|
3187
|
+
const { pick } = await import('./picker.js');
|
|
3188
|
+
const items = COMMAND_CATALOG.map((c) => ({
|
|
3189
|
+
label: c.command,
|
|
3190
|
+
hint: c.category,
|
|
3191
|
+
description: c.description,
|
|
3192
|
+
value: c.command,
|
|
3193
|
+
}));
|
|
3194
|
+
const selected = await pick(items, {
|
|
3195
|
+
title: 'compact-agent · command palette',
|
|
3196
|
+
footer: 'type to filter · ↑↓ to navigate · Enter to run · Esc to cancel',
|
|
3197
|
+
});
|
|
3198
|
+
if (selected) {
|
|
3199
|
+
globalThis.__crowcoderQueuedInput = selected + '\n';
|
|
3200
|
+
try {
|
|
3201
|
+
rl.emit('line', '');
|
|
3202
|
+
}
|
|
3203
|
+
catch { /* noop */ }
|
|
3204
|
+
}
|
|
3205
|
+
else {
|
|
3206
|
+
// Cancel from space-triggered picker: prompt is
|
|
3207
|
+
// clean, resolve readline with empty so the loop
|
|
3208
|
+
// iterates back to a fresh prompt.
|
|
3209
|
+
try {
|
|
3210
|
+
rl.emit('line', '');
|
|
3211
|
+
}
|
|
3212
|
+
catch { /* noop */ }
|
|
3087
3213
|
}
|
|
3088
|
-
catch { /* noop */ }
|
|
3089
3214
|
}
|
|
3090
3215
|
}
|
|
3091
3216
|
finally {
|
|
@@ -3165,13 +3290,13 @@ async function main() {
|
|
|
3165
3290
|
// deterministic; higher = more creative. Step ± 0.1, clamped
|
|
3166
3291
|
// to [0.0, 2.0]. Saved immediately so the next API call uses
|
|
3167
3292
|
// the new value. Persisted so the setting survives restarts.
|
|
3168
|
-
if ((
|
|
3293
|
+
if ((lookup === ',' || lookup === '.') && meta) {
|
|
3169
3294
|
const cur = typeof config.temperature === 'number' ? config.temperature : 0.3;
|
|
3170
|
-
const step =
|
|
3295
|
+
const step = lookup === ',' ? -0.1 : +0.1;
|
|
3171
3296
|
const next = Math.max(0, Math.min(2.0, Math.round((cur + step) * 100) / 100));
|
|
3172
3297
|
config.temperature = next;
|
|
3173
3298
|
saveConfig(config);
|
|
3174
|
-
const label =
|
|
3299
|
+
const label = lookup === ',' ? 'Alt+,' : 'Alt+.';
|
|
3175
3300
|
announce(label, `Temperature ${next.toFixed(2)} (lower = more careful, higher = more creative).`);
|
|
3176
3301
|
return;
|
|
3177
3302
|
}
|