dual-brain 7.1.19 → 7.1.21

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.
@@ -672,7 +672,7 @@ function cmdBreakGlass(reason) {
672
672
  * Render the data-tools-style rounded header box for the main screen.
673
673
  * Inner width is 39 chars. Lines are padded with spaces to fill the box.
674
674
  */
675
- function renderHeader(version, providerLines) {
675
+ function renderHeader(version, providerLines, dtVersion) {
676
676
  const W = 39; // inner width
677
677
  const pad = (s) => {
678
678
  // Strip ANSI codes for length calculation
@@ -683,11 +683,13 @@ function renderHeader(version, providerLines) {
683
683
  const sep = ` ├${'─'.repeat(W)}┤`;
684
684
  const bottom = ` └${'─'.repeat(W)}┘`;
685
685
 
686
- const title = `DATA Tools - Dual Brain v${version}`;
686
+ const title = dtVersion ? `DATA Tools v${dtVersion}` : `DATA Tools`;
687
+ const subTitle = `🧠 Dual Brain v${version}`;
687
688
  const credit = `by Steve Moraco + dual-brain`;
688
689
 
689
690
  const lines = [top];
690
691
  lines.push(` │ ${pad(title)}│`);
692
+ lines.push(` │ ${pad(subTitle)}│`);
691
693
  lines.push(` │ ${pad(credit)}│`);
692
694
  lines.push(sep);
693
695
  for (const pl of providerLines) {
@@ -739,18 +741,20 @@ async function welcomeScreen(rl, ask) {
739
741
  const claudeReady = auth.claude.found;
740
742
  const openaiReady = auth.openai.found;
741
743
 
742
- const claudePlanLabel = claudeReady
743
- ? (CLAUDE_PLAN_LABELS[plans.claude] ?? plans.claude ?? 'plan unknown')
744
- : null;
745
- const openaiPlanLabel = openaiReady
746
- ? (OPENAI_PLAN_LABELS[plans.openai] ?? plans.openai ?? 'plan unknown')
747
- : null;
744
+ // Plan labels are inferred from auth config (rate-limit tier / JWT),
745
+ // not reported directly by the CLI. Suffix shows configured tier, not plan name.
746
+ const claudePlanSuffix = claudeReady && plans.claude
747
+ ? ` · ${plans.claude} configured`
748
+ : '';
749
+ const openaiPlanSuffix = openaiReady && plans.openai
750
+ ? ` · ${plans.openai} configured`
751
+ : '';
748
752
 
749
753
  const detectedLines = [];
750
- if (claudeReady) detectedLines.push(` Claude CLI ready${claudePlanLabel ? ` (${claudePlanLabel})` : ''}`);
751
- else detectedLines.push(` Claude CLI not logged in`);
752
- if (openaiReady) detectedLines.push(` Codex CLI ready${openaiPlanLabel ? ` (${openaiPlanLabel})` : ''}`);
753
- else detectedLines.push(` Codex CLI not logged in`);
754
+ if (claudeReady) detectedLines.push(` Claude: authenticated${claudePlanSuffix}`);
755
+ else detectedLines.push(` Claude: not connected`);
756
+ if (openaiReady) detectedLines.push(` Codex: authenticated${openaiPlanSuffix}`);
757
+ else detectedLines.push(` Codex: not connected`);
754
758
 
755
759
  console.log('');
756
760
  console.log('Detected:');
@@ -1031,7 +1035,15 @@ async function mainScreen(rl, ask) {
1031
1035
  subLine('OpenAI', openaiPlan, auth.openai.found, openaiExpired, openaiDays, openaiSub),
1032
1036
  ];
1033
1037
 
1034
- console.log(`📦 DATA Tools - Dual Brain v${version}`);
1038
+ const rtMain = detectReplitTools(cwd);
1039
+ const dtVersion = (rtMain.installed && rtMain.version) ? rtMain.version : null;
1040
+ if (dtVersion) {
1041
+ console.log(`📦 DATA Tools v${dtVersion}`);
1042
+ console.log(`🧠 Dual Brain v${version}`);
1043
+ } else {
1044
+ console.log(`📦 DATA Tools`);
1045
+ console.log(`🧠 Dual Brain v${version}`);
1046
+ }
1035
1047
  const latestVersion = await checkForUpdates(version);
1036
1048
  if (latestVersion) {
1037
1049
  console.log(` ⬆️ Update available: v${version} → v${latestVersion}`);
@@ -1039,36 +1051,11 @@ async function mainScreen(rl, ask) {
1039
1051
  }
1040
1052
  console.log('');
1041
1053
 
1042
- // Help shortcuts box (matching data-tools style)
1043
- const W = 37;
1044
- const helpTop = ` ┌${'─'.repeat(W)}┐`;
1045
- const helpSep = ` ├${'─'.repeat(W)}┤`;
1046
- const helpBottom = ` └${'─'.repeat(W)}┘`;
1047
- const helpPad = (s) => s + ' '.repeat(Math.max(0, W - s.length));
1048
-
1049
- console.log(helpTop);
1050
- console.log(` │ ${helpPad('At ~/workspace$ prompt:')}│`);
1051
- console.log(` │ ${helpPad('db = show this menu')}│`);
1052
- console.log(` │ ${helpPad('j = login to claude')}│`);
1053
- console.log(` │ ${helpPad('k = login to codex')}│`);
1054
- console.log(helpSep);
1055
- console.log(` │ ${helpPad('In Claude:')}│`);
1056
- console.log(` │ ${helpPad('Ctrl+C x2 = back to menu')}│`);
1057
- console.log(` │ ${helpPad('Ctrl+C x3 = exit to shell')}│`);
1058
- console.log(helpBottom);
1059
- console.log('');
1060
-
1061
1054
  // Provider status (outside the box)
1062
1055
  for (const line of headerLines) {
1063
1056
  console.log(` ${line}`);
1064
1057
  }
1065
1058
 
1066
- // replit-tools indicator
1067
- const rtMain = detectReplitTools(cwd);
1068
- if (rtMain.installed && rtMain.version) {
1069
- console.log(` 🔗 replit-tools v${rtMain.version}`);
1070
- }
1071
-
1072
1059
  const sparkline = buildSparkline(cwd);
1073
1060
  if (sparkline) {
1074
1061
  console.log(` Activity: ${sparkline}`);
@@ -1133,7 +1120,12 @@ async function mainScreen(rl, ask) {
1133
1120
  const active = sess.isActive ? ' ●' : '';
1134
1121
  const cat = sess.category ? ` [${sess.category}]` : '';
1135
1122
  const tool = (sess.tool === 'codex') ? 'cdx' : 'cld';
1136
- const displayName = (sess.name && sess.name.length > 40) ? sess.name.slice(0, 37) + '...' : (sess.name || sess.id.slice(0, 8));
1123
+ // If the name is still the "Session XXXXXXXX" fallback, try the project path instead
1124
+ let rawName = sess.name || '';
1125
+ if (/^Session [0-9a-f]{8,}$/i.test(rawName)) {
1126
+ rawName = sess.project ? sess.project.replace(/^-/, '/').replace(/-/g, '/') : sess.id.slice(0, 8);
1127
+ }
1128
+ const displayName = rawName.length > 40 ? rawName.slice(0, 37) + '...' : (rawName || sess.id.slice(0, 8));
1137
1129
  console.log(` [${i + 1}] ${pin}${tool} ${sess.age.padEnd(8)} ${displayName}${active}${cat}`);
1138
1130
  });
1139
1131
  console.log('');
@@ -1171,15 +1163,41 @@ async function mainScreen(rl, ask) {
1171
1163
  console.log(' [r] Resume (full list)');
1172
1164
  console.log(' [/] Search sessions');
1173
1165
  console.log(' [e] Manage sessions');
1174
- console.log(' [i] Import from replit-tools');
1175
1166
  console.log(' [m] Manage subscriptions');
1176
- console.log(' [d] Switch to data-tools');
1177
1167
  console.log(' [s] Settings');
1168
+ console.log(' [?] Help & shortcuts');
1169
+ console.log('');
1170
+ console.log(' \x1b[2mreplit-tools:\x1b[0m');
1171
+ console.log(' [i] Import sessions');
1172
+ console.log(' [d] Switch to data-tools');
1173
+ console.log('');
1178
1174
  console.log(' [q] Exit');
1179
1175
  console.log('');
1180
1176
 
1181
1177
  const choice = (await ask(' Choice: ')).trim().toLowerCase();
1182
1178
 
1179
+ if (choice === '?') {
1180
+ const W = 37;
1181
+ const helpTop = ` ┌${'─'.repeat(W)}┐`;
1182
+ const helpSep = ` ├${'─'.repeat(W)}┤`;
1183
+ const helpBottom = ` └${'─'.repeat(W)}┘`;
1184
+ const helpPad = (s) => s + ' '.repeat(Math.max(0, W - s.length));
1185
+ console.log('');
1186
+ console.log(helpTop);
1187
+ console.log(` │ ${helpPad('At ~/workspace$ prompt:')}│`);
1188
+ console.log(` │ ${helpPad('db = show this menu')}│`);
1189
+ console.log(` │ ${helpPad('j = login to claude')}│`);
1190
+ console.log(` │ ${helpPad('k = login to codex')}│`);
1191
+ console.log(helpSep);
1192
+ console.log(` │ ${helpPad('In Claude:')}│`);
1193
+ console.log(` │ ${helpPad('Ctrl+C x2 = back to menu')}│`);
1194
+ console.log(` │ ${helpPad('Ctrl+C x3 = exit to shell')}│`);
1195
+ console.log(helpBottom);
1196
+ console.log('');
1197
+ await ask(' Press Enter to continue...');
1198
+ return { next: 'main' };
1199
+ }
1200
+
1183
1201
  if (choice === 'n') { return { next: 'new-session' }; }
1184
1202
 
1185
1203
  if (choice === 'c') {
@@ -1696,18 +1714,16 @@ async function runOnboardingWizard(detection, cwd, rl) {
1696
1714
  console.log(wRow(`Step 1 of 5: Detected providers`));
1697
1715
  console.log(wSep);
1698
1716
 
1699
- const claudePlanLabel = claudeReady
1700
- ? (CLAUDE_PLAN_LABELS[plans.claude] ?? plans.claude ?? 'plan unknown')
1701
- : null;
1702
- const openaiPlanLabel = openaiReady
1703
- ? (OPENAI_PLAN_LABELS[plans.openai] ?? plans.openai ?? 'plan unknown')
1704
- : null;
1717
+ // Plan tier is inferred from auth config signals — not the actual plan name.
1718
+ // Show the tier ($20/$100/$200) with "configured" suffix to be honest.
1719
+ const claudePlanSuffix = claudeReady && plans.claude ? ` · ${plans.claude} configured` : '';
1720
+ const openaiPlanSuffix = openaiReady && plans.openai ? ` · ${plans.openai} configured` : '';
1705
1721
 
1706
1722
  console.log(wRow(claudeReady
1707
- ? `✓ Claude CLI ${claudePlanLabel ? `(${claudePlanLabel})` : ''}`
1723
+ ? `✓ Claude CLI${claudePlanSuffix}`
1708
1724
  : `✗ Claude CLI not logged in`));
1709
1725
  console.log(wRow(openaiReady
1710
- ? `✓ Codex CLI ${openaiPlanLabel ? `(${openaiPlanLabel})` : ''}`
1726
+ ? `✓ Codex CLI${openaiPlanSuffix}`
1711
1727
  : `✗ Codex CLI not logged in`));
1712
1728
  if (existingSessions.length > 0) {
1713
1729
  console.log(wRow(`✓ ${existingSessions.length} data-tools session${existingSessions.length !== 1 ? 's' : ''} found`));
@@ -1747,31 +1763,33 @@ async function runOnboardingWizard(detection, cwd, rl) {
1747
1763
  console.log(wSep);
1748
1764
 
1749
1765
  if (claudeReady) {
1750
- const detectedClaudePlan = plans.claude || 'pro';
1751
- const detectedClaudeLabel = CLAUDE_PLAN_LABELS[detectedClaudePlan] ?? detectedClaudePlan;
1752
- console.log(wRow(`Claude detected: ${detectedClaudeLabel}`));
1766
+ // Plan tier is inferred from auth config (rate-limit signal), not the actual plan name.
1767
+ const configuredClaudePlan = plans.claude || '$20';
1768
+ const configuredClaudeDesc = configuredClaudePlan + ' configured';
1769
+ console.log(wRow(`Claude — ${configuredClaudeDesc}`));
1753
1770
  console.log(wRow(` [1] Pro ($20/mo)`));
1754
1771
  console.log(wRow(` [2] Max x5 ($100/mo)`));
1755
1772
  console.log(wRow(` [3] Max x20 ($200/mo)`));
1756
- console.log(wRow(` [Enter] Keep detected (${detectedClaudeLabel})`));
1773
+ console.log(wRow(` [Enter] Keep configured (${configuredClaudePlan})`));
1757
1774
  console.log(wSep);
1758
1775
  const claudeChoice = (await ask(' Claude plan [1/2/3/Enter]: ')).trim();
1759
1776
  const claudePlanMap = { '1': 'pro', '2': 'max5', '3': 'max20' };
1760
- state.claudePlan = claudePlanMap[claudeChoice] || detectedClaudePlan;
1777
+ state.claudePlan = claudePlanMap[claudeChoice] || configuredClaudePlan;
1761
1778
  }
1762
1779
 
1763
1780
  if (openaiReady) {
1764
- const detectedOpenaiPlan = plans.openai || 'plus';
1765
- const detectedOpenaiLabel = OPENAI_PLAN_LABELS[detectedOpenaiPlan] ?? detectedOpenaiPlan;
1766
- console.log(wRow(`OpenAI detected: ${detectedOpenaiLabel}`));
1781
+ // Plan tier is inferred from JWT claim in auth config, not the actual plan name.
1782
+ const configuredOpenaiPlan = plans.openai || '$20';
1783
+ const configuredOpenaiDesc = configuredOpenaiPlan + ' configured';
1784
+ console.log(wRow(`OpenAI — ${configuredOpenaiDesc}`));
1767
1785
  console.log(wRow(` [1] Plus ($20/mo)`));
1768
1786
  console.log(wRow(` [2] Pro ($100/mo)`));
1769
1787
  console.log(wRow(` [3] Pro ($200/mo higher limits)`));
1770
- console.log(wRow(` [Enter] Keep detected (${detectedOpenaiLabel})`));
1788
+ console.log(wRow(` [Enter] Keep configured (${configuredOpenaiPlan})`));
1771
1789
  console.log(wSep);
1772
1790
  const openaiChoice = (await ask(' OpenAI plan [1/2/3/Enter]: ')).trim();
1773
1791
  const openaiPlanMap = { '1': 'plus', '2': 'pro', '3': 'pro200' };
1774
- state.openaiPlan = openaiPlanMap[openaiChoice] || detectedOpenaiPlan;
1792
+ state.openaiPlan = openaiPlanMap[openaiChoice] || configuredOpenaiPlan;
1775
1793
  }
1776
1794
 
1777
1795
  console.log(wBottom);
@@ -2053,8 +2071,9 @@ async function diagnosticsScreen(rl, ask) {
2053
2071
 
2054
2072
  const claudeHealthBadge = auth.claude.found ? _providerBadge('claude') : 'not logged in';
2055
2073
  const openaiHealthBadge = auth.openai.found ? _providerBadge('openai') : 'not logged in';
2056
- const claudePlanStr = plans.claude ? (CLAUDE_PLAN_LABELS[plans.claude] ?? plans.claude) : 'unknown';
2057
- const openaiPlanStr = plans.openai ? (OPENAI_PLAN_LABELS[plans.openai] ?? plans.openai) : 'unknown';
2074
+ // Plan tier is inferred from auth config signals — show tier with "configured" to be honest.
2075
+ const claudePlanStr = plans.claude ? `${plans.claude} configured` : 'unknown';
2076
+ const openaiPlanStr = plans.openai ? `${plans.openai} configured` : 'unknown';
2058
2077
 
2059
2078
  // ── Enforcement checks ────────────────────────────────────────────────────
2060
2079
  const hooksDir = join(cwd, '.claude', 'hooks');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "7.1.19",
3
+ "version": "7.1.21",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/profile.mjs CHANGED
@@ -240,9 +240,14 @@ function decodeJwtPayload(token) {
240
240
  }
241
241
 
242
242
  /**
243
- * Detect actual subscription plans from Claude Code and Codex config files.
243
+ * Infer plan tier from Claude Code and Codex auth config files.
244
244
  * Returns { claude: '$20'|'$100'|'$200'|null, openai: '$20'|'$100'|'$200'|null }.
245
245
  * Returns nulls for any provider whose config cannot be read — never throws.
246
+ *
247
+ * NOTE: This reads rate-limit tier signals (organizationRateLimitTier for Claude,
248
+ * chatgpt_plan_type JWT claim for OpenAI) and maps them to price tiers.
249
+ * It does NOT retrieve the actual subscription plan name from the provider —
250
+ * labels like "Max x5" or "Pro" are our own interpretations of those signals.
246
251
  */
247
252
  function detectPlans() {
248
253
  const plans = { claude: null, openai: null };
@@ -371,23 +376,19 @@ function loadProfile(cwd) {
371
376
  }
372
377
  if (!profile) profile = defaultProfile();
373
378
 
374
- // Auto-detect plans from provider config files and apply if detected.
379
+ // Read plan tier from auth config files (JWT or organizationRateLimitTier) and
380
+ // apply if it differs from the stored profile value.
381
+ // NOTE: detectPlans() reads rate-limit tier data from the auth config — it infers
382
+ // a price tier ($20/$100/$200) from that signal, not from the subscription name itself.
383
+ // The plan label (e.g. "Max x5") comes from our own mapping, not from Claude/OpenAI.
375
384
  const detected = detectPlans();
376
385
  for (const [provider, detectedPlan] of Object.entries(detected)) {
377
386
  if (!detectedPlan) continue;
378
387
  if (!profile.providers[provider]) continue;
379
388
  const stored = profile.providers[provider].plan;
380
389
  if (stored !== detectedPlan) {
381
- const labels = {
382
- claude: {
383
- '$20': 'Claude Pro ($20)', '$100': 'Claude Max x5 ($100)', '$200': 'Claude Max x20 ($200)',
384
- },
385
- openai: {
386
- '$20': 'ChatGPT Plus ($20)', '$100': 'ChatGPT Pro ($100)', '$200': 'ChatGPT Pro ($200)',
387
- },
388
- };
389
- const label = labels[provider]?.[detectedPlan] ?? `${provider} ${detectedPlan}`;
390
- process.stderr.write(`[dual-brain] Detected ${label} plan\n`);
390
+ const providerName = provider === 'claude' ? 'Claude' : 'OpenAI';
391
+ process.stderr.write(`[dual-brain] ${providerName}: plan updated to ${detectedPlan} (from auth config)\n`);
391
392
  profile.providers[provider].plan = detectedPlan;
392
393
  }
393
394
  }
@@ -622,12 +623,10 @@ async function autoSetup(cwd) {
622
623
  if (auth.claude.found) {
623
624
  profile.providers.claude.enabled = true;
624
625
  profile.providers.claude.plan = plans.claude || '$20';
625
- const planLabel = {
626
- '$20': 'Claude Pro ($20)',
627
- '$100': 'Claude Max x5 ($100)',
628
- '$200': 'Claude Max x20 ($200)',
629
- }[profile.providers.claude.plan] || profile.providers.claude.plan;
630
- result.actions.push(`${planLabel} (${auth.claude.source})`);
626
+ // Plan tier is inferred from auth config signal — show tier with "configured",
627
+ // not a plan name we didn't actually detect.
628
+ const claudeTierLabel = plans.claude ? `${plans.claude} configured` : 'connected';
629
+ result.actions.push(`Claude: ${claudeTierLabel} (${auth.claude.source})`);
631
630
  } else {
632
631
  profile.providers.claude.enabled = false;
633
632
  result.warnings.push('Claude CLI not logged in — run: claude login');
@@ -637,12 +636,10 @@ async function autoSetup(cwd) {
637
636
  if (auth.openai.found) {
638
637
  profile.providers.openai.enabled = true;
639
638
  profile.providers.openai.plan = plans.openai || '$20';
640
- const planLabel = {
641
- '$20': 'ChatGPT Plus ($20)',
642
- '$100': 'ChatGPT Pro ($100)',
643
- '$200': 'ChatGPT Pro ($200)',
644
- }[profile.providers.openai.plan] || profile.providers.openai.plan;
645
- result.actions.push(`${planLabel} (${auth.openai.source})`);
639
+ // Plan tier is inferred from JWT claim in auth config — show tier with "configured",
640
+ // not a plan name we didn't actually detect.
641
+ const openaiTierLabel = plans.openai ? `${plans.openai} configured` : 'connected';
642
+ result.actions.push(`OpenAI: ${openaiTierLabel} (${auth.openai.source})`);
646
643
  } else {
647
644
  profile.providers.openai.enabled = false;
648
645
  result.warnings.push('Codex CLI not logged in — run: codex login');
@@ -938,7 +935,11 @@ async function detectExistingAuth(cwd) {
938
935
  }
939
936
 
940
937
  // -------------------------------------------------------------------------
941
- // Plan detection (re-use detectPlans which reads the same config files)
938
+ // Plan tier inference (re-uses detectPlans which reads auth config files)
939
+ // NOTE: This is NOT subscription detection — we infer a price tier ($20/$100/$200)
940
+ // from rate-limit tier signals in the auth config (organizationRateLimitTier for
941
+ // Claude, JWT chatgpt_plan_type for OpenAI). The CLI does not report the actual
942
+ // plan name or price. Any plan label shown to the user comes from our own mapping.
942
943
  // -------------------------------------------------------------------------
943
944
  const plans = detectPlans();
944
945
  if (result.claude.found && plans.claude) result.claude.plan = plans.claude;