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.
@@ -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
- console.log(renderHeader(version, headerLines));
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
- console.log(` [${i + 1}] ${pin}${sess.age.padEnd(6)} ${sess.name}${active}${cat}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "7.1.11",
3
+ "version": "7.1.12",
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/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