dual-brain 7.1.19 → 7.1.20

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.
@@ -739,18 +739,20 @@ async function welcomeScreen(rl, ask) {
739
739
  const claudeReady = auth.claude.found;
740
740
  const openaiReady = auth.openai.found;
741
741
 
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;
742
+ // Plan labels are inferred from auth config (rate-limit tier / JWT),
743
+ // not reported directly by the CLI. Suffix shows configured tier, not plan name.
744
+ const claudePlanSuffix = claudeReady && plans.claude
745
+ ? ` · ${plans.claude} configured`
746
+ : '';
747
+ const openaiPlanSuffix = openaiReady && plans.openai
748
+ ? ` · ${plans.openai} configured`
749
+ : '';
748
750
 
749
751
  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`);
752
+ if (claudeReady) detectedLines.push(` Claude: authenticated${claudePlanSuffix}`);
753
+ else detectedLines.push(` Claude: not connected`);
754
+ if (openaiReady) detectedLines.push(` Codex: authenticated${openaiPlanSuffix}`);
755
+ else detectedLines.push(` Codex: not connected`);
754
756
 
755
757
  console.log('');
756
758
  console.log('Detected:');
@@ -1039,25 +1041,6 @@ async function mainScreen(rl, ask) {
1039
1041
  }
1040
1042
  console.log('');
1041
1043
 
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
1044
  // Provider status (outside the box)
1062
1045
  for (const line of headerLines) {
1063
1046
  console.log(` ${line}`);
@@ -1133,7 +1116,12 @@ async function mainScreen(rl, ask) {
1133
1116
  const active = sess.isActive ? ' ●' : '';
1134
1117
  const cat = sess.category ? ` [${sess.category}]` : '';
1135
1118
  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));
1119
+ // If the name is still the "Session XXXXXXXX" fallback, try the project path instead
1120
+ let rawName = sess.name || '';
1121
+ if (/^Session [0-9a-f]{8,}$/i.test(rawName)) {
1122
+ rawName = sess.project ? sess.project.replace(/^-/, '/').replace(/-/g, '/') : sess.id.slice(0, 8);
1123
+ }
1124
+ const displayName = rawName.length > 40 ? rawName.slice(0, 37) + '...' : (rawName || sess.id.slice(0, 8));
1137
1125
  console.log(` [${i + 1}] ${pin}${tool} ${sess.age.padEnd(8)} ${displayName}${active}${cat}`);
1138
1126
  });
1139
1127
  console.log('');
@@ -1171,15 +1159,41 @@ async function mainScreen(rl, ask) {
1171
1159
  console.log(' [r] Resume (full list)');
1172
1160
  console.log(' [/] Search sessions');
1173
1161
  console.log(' [e] Manage sessions');
1174
- console.log(' [i] Import from replit-tools');
1175
1162
  console.log(' [m] Manage subscriptions');
1176
- console.log(' [d] Switch to data-tools');
1177
1163
  console.log(' [s] Settings');
1164
+ console.log(' [?] Help & shortcuts');
1165
+ console.log('');
1166
+ console.log(' \x1b[2mreplit-tools:\x1b[0m');
1167
+ console.log(' [i] Import sessions');
1168
+ console.log(' [d] Switch to data-tools');
1169
+ console.log('');
1178
1170
  console.log(' [q] Exit');
1179
1171
  console.log('');
1180
1172
 
1181
1173
  const choice = (await ask(' Choice: ')).trim().toLowerCase();
1182
1174
 
1175
+ if (choice === '?') {
1176
+ const W = 37;
1177
+ const helpTop = ` ┌${'─'.repeat(W)}┐`;
1178
+ const helpSep = ` ├${'─'.repeat(W)}┤`;
1179
+ const helpBottom = ` └${'─'.repeat(W)}┘`;
1180
+ const helpPad = (s) => s + ' '.repeat(Math.max(0, W - s.length));
1181
+ console.log('');
1182
+ console.log(helpTop);
1183
+ console.log(` │ ${helpPad('At ~/workspace$ prompt:')}│`);
1184
+ console.log(` │ ${helpPad('db = show this menu')}│`);
1185
+ console.log(` │ ${helpPad('j = login to claude')}│`);
1186
+ console.log(` │ ${helpPad('k = login to codex')}│`);
1187
+ console.log(helpSep);
1188
+ console.log(` │ ${helpPad('In Claude:')}│`);
1189
+ console.log(` │ ${helpPad('Ctrl+C x2 = back to menu')}│`);
1190
+ console.log(` │ ${helpPad('Ctrl+C x3 = exit to shell')}│`);
1191
+ console.log(helpBottom);
1192
+ console.log('');
1193
+ await ask(' Press Enter to continue...');
1194
+ return { next: 'main' };
1195
+ }
1196
+
1183
1197
  if (choice === 'n') { return { next: 'new-session' }; }
1184
1198
 
1185
1199
  if (choice === 'c') {
@@ -1696,18 +1710,16 @@ async function runOnboardingWizard(detection, cwd, rl) {
1696
1710
  console.log(wRow(`Step 1 of 5: Detected providers`));
1697
1711
  console.log(wSep);
1698
1712
 
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;
1713
+ // Plan tier is inferred from auth config signals — not the actual plan name.
1714
+ // Show the tier ($20/$100/$200) with "configured" suffix to be honest.
1715
+ const claudePlanSuffix = claudeReady && plans.claude ? ` · ${plans.claude} configured` : '';
1716
+ const openaiPlanSuffix = openaiReady && plans.openai ? ` · ${plans.openai} configured` : '';
1705
1717
 
1706
1718
  console.log(wRow(claudeReady
1707
- ? `✓ Claude CLI ${claudePlanLabel ? `(${claudePlanLabel})` : ''}`
1719
+ ? `✓ Claude CLI${claudePlanSuffix}`
1708
1720
  : `✗ Claude CLI not logged in`));
1709
1721
  console.log(wRow(openaiReady
1710
- ? `✓ Codex CLI ${openaiPlanLabel ? `(${openaiPlanLabel})` : ''}`
1722
+ ? `✓ Codex CLI${openaiPlanSuffix}`
1711
1723
  : `✗ Codex CLI not logged in`));
1712
1724
  if (existingSessions.length > 0) {
1713
1725
  console.log(wRow(`✓ ${existingSessions.length} data-tools session${existingSessions.length !== 1 ? 's' : ''} found`));
@@ -1747,31 +1759,33 @@ async function runOnboardingWizard(detection, cwd, rl) {
1747
1759
  console.log(wSep);
1748
1760
 
1749
1761
  if (claudeReady) {
1750
- const detectedClaudePlan = plans.claude || 'pro';
1751
- const detectedClaudeLabel = CLAUDE_PLAN_LABELS[detectedClaudePlan] ?? detectedClaudePlan;
1752
- console.log(wRow(`Claude detected: ${detectedClaudeLabel}`));
1762
+ // Plan tier is inferred from auth config (rate-limit signal), not the actual plan name.
1763
+ const configuredClaudePlan = plans.claude || '$20';
1764
+ const configuredClaudeDesc = configuredClaudePlan + ' configured';
1765
+ console.log(wRow(`Claude — ${configuredClaudeDesc}`));
1753
1766
  console.log(wRow(` [1] Pro ($20/mo)`));
1754
1767
  console.log(wRow(` [2] Max x5 ($100/mo)`));
1755
1768
  console.log(wRow(` [3] Max x20 ($200/mo)`));
1756
- console.log(wRow(` [Enter] Keep detected (${detectedClaudeLabel})`));
1769
+ console.log(wRow(` [Enter] Keep configured (${configuredClaudePlan})`));
1757
1770
  console.log(wSep);
1758
1771
  const claudeChoice = (await ask(' Claude plan [1/2/3/Enter]: ')).trim();
1759
1772
  const claudePlanMap = { '1': 'pro', '2': 'max5', '3': 'max20' };
1760
- state.claudePlan = claudePlanMap[claudeChoice] || detectedClaudePlan;
1773
+ state.claudePlan = claudePlanMap[claudeChoice] || configuredClaudePlan;
1761
1774
  }
1762
1775
 
1763
1776
  if (openaiReady) {
1764
- const detectedOpenaiPlan = plans.openai || 'plus';
1765
- const detectedOpenaiLabel = OPENAI_PLAN_LABELS[detectedOpenaiPlan] ?? detectedOpenaiPlan;
1766
- console.log(wRow(`OpenAI detected: ${detectedOpenaiLabel}`));
1777
+ // Plan tier is inferred from JWT claim in auth config, not the actual plan name.
1778
+ const configuredOpenaiPlan = plans.openai || '$20';
1779
+ const configuredOpenaiDesc = configuredOpenaiPlan + ' configured';
1780
+ console.log(wRow(`OpenAI — ${configuredOpenaiDesc}`));
1767
1781
  console.log(wRow(` [1] Plus ($20/mo)`));
1768
1782
  console.log(wRow(` [2] Pro ($100/mo)`));
1769
1783
  console.log(wRow(` [3] Pro ($200/mo higher limits)`));
1770
- console.log(wRow(` [Enter] Keep detected (${detectedOpenaiLabel})`));
1784
+ console.log(wRow(` [Enter] Keep configured (${configuredOpenaiPlan})`));
1771
1785
  console.log(wSep);
1772
1786
  const openaiChoice = (await ask(' OpenAI plan [1/2/3/Enter]: ')).trim();
1773
1787
  const openaiPlanMap = { '1': 'plus', '2': 'pro', '3': 'pro200' };
1774
- state.openaiPlan = openaiPlanMap[openaiChoice] || detectedOpenaiPlan;
1788
+ state.openaiPlan = openaiPlanMap[openaiChoice] || configuredOpenaiPlan;
1775
1789
  }
1776
1790
 
1777
1791
  console.log(wBottom);
@@ -2053,8 +2067,9 @@ async function diagnosticsScreen(rl, ask) {
2053
2067
 
2054
2068
  const claudeHealthBadge = auth.claude.found ? _providerBadge('claude') : 'not logged in';
2055
2069
  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';
2070
+ // Plan tier is inferred from auth config signals — show tier with "configured" to be honest.
2071
+ const claudePlanStr = plans.claude ? `${plans.claude} configured` : 'unknown';
2072
+ const openaiPlanStr = plans.openai ? `${plans.openai} configured` : 'unknown';
2058
2073
 
2059
2074
  // ── Enforcement checks ────────────────────────────────────────────────────
2060
2075
  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.20",
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;