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.
Files changed (2) hide show
  1. package/bin/dual-brain.mjs +75 -29
  2. package/package.json +1 -1
@@ -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
- const fx = await getFx();
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(` ${DIM}[Enter] resume [n] new [s] skip${RST}\n\n`);
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
- // Input bar — rendered below panels; the key handler will overwrite this line
2740
- const inputHint = `${DIM}[?] help${RST}`;
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
- const inputLeftW = (inputLeft + ' task or command...').replace(/\x1b\[[0-9;]*m/g, '').length;
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(` [Enter] to run, [n] to cancel\n\n`);
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[HEAD GATE]\x1b[0m ${reason}\n`);
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} [Enter] to proceed, [n] to cancel\n\n`);
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
- // Normal dispatch show depth but don't block
3027
- process.stdout.write(`\n Launch coding session: ${summary}\n`);
3028
- process.stdout.write(` Depth: ${hj?.depth || '?'} Model: ${model} [Enter] to proceed, [n] to cancel\n\n`);
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('Command Palette'),
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 ss${RESET} List recent sessions`),
3202
+ row(`${DIM}sessions${RESET} List recent sessions`),
3153
3203
  row(`${DIM}search <query>${RESET} Search session history`),
3154
- row(`${DIM}budget b${RESET} Token usage + routing`),
3155
- row(`${DIM}health h${RESET} System health check`),
3156
- row(`${DIM}doctor d${RESET} Repo diagnostics`),
3157
- row(`${DIM}settings s${RESET} Settings screen`),
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('Or type any task to launch a coding session'),
3163
- row(`${DIM}Questions (why/how/what)haiku (cheap)${RESET}`),
3164
- row(`${DIM}Work tasks → confirm then dispatch${RESET}`),
3209
+ row(`Questions quick answer (haiku)`),
3210
+ row(`Work tasksHEAD evaluates, then dispatches`),
3165
3211
  sep,
3166
- row(`${DIM}[Enter] go back${RESET}`),
3212
+ row(`${DIM}Enter${RESET} go back`),
3167
3213
  bot,
3168
3214
  ];
3169
3215
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {