dual-brain 7.1.10 → 7.1.12
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 +217 -48
- package/package.json +1 -1
- package/src/session.mjs +108 -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}`;
|
|
@@ -788,8 +823,32 @@ async function mainScreen(rl, ask) {
|
|
|
788
823
|
subLine('OpenAI', openaiPlan, auth.openai.found, openaiExpired, openaiDays, openaiSub),
|
|
789
824
|
];
|
|
790
825
|
|
|
826
|
+
console.log(`📦 DATA Tools - Dual Brain v${version}`);
|
|
791
827
|
console.log('');
|
|
792
|
-
|
|
828
|
+
|
|
829
|
+
// Help shortcuts box (matching data-tools style)
|
|
830
|
+
const W = 37;
|
|
831
|
+
const helpTop = ` ┌${'─'.repeat(W)}┐`;
|
|
832
|
+
const helpSep = ` ├${'─'.repeat(W)}┤`;
|
|
833
|
+
const helpBottom = ` └${'─'.repeat(W)}┘`;
|
|
834
|
+
const helpPad = (s) => s + ' '.repeat(Math.max(0, W - s.length));
|
|
835
|
+
|
|
836
|
+
console.log(helpTop);
|
|
837
|
+
console.log(` │ ${helpPad('At ~/workspace$ prompt:')}│`);
|
|
838
|
+
console.log(` │ ${helpPad('db = show this menu')}│`);
|
|
839
|
+
console.log(` │ ${helpPad('j = login to claude')}│`);
|
|
840
|
+
console.log(` │ ${helpPad('k = login to codex')}│`);
|
|
841
|
+
console.log(helpSep);
|
|
842
|
+
console.log(` │ ${helpPad('In Claude:')}│`);
|
|
843
|
+
console.log(` │ ${helpPad('Ctrl+C x2 = back to menu')}│`);
|
|
844
|
+
console.log(` │ ${helpPad('Ctrl+C x3 = exit to shell')}│`);
|
|
845
|
+
console.log(helpBottom);
|
|
846
|
+
console.log('');
|
|
847
|
+
|
|
848
|
+
// Provider status (outside the box)
|
|
849
|
+
for (const line of headerLines) {
|
|
850
|
+
console.log(` ${line}`);
|
|
851
|
+
}
|
|
793
852
|
|
|
794
853
|
// Auto-refresh expired subscriptions
|
|
795
854
|
if (claudeExpired || openaiExpired) {
|
|
@@ -799,7 +858,7 @@ async function mainScreen(rl, ask) {
|
|
|
799
858
|
if (openaiExpired) expired.push('OpenAI');
|
|
800
859
|
console.log(`\n ${expired.join(' & ')} subscription expired. Re-authenticating...`);
|
|
801
860
|
if (claudeExpired) {
|
|
802
|
-
const r = spawnSync('claude', ['login'], { stdio: 'inherit', timeout: 30000 });
|
|
861
|
+
const r = spawnSync('claude', ['auth', 'login'], { stdio: 'inherit', timeout: 30000 });
|
|
803
862
|
if (r.status === 0) {
|
|
804
863
|
claudeSub.expiresAt = null;
|
|
805
864
|
saveProfile(profile, { cwd });
|
|
@@ -820,28 +879,42 @@ async function mainScreen(rl, ask) {
|
|
|
820
879
|
const recentSessions = enrichSessions(importReplitSessions(cwd), cwd).slice(0, 7);
|
|
821
880
|
|
|
822
881
|
if (recentSessions.length > 0) {
|
|
823
|
-
console.log(
|
|
882
|
+
console.log(' Recent Sessions:');
|
|
824
883
|
recentSessions.forEach((sess, i) => {
|
|
825
884
|
const pin = sess.pinned ? '📌 ' : ' ';
|
|
826
885
|
const active = sess.isActive ? ' ●' : '';
|
|
827
886
|
const cat = sess.category ? ` [${sess.category}]` : '';
|
|
828
|
-
|
|
887
|
+
const tool = (sess.tool === 'codex') ? 'cdx' : 'cld';
|
|
888
|
+
console.log(` [${i + 1}] ${pin}${tool} ${sess.age.padEnd(8)} ${sess.name}${active}${cat}`);
|
|
829
889
|
});
|
|
830
890
|
console.log('');
|
|
831
891
|
}
|
|
832
892
|
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
893
|
+
const brandW = 37;
|
|
894
|
+
const brandTop = ` ┌${'─'.repeat(brandW)}┐`;
|
|
895
|
+
const brandBottom = ` └${'─'.repeat(brandW)}┘`;
|
|
896
|
+
const brandPad = (s) => {
|
|
897
|
+
const leftPad = Math.floor((brandW - s.length) / 2);
|
|
898
|
+
const rightPad = brandW - s.length - leftPad;
|
|
899
|
+
return ' '.repeat(leftPad) + s + ' '.repeat(rightPad);
|
|
900
|
+
};
|
|
901
|
+
console.log(brandTop);
|
|
902
|
+
console.log(` │ ${brandPad('Dual Brain Session Manager')}│`);
|
|
903
|
+
console.log(` │ ${brandPad('by Steve Moraco + dual-brain')}│`);
|
|
904
|
+
console.log(brandBottom);
|
|
905
|
+
console.log('');
|
|
906
|
+
|
|
907
|
+
console.log(' [c] Continue last session');
|
|
908
|
+
console.log(' [n] New session');
|
|
836
909
|
if (recentSessions.length > 0) {
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
console.log(
|
|
910
|
+
console.log(' [1-9] Resume numbered above');
|
|
911
|
+
}
|
|
912
|
+
console.log(' [e] Manage sessions');
|
|
913
|
+
console.log(' [i] Import from replit-tools');
|
|
914
|
+
console.log(' [m] Manage subscriptions');
|
|
915
|
+
console.log(' [d] Switch to data-tools');
|
|
916
|
+
console.log(' [s] Settings');
|
|
917
|
+
console.log(' [q] Exit');
|
|
845
918
|
console.log('');
|
|
846
919
|
|
|
847
920
|
const choice = (await ask(' Choice: ')).trim().toLowerCase();
|
|
@@ -872,6 +945,18 @@ async function mainScreen(rl, ask) {
|
|
|
872
945
|
|
|
873
946
|
if (choice === 'e') { return { next: 'sessions' }; }
|
|
874
947
|
|
|
948
|
+
if (choice === 'i') {
|
|
949
|
+
const sessions = importReplitSessions(cwd);
|
|
950
|
+
if (sessions.length === 0) {
|
|
951
|
+
console.log('\n No replit-tools sessions found to import.\n');
|
|
952
|
+
} else {
|
|
953
|
+
console.log(`\n ✅ Found ${sessions.length} sessions from replit-tools.`);
|
|
954
|
+
console.log(' Sessions are automatically available in the list above.\n');
|
|
955
|
+
}
|
|
956
|
+
await ask(' Press Enter to continue...');
|
|
957
|
+
return { next: 'main' };
|
|
958
|
+
}
|
|
959
|
+
|
|
875
960
|
if (choice === 'd') {
|
|
876
961
|
const { spawnSync } = await import('node:child_process');
|
|
877
962
|
const which = spawnSync('which', ['claude-menu'], { encoding: 'utf8' });
|
|
@@ -1000,6 +1085,26 @@ async function settingsScreen(rl, ask) {
|
|
|
1000
1085
|
return { next: 'settings' };
|
|
1001
1086
|
}
|
|
1002
1087
|
|
|
1088
|
+
// ─── Helper: aggregatePlans ───────────────────────────────────────────────────
|
|
1089
|
+
|
|
1090
|
+
const PLAN_PRICES = {
|
|
1091
|
+
pro: '$20', max5: '$100', max20: '$200',
|
|
1092
|
+
plus: '$20', pro100: '$100', pro200: '$200',
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
function aggregatePlans(subs) {
|
|
1096
|
+
if (!subs || subs.length === 0) return '';
|
|
1097
|
+
const counts = {};
|
|
1098
|
+
for (const s of subs) {
|
|
1099
|
+
const price = PLAN_PRICES[s.plan] || s.plan;
|
|
1100
|
+
counts[price] = (counts[price] || 0) + 1;
|
|
1101
|
+
}
|
|
1102
|
+
return Object.entries(counts)
|
|
1103
|
+
.sort((a, b) => parseInt(b[0].slice(1)) - parseInt(a[0].slice(1)))
|
|
1104
|
+
.map(([price, count]) => `${price}×${count}`)
|
|
1105
|
+
.join(' ');
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1003
1108
|
// ─── Screen: subscriptionsScreen ─────────────────────────────────────────────
|
|
1004
1109
|
|
|
1005
1110
|
async function subscriptionsScreen(rl, ask) {
|
|
@@ -1007,37 +1112,49 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1007
1112
|
const cwd = process.cwd();
|
|
1008
1113
|
const profile = loadProfile(cwd);
|
|
1009
1114
|
const auth = await detectAuth();
|
|
1010
|
-
const plans = detectPlans();
|
|
1011
1115
|
|
|
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`);
|
|
1116
|
+
// Backward compat: migrate old single-sub format to subs array
|
|
1117
|
+
for (const prov of ['claude', 'openai']) {
|
|
1118
|
+
const p = profile?.providers?.[prov];
|
|
1119
|
+
if (p && !p.subs && p.plan) {
|
|
1120
|
+
p.subs = [{ plan: p.plan, label: p.label || null, expiresAt: p.expiresAt || null }];
|
|
1121
|
+
}
|
|
1023
1122
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1123
|
+
|
|
1124
|
+
// Build status lines — roster format
|
|
1125
|
+
const lines = [];
|
|
1126
|
+
|
|
1127
|
+
function buildProviderLines(provKey, displayName, authFound) {
|
|
1128
|
+
const sub = profile?.providers?.[provKey];
|
|
1129
|
+
const subs = sub?.subs || [];
|
|
1130
|
+
if (!authFound && subs.length === 0) {
|
|
1131
|
+
lines.push(` ⚠️ ${displayName}: not linked`);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const aggregate = aggregatePlans(subs);
|
|
1135
|
+
const prefix = authFound ? '✅' : '⚠️ ';
|
|
1136
|
+
lines.push(` ${prefix} ${displayName}:${aggregate ? ' ' + aggregate : ' (no subs)'}`);
|
|
1137
|
+
subs.forEach((s, i) => {
|
|
1138
|
+
const planLabels = provKey === 'claude' ? CLAUDE_PLAN_LABELS : OPENAI_PLAN_LABELS;
|
|
1139
|
+
const planLabel = planLabels[s.plan] ?? s.plan ?? 'unknown';
|
|
1140
|
+
const nameStr = (s.label || '(no label)').padEnd(22);
|
|
1141
|
+
const d = s.expiresAt ? daysUntil(s.expiresAt) : null;
|
|
1142
|
+
const expiry = d === null ? '' : d < 0 ? ' (expired)' : d === 0 ? ' (today)' : ` (${d}d left)`;
|
|
1143
|
+
lines.push(` ${i + 1}. ${nameStr} ${planLabel}${expiry}`);
|
|
1144
|
+
});
|
|
1033
1145
|
}
|
|
1034
1146
|
|
|
1147
|
+
buildProviderLines('claude', 'Claude', auth.claude.found);
|
|
1148
|
+
lines.push('');
|
|
1149
|
+
buildProviderLines('openai', 'OpenAI', auth.openai.found);
|
|
1150
|
+
|
|
1035
1151
|
console.log(box('Subscriptions', lines));
|
|
1036
1152
|
console.log('');
|
|
1037
1153
|
|
|
1038
1154
|
const menuOpts = [
|
|
1039
1155
|
{ key: '1', label: 'Add Claude sub', section: 'Link' },
|
|
1040
1156
|
{ key: '2', label: 'Add Codex sub', section: 'Link' },
|
|
1157
|
+
{ key: 'r', label: 'Remove a sub', section: 'Link' },
|
|
1041
1158
|
{ key: 'b', label: 'Back to home', section: '' },
|
|
1042
1159
|
];
|
|
1043
1160
|
console.log(menu(menuOpts));
|
|
@@ -1049,7 +1166,7 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1049
1166
|
console.log('\n Linking Claude subscription...');
|
|
1050
1167
|
console.log(' A browser window will open — paste the code below when prompted.\n');
|
|
1051
1168
|
const { spawnSync } = await import('node:child_process');
|
|
1052
|
-
const r = spawnSync('claude', ['login'], { stdio: 'inherit', timeout: 60000 });
|
|
1169
|
+
const r = spawnSync('claude', ['auth', 'login'], { stdio: 'inherit', timeout: 60000 });
|
|
1053
1170
|
if (r.status === 0) {
|
|
1054
1171
|
console.log('\n ✅ Claude linked successfully!\n');
|
|
1055
1172
|
const label = (await ask(" Label (e.g. \"Josh's $100 sub\", or Enter to skip): ")).trim();
|
|
@@ -1060,8 +1177,9 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1060
1177
|
if (!profile.providers.claude) profile.providers.claude = { enabled: true };
|
|
1061
1178
|
profile.providers.claude.plan = plan;
|
|
1062
1179
|
profile.providers.claude.enabled = true;
|
|
1063
|
-
|
|
1064
|
-
if (
|
|
1180
|
+
// Push to subs array instead of overwriting
|
|
1181
|
+
if (!profile.providers.claude.subs) profile.providers.claude.subs = [];
|
|
1182
|
+
profile.providers.claude.subs.push({ plan, label: label || null, expiresAt: expiry || null });
|
|
1065
1183
|
saveProfile(profile, { cwd });
|
|
1066
1184
|
console.log(' ✓ Saved\n');
|
|
1067
1185
|
await ask(' Press Enter to continue...');
|
|
@@ -1087,8 +1205,9 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1087
1205
|
if (!profile.providers.openai) profile.providers.openai = { enabled: true };
|
|
1088
1206
|
profile.providers.openai.plan = plan;
|
|
1089
1207
|
profile.providers.openai.enabled = true;
|
|
1090
|
-
|
|
1091
|
-
if (
|
|
1208
|
+
// Push to subs array instead of overwriting
|
|
1209
|
+
if (!profile.providers.openai.subs) profile.providers.openai.subs = [];
|
|
1210
|
+
profile.providers.openai.subs.push({ plan, label: label || null, expiresAt: expiry || null });
|
|
1092
1211
|
saveProfile(profile, { cwd });
|
|
1093
1212
|
console.log(' ✓ Saved\n');
|
|
1094
1213
|
await ask(' Press Enter to continue...');
|
|
@@ -1099,6 +1218,56 @@ async function subscriptionsScreen(rl, ask) {
|
|
|
1099
1218
|
return { next: 'subscriptions' };
|
|
1100
1219
|
}
|
|
1101
1220
|
|
|
1221
|
+
if (choice === 'r') {
|
|
1222
|
+
// Build a flat numbered list of all subs across both providers
|
|
1223
|
+
const allSubs = [];
|
|
1224
|
+
for (const [provKey, displayName] of [['claude', 'Claude'], ['openai', 'OpenAI']]) {
|
|
1225
|
+
const subs = profile?.providers?.[provKey]?.subs || [];
|
|
1226
|
+
for (const s of subs) {
|
|
1227
|
+
allSubs.push({ provKey, displayName, sub: s });
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (allSubs.length === 0) {
|
|
1232
|
+
console.log('\n No subscriptions to remove.\n');
|
|
1233
|
+
await ask(' Press Enter to continue...');
|
|
1234
|
+
return { next: 'subscriptions' };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
console.log('\n Remove a subscription:\n');
|
|
1238
|
+
allSubs.forEach(({ displayName, sub }, i) => {
|
|
1239
|
+
const planLabels = displayName === 'Claude' ? CLAUDE_PLAN_LABELS : OPENAI_PLAN_LABELS;
|
|
1240
|
+
const planLabel = planLabels[sub.plan] ?? sub.plan ?? 'unknown';
|
|
1241
|
+
const labelStr = sub.label ? ` [${sub.label}]` : '';
|
|
1242
|
+
console.log(` (${i + 1}) ${displayName}: ${planLabel}${labelStr}`);
|
|
1243
|
+
});
|
|
1244
|
+
console.log(' (Enter) Cancel\n');
|
|
1245
|
+
|
|
1246
|
+
const numStr = (await ask(' Remove #: ')).trim();
|
|
1247
|
+
const numChoice = parseInt(numStr, 10);
|
|
1248
|
+
if (!isNaN(numChoice) && numChoice >= 1 && numChoice <= allSubs.length) {
|
|
1249
|
+
const { provKey, sub } = allSubs[numChoice - 1];
|
|
1250
|
+
const confirm = (await ask(` Remove "${sub.label || sub.plan}" from ${provKey}? (y/N): `)).trim().toLowerCase();
|
|
1251
|
+
if (confirm === 'y') {
|
|
1252
|
+
const subs = profile.providers[provKey].subs;
|
|
1253
|
+
const idx = subs.indexOf(sub);
|
|
1254
|
+
if (idx !== -1) subs.splice(idx, 1);
|
|
1255
|
+
// Update top-level plan to first remaining sub (or keep as-is)
|
|
1256
|
+
if (subs.length > 0) {
|
|
1257
|
+
profile.providers[provKey].plan = subs[0].plan;
|
|
1258
|
+
}
|
|
1259
|
+
saveProfile(profile, { cwd });
|
|
1260
|
+
console.log(' ✓ Removed\n');
|
|
1261
|
+
} else {
|
|
1262
|
+
console.log(' Cancelled.\n');
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
console.log(' Cancelled.\n');
|
|
1266
|
+
}
|
|
1267
|
+
await ask(' Press Enter to continue...');
|
|
1268
|
+
return { next: 'subscriptions' };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1102
1271
|
return { next: 'main' };
|
|
1103
1272
|
}
|
|
1104
1273
|
|
|
@@ -1128,7 +1297,7 @@ async function authScreen(rl, ask) {
|
|
|
1128
1297
|
'Claude:',
|
|
1129
1298
|
auth.claude.found
|
|
1130
1299
|
? ` logged in via ${auth.claude.source}`
|
|
1131
|
-
: ` not logged in — run: claude login`,
|
|
1300
|
+
: ` not logged in — run: claude auth login`,
|
|
1132
1301
|
` plan: ${claudePlanLabel}${claudeSub?.label ? ` [${claudeSub.label}]` : ''}`,
|
|
1133
1302
|
'',
|
|
1134
1303
|
'OpenAI:',
|
package/package.json
CHANGED
package/src/session.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* formatSessionCard(session, repo, health) → compact status card string (≤5 lines)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, renameSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, renameSync, readdirSync, statSync } from 'node:fs';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
|
|
16
16
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -309,6 +309,112 @@ export function importReplitSessions(cwd = process.cwd()) {
|
|
|
309
309
|
} catch { continue; }
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// Scan ~/.claude/projects/-home-runner-workspace/ for JSONL files not already in bySession
|
|
313
|
+
const projectsDir = join(process.env.HOME || '/root', '.claude', 'projects', '-home-runner-workspace');
|
|
314
|
+
if (existsSync(projectsDir)) {
|
|
315
|
+
try {
|
|
316
|
+
for (const f of readdirSync(projectsDir)) {
|
|
317
|
+
if (!f.endsWith('.jsonl') || f.startsWith('agent-')) continue;
|
|
318
|
+
const sessionId = f.replace('.jsonl', '');
|
|
319
|
+
if (bySession.has(sessionId)) continue;
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const content = readFileSync(join(projectsDir, f), 'utf8');
|
|
323
|
+
const lines = content.split('\n').filter(Boolean).slice(0, 50);
|
|
324
|
+
let firstPrompt = null;
|
|
325
|
+
let lastTimestamp = 0;
|
|
326
|
+
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
try {
|
|
329
|
+
const entry = JSON.parse(line);
|
|
330
|
+
if (entry.timestamp && entry.timestamp > lastTimestamp) lastTimestamp = entry.timestamp;
|
|
331
|
+
if (!firstPrompt && entry.type === 'user' && entry.message?.content) {
|
|
332
|
+
const text = typeof entry.message.content === 'string'
|
|
333
|
+
? entry.message.content
|
|
334
|
+
: entry.message.content?.[0]?.text;
|
|
335
|
+
if (text && !text.startsWith('/') && text.length < 200) {
|
|
336
|
+
firstPrompt = text;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch { continue; }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (lastTimestamp === 0) {
|
|
343
|
+
const stat = statSync(join(projectsDir, f));
|
|
344
|
+
lastTimestamp = Math.floor(stat.mtimeMs / 1000);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
bySession.set(sessionId, {
|
|
348
|
+
sessionId,
|
|
349
|
+
project: '-home-runner-workspace',
|
|
350
|
+
entries: [],
|
|
351
|
+
firstPrompt: firstPrompt || sessionId.slice(0, 8) + '...',
|
|
352
|
+
lastTimestamp,
|
|
353
|
+
});
|
|
354
|
+
} catch { continue; }
|
|
355
|
+
}
|
|
356
|
+
} catch { /* non-fatal */ }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Scan ~/.codex/sessions/ for codex session JSONLs (YYYY/MM/DD tree)
|
|
360
|
+
const codexSessionsDir = join(process.env.HOME || '/root', '.codex', 'sessions');
|
|
361
|
+
if (existsSync(codexSessionsDir)) {
|
|
362
|
+
try {
|
|
363
|
+
const walk = (dir) => {
|
|
364
|
+
let results = [];
|
|
365
|
+
try {
|
|
366
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
367
|
+
const full = join(dir, entry.name);
|
|
368
|
+
if (entry.isDirectory()) results = results.concat(walk(full));
|
|
369
|
+
else if (entry.isFile() && entry.name.endsWith('.jsonl')) results.push(full);
|
|
370
|
+
}
|
|
371
|
+
} catch {}
|
|
372
|
+
return results;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
for (const f of walk(codexSessionsDir)) {
|
|
376
|
+
try {
|
|
377
|
+
const content = readFileSync(f, 'utf8');
|
|
378
|
+
const lines = content.split('\n').filter(Boolean);
|
|
379
|
+
if (!lines.length) continue;
|
|
380
|
+
|
|
381
|
+
const meta = JSON.parse(lines[0]);
|
|
382
|
+
if (meta.type !== 'session_meta' || !meta.payload) continue;
|
|
383
|
+
if (meta.payload.cwd !== cwd && meta.payload.cwd !== '/home/runner/workspace') continue;
|
|
384
|
+
|
|
385
|
+
const id = meta.payload.id;
|
|
386
|
+
if (bySession.has(id)) continue;
|
|
387
|
+
|
|
388
|
+
let firstPrompt = null;
|
|
389
|
+
let lastTimestamp = Date.parse(meta.payload.timestamp || meta.timestamp) / 1000;
|
|
390
|
+
|
|
391
|
+
for (const ln of lines) {
|
|
392
|
+
try {
|
|
393
|
+
const j = JSON.parse(ln);
|
|
394
|
+
if (j.timestamp) {
|
|
395
|
+
const ts = Date.parse(j.timestamp) / 1000;
|
|
396
|
+
if (ts > lastTimestamp) lastTimestamp = ts;
|
|
397
|
+
}
|
|
398
|
+
if (!firstPrompt && j.type === 'event_msg' && j.payload?.type === 'user_message') {
|
|
399
|
+
const text = (j.payload.message || '').trim();
|
|
400
|
+
if (text) firstPrompt = text;
|
|
401
|
+
}
|
|
402
|
+
} catch { continue; }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
bySession.set(id, {
|
|
406
|
+
sessionId: id,
|
|
407
|
+
project: '-home-runner-workspace',
|
|
408
|
+
entries: [],
|
|
409
|
+
firstPrompt: firstPrompt || id.slice(0, 8) + '...',
|
|
410
|
+
lastTimestamp,
|
|
411
|
+
tool: 'codex',
|
|
412
|
+
});
|
|
413
|
+
} catch { continue; }
|
|
414
|
+
}
|
|
415
|
+
} catch { /* non-fatal */ }
|
|
416
|
+
}
|
|
417
|
+
|
|
312
418
|
// Read active terminal sessions
|
|
313
419
|
// Use the same root as replitBase (go up one level from .claude-persistent)
|
|
314
420
|
const replitRoot = join(replitBase, '..');
|
|
@@ -346,6 +452,7 @@ export function importReplitSessions(cwd = process.cwd()) {
|
|
|
346
452
|
isActive: activeSessionIds.has(id),
|
|
347
453
|
source: 'replit-tools',
|
|
348
454
|
age: timeAgo(sess.lastTimestamp),
|
|
455
|
+
tool: sess.tool || 'claude',
|
|
349
456
|
});
|
|
350
457
|
}
|
|
351
458
|
|