dual-brain 7.1.11 → 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 +41 -2
- package/package.json +1 -1
- package/src/session.mjs +108 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -823,8 +823,32 @@ async function mainScreen(rl, ask) {
|
|
|
823
823
|
subLine('OpenAI', openaiPlan, auth.openai.found, openaiExpired, openaiDays, openaiSub),
|
|
824
824
|
];
|
|
825
825
|
|
|
826
|
+
console.log(`📦 DATA Tools - Dual Brain v${version}`);
|
|
826
827
|
console.log('');
|
|
827
|
-
|
|
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
|
+
}
|
|
828
852
|
|
|
829
853
|
// Auto-refresh expired subscriptions
|
|
830
854
|
if (claudeExpired || openaiExpired) {
|
|
@@ -860,11 +884,26 @@ async function mainScreen(rl, ask) {
|
|
|
860
884
|
const pin = sess.pinned ? '📌 ' : ' ';
|
|
861
885
|
const active = sess.isActive ? ' ●' : '';
|
|
862
886
|
const cat = sess.category ? ` [${sess.category}]` : '';
|
|
863
|
-
|
|
887
|
+
const tool = (sess.tool === 'codex') ? 'cdx' : 'cld';
|
|
888
|
+
console.log(` [${i + 1}] ${pin}${tool} ${sess.age.padEnd(8)} ${sess.name}${active}${cat}`);
|
|
864
889
|
});
|
|
865
890
|
console.log('');
|
|
866
891
|
}
|
|
867
892
|
|
|
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
|
+
|
|
868
907
|
console.log(' [c] Continue last session');
|
|
869
908
|
console.log(' [n] New session');
|
|
870
909
|
if (recentSessions.length > 0) {
|
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
|
|