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.
- package/bin/dual-brain.mjs +80 -61
- package/package.json +1 -1
- package/src/profile.mjs +26 -25
package/bin/dual-brain.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
|
751
|
-
else detectedLines.push(` Claude
|
|
752
|
-
if (openaiReady) detectedLines.push(` Codex
|
|
753
|
-
else detectedLines.push(` Codex
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
const
|
|
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
|
|
1723
|
+
? `✓ Claude CLI${claudePlanSuffix}`
|
|
1708
1724
|
: `✗ Claude CLI not logged in`));
|
|
1709
1725
|
console.log(wRow(openaiReady
|
|
1710
|
-
? `✓ Codex CLI
|
|
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
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
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
|
|
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] ||
|
|
1777
|
+
state.claudePlan = claudePlanMap[claudeChoice] || configuredClaudePlan;
|
|
1761
1778
|
}
|
|
1762
1779
|
|
|
1763
1780
|
if (openaiReady) {
|
|
1764
|
-
|
|
1765
|
-
const
|
|
1766
|
-
|
|
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
|
|
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] ||
|
|
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
|
-
|
|
2057
|
-
const
|
|
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
package/src/profile.mjs
CHANGED
|
@@ -240,9 +240,14 @@ function decodeJwtPayload(token) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
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
|
-
//
|
|
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
|
|
382
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
|
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;
|