dual-brain 0.2.10 → 0.2.11
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/bin/dual-brain.mjs +75 -29
- package/package.json +1 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -2086,6 +2086,7 @@ const FREE_COMMANDS = new Map([
|
|
|
2086
2086
|
['help', 'help'], ['?', 'help'],
|
|
2087
2087
|
['quit', 'quit'], ['q', 'quit'], ['exit', 'quit'],
|
|
2088
2088
|
['budget', 'budget'], ['b', 'budget'],
|
|
2089
|
+
['auto', 'auto'], ['automode', 'auto'],
|
|
2089
2090
|
]);
|
|
2090
2091
|
|
|
2091
2092
|
/**
|
|
@@ -2271,11 +2272,16 @@ async function mainScreen(rl, ask) {
|
|
|
2271
2272
|
const auth = await detectAuth();
|
|
2272
2273
|
|
|
2273
2274
|
// ── Dashboard load animation (full mode only) ─────────────────────────────
|
|
2274
|
-
|
|
2275
|
+
let fx = null;
|
|
2276
|
+
try { fx = await Promise.race([getFx(), new Promise(r => setTimeout(() => r(null), 3000))]); } catch {}
|
|
2275
2277
|
let dashSpinner = null;
|
|
2276
2278
|
if (fx && fx.getMode && fx.getMode() === 'full') {
|
|
2277
2279
|
dashSpinner = fx.spinner('Loading dashboard...').start();
|
|
2278
2280
|
}
|
|
2281
|
+
// Safety: kill spinner after 8s no matter what
|
|
2282
|
+
const _spinnerTimeout = dashSpinner ? setTimeout(() => {
|
|
2283
|
+
if (dashSpinner) { try { dashSpinner.stop(); } catch {} dashSpinner = null; }
|
|
2284
|
+
}, 8000) : null;
|
|
2279
2285
|
|
|
2280
2286
|
// ── One-time default shell prompt for returning users (never asked before) ─
|
|
2281
2287
|
if (profile.setupComplete && !profile.defaultShellAsked) {
|
|
@@ -2294,10 +2300,10 @@ async function mainScreen(rl, ask) {
|
|
|
2294
2300
|
const claudeExpired = claudeSub?.expiresAt && Date.parse(claudeSub.expiresAt) < now;
|
|
2295
2301
|
const openaiExpired = openaiSub?.expiresAt && Date.parse(openaiSub.expiresAt) < now;
|
|
2296
2302
|
|
|
2297
|
-
// Silent OAuth token auto-refresh
|
|
2303
|
+
// Silent OAuth token auto-refresh (3s timeout — never block dashboard)
|
|
2298
2304
|
try {
|
|
2299
2305
|
const { autoRefreshToken } = await import('../src/profile.mjs');
|
|
2300
|
-
await autoRefreshToken(cwd);
|
|
2306
|
+
await Promise.race([autoRefreshToken(cwd), new Promise(r => setTimeout(r, 3000))]);
|
|
2301
2307
|
} catch {}
|
|
2302
2308
|
|
|
2303
2309
|
// Append-only session archive sync
|
|
@@ -2353,7 +2359,7 @@ async function mainScreen(rl, ask) {
|
|
|
2353
2359
|
} else {
|
|
2354
2360
|
process.stdout.write(` ${DIM}${interrupted.reason} · ${interrupted.ageLabel}${RST}\n`);
|
|
2355
2361
|
}
|
|
2356
|
-
process.stdout.write(`
|
|
2362
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m resume \x1b[36mn\x1b[0m new \x1b[36ms\x1b[0m skip\n\n`);
|
|
2357
2363
|
|
|
2358
2364
|
// Wait for a keypress to decide what to do with the card
|
|
2359
2365
|
const readline2 = await import('node:readline');
|
|
@@ -2693,6 +2699,7 @@ async function mainScreen(rl, ask) {
|
|
|
2693
2699
|
});
|
|
2694
2700
|
|
|
2695
2701
|
// ── Resolve dashboard spinner before rendering ────────────────────────────
|
|
2702
|
+
if (_spinnerTimeout) clearTimeout(_spinnerTimeout);
|
|
2696
2703
|
if (dashSpinner) dashSpinner.succeed('Dashboard ready');
|
|
2697
2704
|
|
|
2698
2705
|
// ── Stale hint ────────────────────────────────────────────────────────────
|
|
@@ -2736,12 +2743,22 @@ async function mainScreen(rl, ask) {
|
|
|
2736
2743
|
process.stdout.write(panel('Signals', recentLines, { width: panelW }) + '\n\n');
|
|
2737
2744
|
}
|
|
2738
2745
|
|
|
2739
|
-
//
|
|
2740
|
-
const
|
|
2746
|
+
// Shortcut bar — always visible so the user never has to guess
|
|
2747
|
+
const autoLabel = profile.automode ? `\x1b[32m⚡auto\x1b[0m` : `${DIM}auto${RST}`;
|
|
2748
|
+
const shortcutItems = [
|
|
2749
|
+
`${CYAN}Enter${RST} resume`,
|
|
2750
|
+
`${CYAN}n${RST} new`,
|
|
2751
|
+
`${CYAN}/${RST} search`,
|
|
2752
|
+
`${CYAN}s${RST} settings`,
|
|
2753
|
+
`${CYAN}d${RST} doctor`,
|
|
2754
|
+
autoLabel,
|
|
2755
|
+
`${CYAN}q${RST} quit`,
|
|
2756
|
+
];
|
|
2757
|
+
process.stdout.write(` ${DIM}${shortcutItems.join(' ')}${RST}\n\n`);
|
|
2758
|
+
|
|
2759
|
+
// Input bar — rendered below shortcut bar
|
|
2741
2760
|
const inputLeft = tuiPrompt('task or command...');
|
|
2742
|
-
|
|
2743
|
-
const inputGap = Math.max(1, panelW - inputLeftW - 8);
|
|
2744
|
-
process.stdout.write(` ${inputLeft}${' '.repeat(inputGap)}${inputHint}\n`);
|
|
2761
|
+
process.stdout.write(` ${inputLeft}\n`);
|
|
2745
2762
|
|
|
2746
2763
|
// ── Key handling ──────────────────────────────────────────────────────────
|
|
2747
2764
|
// Use raw keypress mode so we can show a live type-to-start buffer.
|
|
@@ -2943,6 +2960,17 @@ async function mainScreen(rl, ask) {
|
|
|
2943
2960
|
await ask('\n Press Enter to continue...');
|
|
2944
2961
|
return { next: 'main' };
|
|
2945
2962
|
}
|
|
2963
|
+
if (cmd === 'auto') {
|
|
2964
|
+
const cwd2 = process.cwd();
|
|
2965
|
+
const prof = loadProfile(cwd2);
|
|
2966
|
+
prof.automode = !prof.automode;
|
|
2967
|
+
saveProfile(prof, { cwd: cwd2 });
|
|
2968
|
+
const state = prof.automode ? '\x1b[32mON\x1b[0m' : '\x1b[2mOFF\x1b[0m';
|
|
2969
|
+
process.stdout.write(`\n Automode: ${state}\n`);
|
|
2970
|
+
process.stdout.write(` ${prof.automode ? 'Tasks dispatch immediately (HEAD still gates dangerous ops)' : 'Tasks require Enter to confirm'}\n\n`);
|
|
2971
|
+
await ask(' Press Enter to continue...');
|
|
2972
|
+
return { next: 'main' };
|
|
2973
|
+
}
|
|
2946
2974
|
if (cmd === 'init --replit') {
|
|
2947
2975
|
await cmdInit(rl);
|
|
2948
2976
|
return { next: 'main' };
|
|
@@ -2978,7 +3006,7 @@ async function mainScreen(rl, ask) {
|
|
|
2978
3006
|
|
|
2979
3007
|
process.stdout.write(`\n Skill: /${skill.command} Agent: ${skill.agent} Model: ${model}\n`);
|
|
2980
3008
|
if (skill.description) process.stdout.write(` ${skill.description}\n`);
|
|
2981
|
-
process.stdout.write(` [
|
|
3009
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m run \x1b[36mn\x1b[0m cancel\n\n`);
|
|
2982
3010
|
const skillConfirm = (await ask(' > ')).trim().toLowerCase();
|
|
2983
3011
|
if (skillConfirm === 'n' || skillConfirm === 'no') return { next: 'main' };
|
|
2984
3012
|
|
|
@@ -3015,17 +3043,26 @@ async function mainScreen(rl, ask) {
|
|
|
3015
3043
|
// HEAD's shouldAskUser gates the dispatch — dangerous/irreversible ops
|
|
3016
3044
|
if (hj?.shouldAskUser) {
|
|
3017
3045
|
const reason = hj.obligations?.find(o => o.type === 'askBeforeIrreversi')?.description || hj.rationale;
|
|
3018
|
-
process.stdout.write(`\n \x1b[31m
|
|
3046
|
+
process.stdout.write(`\n \x1b[31m⚠ CAUTION\x1b[0m ${reason}\n`);
|
|
3019
3047
|
process.stdout.write(` Task: ${summary}\n`);
|
|
3020
|
-
process.stdout.write(` Depth: ${hj.depth} Model: ${model}
|
|
3048
|
+
process.stdout.write(` Depth: ${hj.depth} Model: ${model}\n`);
|
|
3049
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m proceed \x1b[36mn\x1b[0m cancel\n\n`);
|
|
3021
3050
|
const confirm = (await ask(' > ')).trim().toLowerCase();
|
|
3022
3051
|
if (confirm === 'n' || confirm === 'no') return { next: 'main' };
|
|
3023
3052
|
return { next: 'go', prompt: input, model };
|
|
3024
3053
|
}
|
|
3025
3054
|
|
|
3026
|
-
//
|
|
3027
|
-
|
|
3028
|
-
|
|
3055
|
+
// Automode: if HEAD says it's safe, just go — no confirmation needed
|
|
3056
|
+
const automode = profile.automode ?? profile.settings?.automode ?? false;
|
|
3057
|
+
if (automode) {
|
|
3058
|
+
process.stdout.write(`\n \x1b[36m⚡\x1b[0m ${summary} (${model}, depth: ${hj?.depth || '?'})\n`);
|
|
3059
|
+
return { next: 'go', prompt: input, model };
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// Manual mode — show depth, wait for confirmation
|
|
3063
|
+
process.stdout.write(`\n Launch: ${summary}\n`);
|
|
3064
|
+
process.stdout.write(` Depth: ${hj?.depth || '?'} Model: ${model}\n`);
|
|
3065
|
+
process.stdout.write(` \x1b[36mEnter\x1b[0m go \x1b[36mn\x1b[0m cancel\n\n`);
|
|
3029
3066
|
const confirm = (await ask(' > ')).trim().toLowerCase();
|
|
3030
3067
|
if (confirm === 'n' || confirm === 'no') return { next: 'main' };
|
|
3031
3068
|
return { next: 'go', prompt: input, model };
|
|
@@ -3143,27 +3180,36 @@ async function paletteHelpScreen(rl, ask) {
|
|
|
3143
3180
|
const DIM = '\x1b[2m';
|
|
3144
3181
|
const RESET = '\x1b[0m';
|
|
3145
3182
|
|
|
3183
|
+
const CYAN = '\x1b[36m';
|
|
3146
3184
|
const lines = [
|
|
3147
3185
|
top,
|
|
3148
|
-
row(
|
|
3186
|
+
row(`${CYAN}Keyboard Shortcuts${RESET} (single key, no Enter needed)`),
|
|
3187
|
+
sep,
|
|
3188
|
+
row(`${CYAN}Enter${RESET} Resume last session`),
|
|
3189
|
+
row(`${CYAN}n${RESET} New coding session`),
|
|
3190
|
+
row(`${CYAN}1-9${RESET} Resume session by number`),
|
|
3191
|
+
row(`${CYAN}/${RESET} Search session history`),
|
|
3192
|
+
row(`${CYAN}s${RESET} Settings`),
|
|
3193
|
+
row(`${CYAN}d${RESET} Doctor (repo diagnostics)`),
|
|
3194
|
+
row(`${CYAN}t${RESET} Team`),
|
|
3195
|
+
row(`${CYAN}i${RESET} Import sessions`),
|
|
3196
|
+
row(`${CYAN}q${RESET} Quit`),
|
|
3197
|
+
row(`${CYAN}?${RESET} This help`),
|
|
3198
|
+
sep,
|
|
3199
|
+
row(`${CYAN}Typed Commands${RESET} (type then Enter)`),
|
|
3149
3200
|
sep,
|
|
3150
|
-
row(`${DIM}resume r${RESET} Resume last session`),
|
|
3151
3201
|
row(`${DIM}status${RESET} Provider health + budget`),
|
|
3152
|
-
row(`${DIM}sessions
|
|
3202
|
+
row(`${DIM}sessions${RESET} List recent sessions`),
|
|
3153
3203
|
row(`${DIM}search <query>${RESET} Search session history`),
|
|
3154
|
-
row(`${DIM}budget
|
|
3155
|
-
row(`${DIM}health
|
|
3156
|
-
|
|
3157
|
-
row(`${
|
|
3158
|
-
row(`${DIM}team t${RESET} Team screen`),
|
|
3159
|
-
row(`${DIM}help ?${RESET} Show this help`),
|
|
3160
|
-
row(`${DIM}quit q${RESET} Exit`),
|
|
3204
|
+
row(`${DIM}budget${RESET} Token usage + routing`),
|
|
3205
|
+
row(`${DIM}health${RESET} System health check`),
|
|
3206
|
+
sep,
|
|
3207
|
+
row(`${CYAN}Natural Language${RESET} (just type)`),
|
|
3161
3208
|
sep,
|
|
3162
|
-
row(
|
|
3163
|
-
row(
|
|
3164
|
-
row(`${DIM}Work tasks → confirm then dispatch${RESET}`),
|
|
3209
|
+
row(`Questions → quick answer (haiku)`),
|
|
3210
|
+
row(`Work tasks → HEAD evaluates, then dispatches`),
|
|
3165
3211
|
sep,
|
|
3166
|
-
row(`${DIM}
|
|
3212
|
+
row(`${DIM}Enter${RESET} go back`),
|
|
3167
3213
|
bot,
|
|
3168
3214
|
];
|
|
3169
3215
|
|
package/package.json
CHANGED