dual-brain 0.2.19 → 0.2.20
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/install.mjs +158 -67
- package/package.json +1 -1
package/install.mjs
CHANGED
|
@@ -15,6 +15,8 @@ import { dirname, join, resolve } from 'path';
|
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
16
|
import { spawnSync } from 'child_process';
|
|
17
17
|
import { createHash } from 'crypto';
|
|
18
|
+
import { spinner, success as fxSuccess, warn as fxWarn, error as fxError, info as fxInfo, banner, celebrate, colors, sleep, nl, getMode } from './src/fx.mjs';
|
|
19
|
+
import { panel, signalLine, headerBar } from './src/tui.mjs';
|
|
18
20
|
|
|
19
21
|
// Skip hook installation during global npm install — hooks are installed
|
|
20
22
|
// when the user runs 'dual-brain install' in their project directory.
|
|
@@ -103,14 +105,14 @@ if (subcommand && !SUBCOMMANDS.includes(subcommand)) {
|
|
|
103
105
|
process.exit(1);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
// ─── Box Drawing
|
|
108
|
+
// ─── Box Drawing (legacy compat — prefer panel() from tui.mjs) ─────────────
|
|
107
109
|
|
|
108
110
|
const W = 54;
|
|
109
|
-
const
|
|
111
|
+
const pad_legacy = (s, len = W - 2) => {
|
|
110
112
|
s = String(s);
|
|
111
113
|
return s.length >= len ? s.slice(0, len) : s + ' '.repeat(len - s.length);
|
|
112
114
|
};
|
|
113
|
-
const ln = (s) => `║ ${
|
|
115
|
+
const ln = (s) => `║ ${pad_legacy(s)} ║`;
|
|
114
116
|
const br = (l, r) => l + '═'.repeat(W) + r;
|
|
115
117
|
const sep = () => '╠' + '═'.repeat(W) + '╣';
|
|
116
118
|
|
|
@@ -421,10 +423,8 @@ async function authGuidance(env) {
|
|
|
421
423
|
if (env.claude.authed && env.codex.authed) return env;
|
|
422
424
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return env;
|
|
423
425
|
|
|
424
|
-
|
|
425
|
-
console.log('
|
|
426
|
-
console.log(' │ 🔑 Auth Setup │');
|
|
427
|
-
console.log(' └────────────────────────────────────────────┘');
|
|
426
|
+
nl();
|
|
427
|
+
console.log(panel('Auth Setup', ['Checking provider authentication...'], { width: 50 }));
|
|
428
428
|
|
|
429
429
|
if (!env.claude.authed) {
|
|
430
430
|
console.log('');
|
|
@@ -654,27 +654,24 @@ function getAuthState() {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
function printAuthStatusBox(state) {
|
|
657
|
-
const
|
|
657
|
+
const cl = state.claude;
|
|
658
658
|
const x = state.codex;
|
|
659
|
-
const cIcon = c.authed ? '✅' : c.installed ? '⚠️' : '❌';
|
|
660
|
-
const xIcon = x.authed ? '✅' : x.installed ? '⚠️' : '❌';
|
|
661
659
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
console.log('');
|
|
660
|
+
const lines = [];
|
|
661
|
+
lines.push(signalLine(cl.authed ? 'success' : 'warning', `Claude ${cl.authed ? 'authenticated' : cl.installed ? 'not authenticated' : 'not installed'}`));
|
|
662
|
+
lines.push(` Method: ${cl.method}`);
|
|
663
|
+
lines.push(` Expiry: ${cl.expiryText}`);
|
|
664
|
+
lines.push(` Storage: ${cl.storageText}`);
|
|
665
|
+
lines.push('');
|
|
666
|
+
lines.push(signalLine(x.authed ? 'success' : 'warning', `Codex ${x.authed ? 'authenticated' : x.installed ? 'not authenticated' : 'not installed'}`));
|
|
667
|
+
lines.push(` Method: ${x.method}`);
|
|
668
|
+
lines.push(` Expiry: ${x.expiryText}`);
|
|
669
|
+
lines.push(` Storage: ${x.storageText}`);
|
|
670
|
+
if (x.lastRefresh) lines.push(` Refreshed:${x.lastRefreshText}`);
|
|
671
|
+
|
|
672
|
+
nl();
|
|
673
|
+
console.log(panel('Auth Status', lines, { width: 60 }));
|
|
674
|
+
nl();
|
|
678
675
|
}
|
|
679
676
|
|
|
680
677
|
function runCodexDeviceAuth(codexPath) {
|
|
@@ -981,36 +978,38 @@ function install(workspace, env, mode) {
|
|
|
981
978
|
|
|
982
979
|
// ─── Status Report ──────────────────────────────────────────────────────────
|
|
983
980
|
|
|
984
|
-
function printReport(env, mode, actions, isDryRun) {
|
|
985
|
-
const
|
|
981
|
+
function printReport(env, mode, actions, isDryRun, { skipBanner = false } = {}) {
|
|
982
|
+
const m = getMode();
|
|
983
|
+
nl();
|
|
986
984
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
lines.push(sep());
|
|
985
|
+
// Gradient banner (skip if already shown in animated detection)
|
|
986
|
+
if (!skipBanner) banner(`v${VERSION}`);
|
|
990
987
|
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
988
|
+
// Provider status
|
|
989
|
+
const cAuth = env.claude.authed ? 'authenticated' : env.claude.installed ? 'installed · not authed' : 'not installed';
|
|
990
|
+
const xAuth = env.codex.authed ? 'authenticated' : env.codex.installed ? 'installed · not authed' : 'not installed';
|
|
991
|
+
const cHealthy = env.claude.authed;
|
|
992
|
+
const xHealthy = env.codex.authed;
|
|
994
993
|
|
|
994
|
+
const statusLines = [];
|
|
995
|
+
statusLines.push(signalLine(cHealthy ? 'success' : 'warning', `Claude ${cAuth}`));
|
|
996
|
+
statusLines.push(signalLine(xHealthy ? 'success' : 'warning', `Codex ${xAuth}`));
|
|
995
997
|
if (env.isReplit) {
|
|
996
|
-
|
|
998
|
+
statusLines.push(signalLine('info', `Replit${env.hasReplitTools ? ' + replit-tools' : ''}`));
|
|
997
999
|
}
|
|
998
1000
|
|
|
999
1001
|
if (actions) {
|
|
1000
|
-
|
|
1001
|
-
for (const a of actions)
|
|
1002
|
-
|
|
1003
|
-
|
|
1002
|
+
statusLines.push('');
|
|
1003
|
+
for (const a of actions) statusLines.push(` ${a}`);
|
|
1004
|
+
statusLines.push('');
|
|
1005
|
+
statusLines.push(signalLine('success', 'Installed — launching session manager...'));
|
|
1004
1006
|
} else if (isDryRun) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
+
statusLines.push('');
|
|
1008
|
+
statusLines.push(signalLine('info', 'Dry run — no files written'));
|
|
1007
1009
|
}
|
|
1008
1010
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
console.log('');
|
|
1012
|
-
for (const l of lines) console.log(` ${l}`);
|
|
1013
|
-
console.log('');
|
|
1011
|
+
console.log(panel('dual-brain status', statusLines, { width: 64 }));
|
|
1012
|
+
nl();
|
|
1014
1013
|
}
|
|
1015
1014
|
|
|
1016
1015
|
// ─── Profile System ────────────────────────────────────────────────────────
|
|
@@ -1223,17 +1222,19 @@ function cmdMode() {
|
|
|
1223
1222
|
const current = loadProfile(workspace);
|
|
1224
1223
|
const PEMOJIS = { auto: '🤖', balanced: '⚖️ ', 'cost-saver': '🛡️', 'quality-first': '🚀' };
|
|
1225
1224
|
const UI_NAMES = { auto: 'Auto (default)', balanced: 'Balanced', 'cost-saver': 'Conservative', 'quality-first': 'Aggressive' };
|
|
1226
|
-
|
|
1227
|
-
console.log(
|
|
1228
|
-
|
|
1225
|
+
nl();
|
|
1226
|
+
console.log(` ${colors.bold}${colors.cyan}Routing modes:${colors.reset}`);
|
|
1227
|
+
nl();
|
|
1229
1228
|
for (const [name, p] of Object.entries(PROFILES)) {
|
|
1230
|
-
const
|
|
1229
|
+
const isActive = name === current.name;
|
|
1231
1230
|
const label = UI_NAMES[name] || name;
|
|
1232
|
-
|
|
1231
|
+
const activeMarker = isActive ? ` ${colors.green}● active${colors.reset}` : '';
|
|
1232
|
+
const style = isActive ? `${colors.cyan}${colors.bold}` : colors.dim;
|
|
1233
|
+
console.log(` ${PEMOJIS[name] || ' '} ${style}${label.padEnd(15)}${colors.reset} ${p.description}${activeMarker}`);
|
|
1233
1234
|
}
|
|
1234
|
-
|
|
1235
|
+
nl();
|
|
1235
1236
|
console.log(` Switch: ${cmd('npx dual-brain mode <name>')}`);
|
|
1236
|
-
|
|
1237
|
+
nl();
|
|
1237
1238
|
return;
|
|
1238
1239
|
}
|
|
1239
1240
|
|
|
@@ -1434,7 +1435,85 @@ async function main() {
|
|
|
1434
1435
|
if (subcommand === 'budget') { cmdBudget(); return; }
|
|
1435
1436
|
if (subcommand === 'explain') { cmdExplain(); return; }
|
|
1436
1437
|
|
|
1437
|
-
|
|
1438
|
+
const mode_fx = getMode();
|
|
1439
|
+
const animate = mode_fx === 'full' || mode_fx === 'subtle';
|
|
1440
|
+
|
|
1441
|
+
// Animated detection phase
|
|
1442
|
+
nl();
|
|
1443
|
+
banner(`v${VERSION}`);
|
|
1444
|
+
|
|
1445
|
+
let env;
|
|
1446
|
+
if (animate) {
|
|
1447
|
+
const sp1 = spinner('Detecting environment...').start();
|
|
1448
|
+
await sleep(300);
|
|
1449
|
+
env = detectEnvironment();
|
|
1450
|
+
|
|
1451
|
+
// Workspace detection
|
|
1452
|
+
if (env.isReplit) {
|
|
1453
|
+
sp1.succeed(`Replit workspace detected${env.hasReplitTools ? ' + replit-tools' : ''}`);
|
|
1454
|
+
} else {
|
|
1455
|
+
sp1.succeed('Local workspace detected');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Node version
|
|
1459
|
+
const sp2 = spinner('Checking Node.js...').start();
|
|
1460
|
+
await sleep(200);
|
|
1461
|
+
sp2.succeed(`Node ${process.version} found`);
|
|
1462
|
+
|
|
1463
|
+
// Git repo
|
|
1464
|
+
const sp3 = spinner('Checking git...').start();
|
|
1465
|
+
await sleep(200);
|
|
1466
|
+
const gitResult = run('git', ['rev-parse', '--show-toplevel']);
|
|
1467
|
+
const gitBranch = run('git', ['branch', '--show-current']);
|
|
1468
|
+
if (gitResult.status === 0) {
|
|
1469
|
+
const repoName = gitResult.stdout.trim().split('/').pop();
|
|
1470
|
+
const branch = gitBranch.stdout?.trim() || 'unknown';
|
|
1471
|
+
sp3.succeed(`Git repository: ${repoName} (${branch})`);
|
|
1472
|
+
} else {
|
|
1473
|
+
sp3.warn('Not a git repository');
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Claude CLI
|
|
1477
|
+
const sp4 = spinner('Checking Claude CLI...').start();
|
|
1478
|
+
await sleep(300);
|
|
1479
|
+
if (env.claude.installed) {
|
|
1480
|
+
const authLabel = env.claude.authed ? 'CLI OAuth' : 'not authenticated';
|
|
1481
|
+
sp4.succeed(`Claude CLI found · ${authLabel}`);
|
|
1482
|
+
} else {
|
|
1483
|
+
sp4.warn('Claude CLI not found');
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// Codex CLI
|
|
1487
|
+
const sp5 = spinner('Checking Codex CLI...').start();
|
|
1488
|
+
await sleep(300);
|
|
1489
|
+
if (env.codex.installed) {
|
|
1490
|
+
const authLabel = env.codex.authed ? 'authenticated' : 'not authenticated';
|
|
1491
|
+
sp5.succeed(`OpenAI Codex CLI found · ${authLabel}`);
|
|
1492
|
+
} else {
|
|
1493
|
+
sp5.warn('Codex CLI not found');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Sessions
|
|
1497
|
+
const sp6 = spinner('Checking sessions...').start();
|
|
1498
|
+
await sleep(200);
|
|
1499
|
+
const sessionsDir = resolve(process.cwd(), '.replit-tools', '.claude-persistent');
|
|
1500
|
+
if (existsSync(sessionsDir)) {
|
|
1501
|
+
sp6.succeed('Session persistence via replit-tools');
|
|
1502
|
+
} else {
|
|
1503
|
+
sp6.succeed('Standard session management');
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
nl();
|
|
1507
|
+
} else {
|
|
1508
|
+
// Non-animated fallback for CI/plain
|
|
1509
|
+
env = detectEnvironment();
|
|
1510
|
+
fxInfo(`Workspace: ${env.isReplit ? 'Replit' : 'local'}`);
|
|
1511
|
+
fxInfo(`Node ${process.version}`);
|
|
1512
|
+
fxInfo(`Claude: ${env.claude.installed ? (env.claude.authed ? 'authed' : 'installed') : 'missing'}`);
|
|
1513
|
+
fxInfo(`Codex: ${env.codex.installed ? (env.codex.authed ? 'authed' : 'installed') : 'missing'}`);
|
|
1514
|
+
nl();
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1438
1517
|
const startupUpdateInfo = (subcommand === 'update' || dryRun || jsonOut)
|
|
1439
1518
|
? null
|
|
1440
1519
|
: checkForUpdate(env.workspace);
|
|
@@ -1445,9 +1524,9 @@ async function main() {
|
|
|
1445
1524
|
process.stdout.isTTY &&
|
|
1446
1525
|
!process.env.CI
|
|
1447
1526
|
) {
|
|
1448
|
-
|
|
1527
|
+
nl();
|
|
1449
1528
|
const shouldUpdate = await promptForUpdate(startupUpdateInfo);
|
|
1450
|
-
|
|
1529
|
+
nl();
|
|
1451
1530
|
if (shouldUpdate) {
|
|
1452
1531
|
env = healClaudeAuth(env);
|
|
1453
1532
|
env = healCodexAuth(env);
|
|
@@ -1477,29 +1556,38 @@ async function main() {
|
|
|
1477
1556
|
if (jsonOut) {
|
|
1478
1557
|
console.log(JSON.stringify({ version: VERSION, env, mode }, null, 2));
|
|
1479
1558
|
} else {
|
|
1480
|
-
printReport(env, mode, null, true);
|
|
1559
|
+
printReport(env, mode, null, true, { skipBanner: true });
|
|
1481
1560
|
}
|
|
1482
1561
|
process.exit(0);
|
|
1483
1562
|
}
|
|
1484
1563
|
|
|
1485
1564
|
if (subcommand === 'update') {
|
|
1486
1565
|
const actions = performUpdate(env.workspace, env, mode);
|
|
1487
|
-
printReport(env, mode, actions);
|
|
1566
|
+
printReport(env, mode, actions, false, { skipBanner: true });
|
|
1488
1567
|
process.exit(0);
|
|
1489
1568
|
}
|
|
1490
1569
|
|
|
1491
1570
|
// Check for replit-tools on Replit
|
|
1492
1571
|
if (env.isReplit && !env.hasReplitTools) {
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1572
|
+
nl();
|
|
1573
|
+
fxWarn('replit-tools not found — recommended for Replit environments.');
|
|
1574
|
+
fxInfo('Dual-brain works best alongside replit-tools for persistent auth,');
|
|
1575
|
+
fxInfo('session management, and shell integration.');
|
|
1576
|
+
nl();
|
|
1577
|
+
fxInfo(`Install: ${cmd('npx -y data-tools')}`);
|
|
1578
|
+
nl();
|
|
1500
1579
|
}
|
|
1501
1580
|
|
|
1502
|
-
|
|
1581
|
+
// Install hooks and config
|
|
1582
|
+
let actions;
|
|
1583
|
+
if (animate) {
|
|
1584
|
+
const spInstall = spinner('Installing dual-brain hooks...').start();
|
|
1585
|
+
await sleep(400);
|
|
1586
|
+
actions = install(env.workspace, env, mode);
|
|
1587
|
+
spInstall.succeed(`Installed ${actions.length} components`);
|
|
1588
|
+
} else {
|
|
1589
|
+
actions = install(env.workspace, env, mode);
|
|
1590
|
+
}
|
|
1503
1591
|
|
|
1504
1592
|
// Write a standalone shell-hook.sh so users can source it from .bashrc.
|
|
1505
1593
|
// Non-interactive installs (npm postinstall) just print the hint; interactive
|
|
@@ -1530,7 +1618,10 @@ async function main() {
|
|
|
1530
1618
|
}
|
|
1531
1619
|
}
|
|
1532
1620
|
|
|
1533
|
-
|
|
1621
|
+
nl();
|
|
1622
|
+
await celebrate('Setup complete');
|
|
1623
|
+
nl();
|
|
1624
|
+
printReport(env, mode, actions, false, { skipBanner: true });
|
|
1534
1625
|
|
|
1535
1626
|
// After install, launch the session manager (interactive TTY only)
|
|
1536
1627
|
if (process.stdin.isTTY && process.stdout.isTTY && !process.env.CI) {
|
package/package.json
CHANGED