dual-brain 7.1.10 → 7.1.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 +177 -47
- package/package.json +1 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -126,7 +126,7 @@ function printSubscriptionTable(auth, profile) {
|
|
|
126
126
|
|
|
127
127
|
const claudeLine1 = auth.claude.found
|
|
128
128
|
? ` Claude: logged in (${auth.claude.source})`
|
|
129
|
-
: ` Claude: not logged in — run: claude login`;
|
|
129
|
+
: ` Claude: not logged in — run: claude auth login`;
|
|
130
130
|
const claudeLine2 = ` plan: ${claudePlanLabel}${claudeLabel}`;
|
|
131
131
|
|
|
132
132
|
const openaiLine1 = auth.openai.found
|
|
@@ -156,7 +156,7 @@ async function cmdInit(rl) {
|
|
|
156
156
|
const noneFound = !auth.claude.found && !auth.openai.found;
|
|
157
157
|
if (noneFound) {
|
|
158
158
|
console.log('\nNo AI provider found. Log in first:');
|
|
159
|
-
console.log(' Claude: claude login');
|
|
159
|
+
console.log(' Claude: claude auth login');
|
|
160
160
|
console.log(' OpenAI: codex login\n');
|
|
161
161
|
console.log('Then re-run: dual-brain init');
|
|
162
162
|
return;
|
|
@@ -189,7 +189,7 @@ async function cmdAuth(subArgs = []) {
|
|
|
189
189
|
|
|
190
190
|
if (!auth.claude.found || !auth.openai.found) {
|
|
191
191
|
console.log('');
|
|
192
|
-
if (!auth.claude.found) console.log(' Claude not logged in. Run: claude login');
|
|
192
|
+
if (!auth.claude.found) console.log(' Claude not logged in. Run: claude auth login');
|
|
193
193
|
if (!auth.openai.found) console.log(' OpenAI not logged in. Run: codex login');
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -525,6 +525,35 @@ function cmdForget(text) {
|
|
|
525
525
|
|
|
526
526
|
// ─── Screen helpers ───────────────────────────────────────────────────────────
|
|
527
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Render the data-tools-style rounded header box for the main screen.
|
|
530
|
+
* Inner width is 39 chars. Lines are padded with spaces to fill the box.
|
|
531
|
+
*/
|
|
532
|
+
function renderHeader(version, providerLines) {
|
|
533
|
+
const W = 39; // inner width
|
|
534
|
+
const pad = (s) => {
|
|
535
|
+
// Strip ANSI codes for length calculation
|
|
536
|
+
const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
537
|
+
return s + ' '.repeat(Math.max(0, W - visible.length));
|
|
538
|
+
};
|
|
539
|
+
const top = ` ┌${'─'.repeat(W)}┐`;
|
|
540
|
+
const sep = ` ├${'─'.repeat(W)}┤`;
|
|
541
|
+
const bottom = ` └${'─'.repeat(W)}┘`;
|
|
542
|
+
|
|
543
|
+
const title = `DATA Tools - Dual Brain v${version}`;
|
|
544
|
+
const credit = `by Steve Moraco + dual-brain`;
|
|
545
|
+
|
|
546
|
+
const lines = [top];
|
|
547
|
+
lines.push(` │ ${pad(title)}│`);
|
|
548
|
+
lines.push(` │ ${pad(credit)}│`);
|
|
549
|
+
lines.push(sep);
|
|
550
|
+
for (const pl of providerLines) {
|
|
551
|
+
lines.push(` │ ${pad(pl)}│`);
|
|
552
|
+
}
|
|
553
|
+
lines.push(bottom);
|
|
554
|
+
return lines.join('\n');
|
|
555
|
+
}
|
|
556
|
+
|
|
528
557
|
function profileExists(cwd) {
|
|
529
558
|
const dir = cwd || process.cwd();
|
|
530
559
|
const globalPath = join(process.env.HOME || '/root', '.config', 'dual-brain', 'profile.json');
|
|
@@ -609,8 +638,8 @@ async function welcomeScreen(rl, ask) {
|
|
|
609
638
|
|
|
610
639
|
if (!claudeReady && !openaiReady) {
|
|
611
640
|
console.log('No CLI login found. Log in first:');
|
|
612
|
-
console.log(' claude login — for Claude');
|
|
613
|
-
console.log(' codex login
|
|
641
|
+
console.log(' claude auth login — for Claude');
|
|
642
|
+
console.log(' codex login — for OpenAI/Codex\n');
|
|
614
643
|
console.log('Then re-run: dual-brain init');
|
|
615
644
|
return { next: 'exit' };
|
|
616
645
|
}
|
|
@@ -777,7 +806,13 @@ async function mainScreen(rl, ask) {
|
|
|
777
806
|
|
|
778
807
|
function subLine(name, plan, found, expired, days, sub) {
|
|
779
808
|
const label = sub?.label ? ` [${sub.label}]` : '';
|
|
780
|
-
if (!found) return `⚠️ ${name}: not logged in — run: ${name === 'Claude' ? 'claude login' : 'codex login'}`;
|
|
809
|
+
if (!found) return `⚠️ ${name}: not logged in — run: ${name === 'Claude' ? 'claude auth login' : 'codex login'}`;
|
|
810
|
+
// Multi-sub: show aggregated counts when more than one sub exists
|
|
811
|
+
const subs = sub?.subs;
|
|
812
|
+
if (subs && subs.length > 1) {
|
|
813
|
+
const aggregate = aggregatePlans(subs);
|
|
814
|
+
return `✅ ${name}: ${aggregate} [${subs.length} subs]`;
|
|
815
|
+
}
|
|
781
816
|
if (expired) return `🔴 ${name}: ${plan} expired${label} — will re-auth`;
|
|
782
817
|
const daysNote = (days !== null && days <= 7) ? ` (${days}d left)` : '';
|
|
783
818
|
return `✅ ${name}: ${plan}${label}${daysNote}`;
|
|
@@ -789,7 +824,7 @@ async function mainScreen(rl, ask) {
|
|
|
789
824
|
];
|
|
790
825
|
|
|
791
826
|
console.log('');
|
|
792
|
-
console.log(
|
|
827
|
+
console.log(renderHeader(version, headerLines));
|
|
793
828
|
|
|
794
829
|
// Auto-refresh expired subscriptions
|
|
795
830
|
if (claudeExpired || openaiExpired) {
|
|
@@ -799,7 +834,7 @@ async function mainScreen(rl, ask) {
|
|
|
799
834
|
if (openaiExpired) expired.push('OpenAI');
|
|
800
835
|
console.log(`\n ${expired.join(' & ')} subscription expired. Re-authenticating...`);
|
|
801
836
|
if (claudeExpired) {
|
|
802
|
-
const r = spawnSync('claude', ['login'], { stdio: 'inherit', timeout: 30000 });
|
|
837
|
+
const r = spawnSync('claude', ['auth', 'login'], { stdio: 'inherit', timeout: 30000 });
|
|
803
838
|
if (r.status === 0) {
|
|
804
839
|
claudeSub.expiresAt = null;
|
|
805
840
|
saveProfile(profile, { cwd });
|
|
@@ -820,7 +855,7 @@ async function mainScreen(rl, ask) {
|
|
|
820
855
|
const recentSessions = enrichSessions(importReplitSessions(cwd), cwd).slice(0, 7);
|
|
821
856
|
|
|
822
857
|
if (recentSessions.length > 0) {
|
|
823
|
-
console.log(
|
|
858
|
+
console.log(' Recent Sessions:');
|
|
824
859
|
recentSessions.forEach((sess, i) => {
|
|
825
860
|
const pin = sess.pinned ? '📌 ' : ' ';
|
|
826
861
|
const active = sess.isActive ? ' ●' : '';
|
|
@@ -830,18 +865,17 @@ async function mainScreen(rl, ask) {
|
|
|
830
865
|
console.log('');
|
|
831
866
|
}
|
|
832
867
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
menuOpts.push({ key: 'n', label: 'New session', section: 'Sessions' });
|
|
868
|
+
console.log(' [c] Continue last session');
|
|
869
|
+
console.log(' [n] New session');
|
|
836
870
|
if (recentSessions.length > 0) {
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
console.log(
|
|
871
|
+
console.log(' [1-9] Resume numbered above');
|
|
872
|
+
}
|
|
873
|
+
console.log(' [e] Manage sessions');
|
|
874
|
+
console.log(' [i] Import from replit-tools');
|
|
875
|
+
console.log(' [m] Manage subscriptions');
|
|
876
|
+
console.log(' [d] Switch to data-tools');
|
|
877
|
+
console.log(' [s] Settings');
|
|
878
|
+
console.log(' [q] Exit');
|
|
845
879
|
console.log('');
|
|
846
880
|
|
|
847
881
|
const choice = (await ask(' Choice: ')).trim().toLowerCase();
|
|
@@ -872,6 +906,18 @@ async function mainScreen(rl, ask) {
|
|
|
872
906
|
|
|
873
907
|
if (choice === 'e') { return { next: 'sessions' }; }
|
|
874
908
|
|
|
909
|
+
if (choice === 'i') {
|
|
910
|
+
const sessions = importReplitSessions(cwd);
|
|
911
|
+
if (sessions.length === 0) {
|
|
912
|
+
console.log('\n No replit-tools sessions found to import.\n');
|
|
913
|
+
} else {
|
|
914
|
+
console.log(`\n ✅ Found ${sessions.length} sessions from replit-tools.`);
|
|
915
|
+
console.log(' Sessions are automatically available in the list above.\n');
|
|
916
|
+
}
|
|
917
|
+
await ask(' Press Enter to continue...');
|
|
918
|
+
return { next: 'main' };
|
|
919
|
+
}
|
|
920
|
+
|
|
875
921
|
if (choice === 'd') {
|
|
876
922
|
const { spawnSync } = await import('node:child_process');
|
|
877
923
|
const which = spawnSync('which', ['claude-menu'], { encoding: 'utf8' });
|
|
@@ -1000,6 +1046,26 @@ async function settingsScreen(rl, ask) {
|
|
|
1000
1046
|
return { next: 'settings' };
|
|
1001
1047
|
}
|
|
1002
1048
|
|
|
1049
|
+
// ─── Helper: aggregatePlans ───────────────────────────────────────────────────
|
|
1050
|
+
|
|
1051
|
+
const PLAN_PRICES = {
|
|
1052
|
+
pro: '$20', max5: '$100', max20: '$200',
|
|
1053
|
+
plus: '$20', pro100: '$100', pro200: '$200',
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
function aggregatePlans(subs) {
|
|
1057
|
+
if (!subs || subs.length === 0) return '';
|
|
1058
|
+
const counts = {};
|
|
1059
|
+
for (const s of subs) {
|
|
1060
|
+
const price = PLAN_PRICES[s.plan] || s.plan;
|
|
1061
|
+
counts[price] = (counts[price] || 0) + 1;
|
|
1062
|
+
}
|
|
1063
|
+
return Object.entries(counts)
|
|
1064
|
+
.sort((a, b) => parseInt(b[0].slice(1)) - parseInt(a[0].slice(1)))
|
|
1065
|
+
.map(([price, count]) => `${price}×${count}`)
|
|
1066
|
+
.join(' ');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1003
1069
|
// ─── Screen: subscriptionsScreen ─────────────────────────────────────────────
|
|
1004
1070
|
|
|
1005
1071
|
async function subscriptionsScreen(rl, ask) {
|
|
@@ -1007,37 +1073,49 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1007
1073
|
const cwd = process.cwd();
|
|
1008
1074
|
const profile = loadProfile(cwd);
|
|
1009
1075
|
const auth = await detectAuth();
|
|
1010
|
-
const plans = detectPlans();
|
|
1011
1076
|
|
|
1012
|
-
//
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const d = sub?.expiresAt ? daysUntil(sub.expiresAt) : null;
|
|
1019
|
-
const expiry = d !== null ? ` (${d < 0 ? 'expired' : d === 0 ? 'today' : `${d}d left`})` : '';
|
|
1020
|
-
lines.push(` ✅ Claude: ${plan}${label}${expiry}`);
|
|
1021
|
-
} else {
|
|
1022
|
-
lines.push(` ⚠️ Claude: not linked`);
|
|
1077
|
+
// Backward compat: migrate old single-sub format to subs array
|
|
1078
|
+
for (const prov of ['claude', 'openai']) {
|
|
1079
|
+
const p = profile?.providers?.[prov];
|
|
1080
|
+
if (p && !p.subs && p.plan) {
|
|
1081
|
+
p.subs = [{ plan: p.plan, label: p.label || null, expiresAt: p.expiresAt || null }];
|
|
1082
|
+
}
|
|
1023
1083
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1084
|
+
|
|
1085
|
+
// Build status lines — roster format
|
|
1086
|
+
const lines = [];
|
|
1087
|
+
|
|
1088
|
+
function buildProviderLines(provKey, displayName, authFound) {
|
|
1089
|
+
const sub = profile?.providers?.[provKey];
|
|
1090
|
+
const subs = sub?.subs || [];
|
|
1091
|
+
if (!authFound && subs.length === 0) {
|
|
1092
|
+
lines.push(` ⚠️ ${displayName}: not linked`);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const aggregate = aggregatePlans(subs);
|
|
1096
|
+
const prefix = authFound ? '✅' : '⚠️ ';
|
|
1097
|
+
lines.push(` ${prefix} ${displayName}:${aggregate ? ' ' + aggregate : ' (no subs)'}`);
|
|
1098
|
+
subs.forEach((s, i) => {
|
|
1099
|
+
const planLabels = provKey === 'claude' ? CLAUDE_PLAN_LABELS : OPENAI_PLAN_LABELS;
|
|
1100
|
+
const planLabel = planLabels[s.plan] ?? s.plan ?? 'unknown';
|
|
1101
|
+
const nameStr = (s.label || '(no label)').padEnd(22);
|
|
1102
|
+
const d = s.expiresAt ? daysUntil(s.expiresAt) : null;
|
|
1103
|
+
const expiry = d === null ? '' : d < 0 ? ' (expired)' : d === 0 ? ' (today)' : ` (${d}d left)`;
|
|
1104
|
+
lines.push(` ${i + 1}. ${nameStr} ${planLabel}${expiry}`);
|
|
1105
|
+
});
|
|
1033
1106
|
}
|
|
1034
1107
|
|
|
1108
|
+
buildProviderLines('claude', 'Claude', auth.claude.found);
|
|
1109
|
+
lines.push('');
|
|
1110
|
+
buildProviderLines('openai', 'OpenAI', auth.openai.found);
|
|
1111
|
+
|
|
1035
1112
|
console.log(box('Subscriptions', lines));
|
|
1036
1113
|
console.log('');
|
|
1037
1114
|
|
|
1038
1115
|
const menuOpts = [
|
|
1039
1116
|
{ key: '1', label: 'Add Claude sub', section: 'Link' },
|
|
1040
1117
|
{ key: '2', label: 'Add Codex sub', section: 'Link' },
|
|
1118
|
+
{ key: 'r', label: 'Remove a sub', section: 'Link' },
|
|
1041
1119
|
{ key: 'b', label: 'Back to home', section: '' },
|
|
1042
1120
|
];
|
|
1043
1121
|
console.log(menu(menuOpts));
|
|
@@ -1049,7 +1127,7 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1049
1127
|
console.log('\n Linking Claude subscription...');
|
|
1050
1128
|
console.log(' A browser window will open — paste the code below when prompted.\n');
|
|
1051
1129
|
const { spawnSync } = await import('node:child_process');
|
|
1052
|
-
const r = spawnSync('claude', ['login'], { stdio: 'inherit', timeout: 60000 });
|
|
1130
|
+
const r = spawnSync('claude', ['auth', 'login'], { stdio: 'inherit', timeout: 60000 });
|
|
1053
1131
|
if (r.status === 0) {
|
|
1054
1132
|
console.log('\n ✅ Claude linked successfully!\n');
|
|
1055
1133
|
const label = (await ask(" Label (e.g. \"Josh's $100 sub\", or Enter to skip): ")).trim();
|
|
@@ -1060,8 +1138,9 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1060
1138
|
if (!profile.providers.claude) profile.providers.claude = { enabled: true };
|
|
1061
1139
|
profile.providers.claude.plan = plan;
|
|
1062
1140
|
profile.providers.claude.enabled = true;
|
|
1063
|
-
|
|
1064
|
-
if (
|
|
1141
|
+
// Push to subs array instead of overwriting
|
|
1142
|
+
if (!profile.providers.claude.subs) profile.providers.claude.subs = [];
|
|
1143
|
+
profile.providers.claude.subs.push({ plan, label: label || null, expiresAt: expiry || null });
|
|
1065
1144
|
saveProfile(profile, { cwd });
|
|
1066
1145
|
console.log(' ✓ Saved\n');
|
|
1067
1146
|
await ask(' Press Enter to continue...');
|
|
@@ -1087,8 +1166,9 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1087
1166
|
if (!profile.providers.openai) profile.providers.openai = { enabled: true };
|
|
1088
1167
|
profile.providers.openai.plan = plan;
|
|
1089
1168
|
profile.providers.openai.enabled = true;
|
|
1090
|
-
|
|
1091
|
-
if (
|
|
1169
|
+
// Push to subs array instead of overwriting
|
|
1170
|
+
if (!profile.providers.openai.subs) profile.providers.openai.subs = [];
|
|
1171
|
+
profile.providers.openai.subs.push({ plan, label: label || null, expiresAt: expiry || null });
|
|
1092
1172
|
saveProfile(profile, { cwd });
|
|
1093
1173
|
console.log(' ✓ Saved\n');
|
|
1094
1174
|
await ask(' Press Enter to continue...');
|
|
@@ -1099,6 +1179,56 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1099
1179
|
return { next: 'subscriptions' };
|
|
1100
1180
|
}
|
|
1101
1181
|
|
|
1182
|
+
if (choice === 'r') {
|
|
1183
|
+
// Build a flat numbered list of all subs across both providers
|
|
1184
|
+
const allSubs = [];
|
|
1185
|
+
for (const [provKey, displayName] of [['claude', 'Claude'], ['openai', 'OpenAI']]) {
|
|
1186
|
+
const subs = profile?.providers?.[provKey]?.subs || [];
|
|
1187
|
+
for (const s of subs) {
|
|
1188
|
+
allSubs.push({ provKey, displayName, sub: s });
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (allSubs.length === 0) {
|
|
1193
|
+
console.log('\n No subscriptions to remove.\n');
|
|
1194
|
+
await ask(' Press Enter to continue...');
|
|
1195
|
+
return { next: 'subscriptions' };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
console.log('\n Remove a subscription:\n');
|
|
1199
|
+
allSubs.forEach(({ displayName, sub }, i) => {
|
|
1200
|
+
const planLabels = displayName === 'Claude' ? CLAUDE_PLAN_LABELS : OPENAI_PLAN_LABELS;
|
|
1201
|
+
const planLabel = planLabels[sub.plan] ?? sub.plan ?? 'unknown';
|
|
1202
|
+
const labelStr = sub.label ? ` [${sub.label}]` : '';
|
|
1203
|
+
console.log(` (${i + 1}) ${displayName}: ${planLabel}${labelStr}`);
|
|
1204
|
+
});
|
|
1205
|
+
console.log(' (Enter) Cancel\n');
|
|
1206
|
+
|
|
1207
|
+
const numStr = (await ask(' Remove #: ')).trim();
|
|
1208
|
+
const numChoice = parseInt(numStr, 10);
|
|
1209
|
+
if (!isNaN(numChoice) && numChoice >= 1 && numChoice <= allSubs.length) {
|
|
1210
|
+
const { provKey, sub } = allSubs[numChoice - 1];
|
|
1211
|
+
const confirm = (await ask(` Remove "${sub.label || sub.plan}" from ${provKey}? (y/N): `)).trim().toLowerCase();
|
|
1212
|
+
if (confirm === 'y') {
|
|
1213
|
+
const subs = profile.providers[provKey].subs;
|
|
1214
|
+
const idx = subs.indexOf(sub);
|
|
1215
|
+
if (idx !== -1) subs.splice(idx, 1);
|
|
1216
|
+
// Update top-level plan to first remaining sub (or keep as-is)
|
|
1217
|
+
if (subs.length > 0) {
|
|
1218
|
+
profile.providers[provKey].plan = subs[0].plan;
|
|
1219
|
+
}
|
|
1220
|
+
saveProfile(profile, { cwd });
|
|
1221
|
+
console.log(' ✓ Removed\n');
|
|
1222
|
+
} else {
|
|
1223
|
+
console.log(' Cancelled.\n');
|
|
1224
|
+
}
|
|
1225
|
+
} else {
|
|
1226
|
+
console.log(' Cancelled.\n');
|
|
1227
|
+
}
|
|
1228
|
+
await ask(' Press Enter to continue...');
|
|
1229
|
+
return { next: 'subscriptions' };
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1102
1232
|
return { next: 'main' };
|
|
1103
1233
|
}
|
|
1104
1234
|
|
|
@@ -1128,7 +1258,7 @@ async function authScreen(rl, ask) {
|
|
|
1128
1258
|
'Claude:',
|
|
1129
1259
|
auth.claude.found
|
|
1130
1260
|
? ` logged in via ${auth.claude.source}`
|
|
1131
|
-
: ` not logged in — run: claude login`,
|
|
1261
|
+
: ` not logged in — run: claude auth login`,
|
|
1132
1262
|
` plan: ${claudePlanLabel}${claudeSub?.label ? ` [${claudeSub.label}]` : ''}`,
|
|
1133
1263
|
'',
|
|
1134
1264
|
'OpenAI:',
|
package/package.json
CHANGED