dual-brain 0.2.30 → 0.3.0
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/.dual-brain/docs/claude-code-extension-points.md +32 -0
- package/.dual-brain/docs/data-tools-capabilities.md +181 -0
- package/.dual-brain/docs/ecosystem-tools.md +91 -0
- package/.dual-brain/docs/panel-handoff.md +124 -0
- package/.dual-brain/docs/ruflo-analysis.md +48 -0
- package/bin/dual-brain.mjs +56 -56
- package/dist/mcp-server/index.d.ts +27 -0
- package/dist/mcp-server/index.js +359 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/agent-protocol.d.ts +163 -0
- package/dist/src/agent-protocol.js +368 -0
- package/dist/src/agent-protocol.js.map +1 -0
- package/dist/src/agents/registry.d.ts +52 -0
- package/dist/src/agents/registry.js +393 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/awareness.d.ts +93 -0
- package/dist/src/awareness.js +406 -0
- package/dist/src/awareness.js.map +1 -0
- package/dist/src/brief.d.ts +48 -0
- package/dist/src/brief.js +179 -0
- package/dist/src/brief.js.map +1 -0
- package/dist/src/calibration.d.ts +32 -0
- package/dist/src/calibration.js +133 -0
- package/dist/src/calibration.js.map +1 -0
- package/dist/src/checkpoint.d.ts +33 -0
- package/dist/src/checkpoint.js +99 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/ci-triage.d.ts +33 -0
- package/dist/src/ci-triage.js +193 -0
- package/dist/src/ci-triage.js.map +1 -0
- package/dist/src/cognitive-loop.d.ts +56 -0
- package/dist/src/cognitive-loop.js +495 -0
- package/dist/src/cognitive-loop.js.map +1 -0
- package/dist/src/collaboration.d.ts +147 -0
- package/dist/src/collaboration.js +438 -0
- package/dist/src/collaboration.js.map +1 -0
- package/dist/src/context-intel.d.ts +47 -0
- package/dist/src/context-intel.js +156 -0
- package/dist/src/context-intel.js.map +1 -0
- package/dist/src/context.d.ts +53 -0
- package/dist/src/context.js +332 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/continuity.d.ts +89 -0
- package/dist/src/continuity.js +230 -0
- package/dist/src/continuity.js.map +1 -0
- package/dist/src/cost-tracker.d.ts +47 -0
- package/dist/src/cost-tracker.js +170 -0
- package/dist/src/cost-tracker.js.map +1 -0
- package/dist/src/debrief.d.ts +53 -0
- package/dist/src/debrief.js +222 -0
- package/dist/src/debrief.js.map +1 -0
- package/dist/src/decide.d.ts +96 -0
- package/dist/src/decide.js +744 -0
- package/dist/src/decide.js.map +1 -0
- package/dist/src/decompose.d.ts +39 -0
- package/dist/src/decompose.js +218 -0
- package/dist/src/decompose.js.map +1 -0
- package/dist/src/detect.d.ts +91 -0
- package/dist/src/detect.js +544 -0
- package/dist/src/detect.js.map +1 -0
- package/dist/src/dispatch.d.ts +154 -0
- package/dist/src/dispatch.js +1306 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/doctor.d.ts +421 -0
- package/dist/src/doctor.js +1689 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/engine.d.ts +70 -0
- package/dist/src/engine.js +155 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/envelope.d.ts +36 -0
- package/dist/src/envelope.js +80 -0
- package/dist/src/envelope.js.map +1 -0
- package/dist/src/failure-memory.d.ts +55 -0
- package/dist/src/failure-memory.js +175 -0
- package/dist/src/failure-memory.js.map +1 -0
- package/dist/src/fx.d.ts +87 -0
- package/dist/src/fx.js +272 -0
- package/dist/src/fx.js.map +1 -0
- package/dist/src/governance.d.ts +93 -0
- package/dist/src/governance.js +261 -0
- package/dist/src/governance.js.map +1 -0
- package/dist/src/handoff.d.ts +11 -0
- package/dist/src/handoff.js +90 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/head-protocol.d.ts +76 -0
- package/dist/src/head-protocol.js +109 -0
- package/dist/src/head-protocol.js.map +1 -0
- package/dist/src/head.d.ts +222 -0
- package/dist/src/head.js +765 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/health.d.ts +132 -0
- package/dist/src/health.js +435 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/inbox.d.ts +70 -0
- package/dist/src/inbox.js +218 -0
- package/dist/src/inbox.js.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/install-hooks.d.ts +13 -0
- package/dist/src/install-hooks.js +88 -0
- package/dist/src/install-hooks.js.map +1 -0
- package/dist/src/integrity.d.ts +59 -0
- package/dist/src/integrity.js +206 -0
- package/dist/src/integrity.js.map +1 -0
- package/dist/src/intelligence.d.ts +104 -0
- package/dist/src/intelligence.js +391 -0
- package/dist/src/intelligence.js.map +1 -0
- package/dist/src/ledger.d.ts +54 -0
- package/dist/src/ledger.js +179 -0
- package/dist/src/ledger.js.map +1 -0
- package/dist/src/living-docs.d.ts +14 -0
- package/dist/src/living-docs.js +197 -0
- package/dist/src/living-docs.js.map +1 -0
- package/dist/src/memory-tiers.d.ts +37 -0
- package/dist/src/memory-tiers.js +160 -0
- package/dist/src/memory-tiers.js.map +1 -0
- package/dist/src/model-profiles.d.ts +65 -0
- package/dist/src/model-profiles.js +568 -0
- package/dist/src/model-profiles.js.map +1 -0
- package/dist/src/models.d.ts +58 -0
- package/dist/src/models.js +327 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/narrative.d.ts +54 -0
- package/dist/src/narrative.js +163 -0
- package/dist/src/narrative.js.map +1 -0
- package/dist/src/nextstep.d.ts +16 -0
- package/dist/src/nextstep.js +103 -0
- package/dist/src/nextstep.js.map +1 -0
- package/dist/src/observer.d.ts +18 -0
- package/dist/src/observer.js +251 -0
- package/dist/src/observer.js.map +1 -0
- package/dist/src/outcome.d.ts +110 -0
- package/dist/src/outcome.js +377 -0
- package/dist/src/outcome.js.map +1 -0
- package/dist/src/pipeline.d.ts +167 -0
- package/dist/src/pipeline.js +1503 -0
- package/dist/src/pipeline.js.map +1 -0
- package/dist/src/playbook.d.ts +59 -0
- package/dist/src/playbook.js +238 -0
- package/dist/src/playbook.js.map +1 -0
- package/dist/src/pr-agent.d.ts +97 -0
- package/dist/src/pr-agent.js +195 -0
- package/dist/src/pr-agent.js.map +1 -0
- package/dist/src/predictive.d.ts +57 -0
- package/dist/src/predictive.js +230 -0
- package/dist/src/predictive.js.map +1 -0
- package/dist/src/profile.d.ts +294 -0
- package/dist/src/profile.js +1347 -0
- package/dist/src/profile.js.map +1 -0
- package/dist/src/prompt-audit.d.ts +22 -0
- package/dist/src/prompt-audit.js +194 -0
- package/dist/src/prompt-audit.js.map +1 -0
- package/dist/src/prompt-intel.d.ts +12 -0
- package/dist/src/prompt-intel.js +321 -0
- package/dist/src/prompt-intel.js.map +1 -0
- package/dist/src/provider-context.d.ts +121 -0
- package/dist/src/provider-context.js +222 -0
- package/dist/src/provider-context.js.map +1 -0
- package/dist/src/provider-manager.d.ts +92 -0
- package/dist/src/provider-manager.js +428 -0
- package/dist/src/provider-manager.js.map +1 -0
- package/dist/src/receipt.d.ts +87 -0
- package/dist/src/receipt.js +326 -0
- package/dist/src/receipt.js.map +1 -0
- package/dist/src/recommendations.d.ts +13 -0
- package/dist/src/recommendations.js +291 -0
- package/dist/src/recommendations.js.map +1 -0
- package/dist/src/redact.d.ts +15 -0
- package/dist/src/redact.js +129 -0
- package/dist/src/redact.js.map +1 -0
- package/dist/src/replit.d.ts +397 -0
- package/dist/src/replit.js +1160 -0
- package/dist/src/replit.js.map +1 -0
- package/dist/src/repo.d.ts +149 -0
- package/dist/src/repo.js +416 -0
- package/dist/src/repo.js.map +1 -0
- package/dist/src/revert.d.ts +30 -0
- package/dist/src/revert.js +166 -0
- package/dist/src/revert.js.map +1 -0
- package/dist/src/room.d.ts +102 -0
- package/dist/src/room.js +212 -0
- package/dist/src/room.js.map +1 -0
- package/dist/src/routing-advisor.d.ts +57 -0
- package/dist/src/routing-advisor.js +221 -0
- package/dist/src/routing-advisor.js.map +1 -0
- package/dist/src/self-correct.d.ts +40 -0
- package/dist/src/self-correct.js +137 -0
- package/dist/src/self-correct.js.map +1 -0
- package/dist/src/session-lock.d.ts +35 -0
- package/dist/src/session-lock.js +134 -0
- package/dist/src/session-lock.js.map +1 -0
- package/dist/src/session.d.ts +267 -0
- package/dist/src/session.js +1660 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/settings-tui.d.ts +5 -0
- package/dist/src/settings-tui.js +422 -0
- package/dist/src/settings-tui.js.map +1 -0
- package/dist/src/setup-flow.d.ts +63 -0
- package/dist/src/setup-flow.js +233 -0
- package/dist/src/setup-flow.js.map +1 -0
- package/dist/src/signal.d.ts +19 -0
- package/dist/src/signal.js +122 -0
- package/dist/src/signal.js.map +1 -0
- package/dist/src/simmer.d.ts +85 -0
- package/dist/src/simmer.js +224 -0
- package/dist/src/simmer.js.map +1 -0
- package/dist/src/state-export.d.ts +129 -0
- package/dist/src/state-export.js +233 -0
- package/dist/src/state-export.js.map +1 -0
- package/dist/src/strategy.d.ts +54 -0
- package/dist/src/strategy.js +95 -0
- package/dist/src/strategy.js.map +1 -0
- package/dist/src/subscription.d.ts +40 -0
- package/dist/src/subscription.js +189 -0
- package/dist/src/subscription.js.map +1 -0
- package/dist/src/templates.d.ts +208 -0
- package/dist/src/templates.js +238 -0
- package/dist/src/templates.js.map +1 -0
- package/dist/src/test.d.ts +9 -0
- package/dist/src/test.js +1173 -0
- package/dist/src/test.js.map +1 -0
- package/dist/src/think-engine.d.ts +67 -0
- package/dist/src/think-engine.js +412 -0
- package/dist/src/think-engine.js.map +1 -0
- package/dist/src/tui.d.ts +71 -0
- package/dist/src/tui.js +242 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/types.d.ts +177 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/update-check.d.ts +7 -0
- package/dist/src/update-check.js +36 -0
- package/dist/src/update-check.js.map +1 -0
- package/dist/src/wave-planner.d.ts +30 -0
- package/dist/src/wave-planner.js +281 -0
- package/dist/src/wave-planner.js.map +1 -0
- package/hooks/head-guard.sh +41 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/vibe-router.mjs +387 -0
- package/package.json +29 -153
- package/src/agents/registry.mjs +0 -405
- package/src/awareness.mjs +0 -425
- package/src/brief.mjs +0 -266
- package/src/calibration.mjs +0 -148
- package/src/checkpoint.mjs +0 -109
- package/src/ci-triage.mjs +0 -191
- package/src/cognitive-loop.mjs +0 -562
- package/src/collaboration.mjs +0 -545
- package/src/context-intel.mjs +0 -158
- package/src/context.mjs +0 -389
- package/src/continuity.mjs +0 -298
- package/src/cost-tracker.mjs +0 -184
- package/src/debrief.mjs +0 -228
- package/src/decide.mjs +0 -1099
- package/src/decompose.mjs +0 -331
- package/src/detect.mjs +0 -702
- package/src/dispatch.mjs +0 -1447
- package/src/doctor.mjs +0 -1607
- package/src/envelope.mjs +0 -139
- package/src/failure-memory.mjs +0 -178
- package/src/fx.mjs +0 -276
- package/src/governance.mjs +0 -279
- package/src/handoff.mjs +0 -87
- package/src/head-protocol.mjs +0 -128
- package/src/head.mjs +0 -952
- package/src/health.mjs +0 -528
- package/src/inbox.mjs +0 -195
- package/src/index.mjs +0 -44
- package/src/install-hooks.mjs +0 -100
- package/src/integrity.mjs +0 -245
- package/src/intelligence.mjs +0 -447
- package/src/ledger.mjs +0 -196
- package/src/living-docs.mjs +0 -210
- package/src/memory-tiers.mjs +0 -193
- package/src/models.mjs +0 -363
- package/src/narrative.mjs +0 -169
- package/src/nextstep.mjs +0 -100
- package/src/observer.mjs +0 -241
- package/src/outcome.mjs +0 -400
- package/src/pipeline.mjs +0 -1711
- package/src/playbook.mjs +0 -257
- package/src/pr-agent.mjs +0 -214
- package/src/predictive.mjs +0 -250
- package/src/profile.mjs +0 -1411
- package/src/prompt-audit.mjs +0 -231
- package/src/prompt-intel.mjs +0 -325
- package/src/provider-context.mjs +0 -257
- package/src/receipt.mjs +0 -344
- package/src/recommendations.mjs +0 -296
- package/src/redact.mjs +0 -192
- package/src/replit.mjs +0 -1210
- package/src/repo.mjs +0 -445
- package/src/revert.mjs +0 -149
- package/src/routing-advisor.mjs +0 -204
- package/src/self-correct.mjs +0 -147
- package/src/session-lock.mjs +0 -160
- package/src/session.mjs +0 -1655
- package/src/settings-tui.mjs +0 -373
- package/src/setup-flow.mjs +0 -223
- package/src/signal.mjs +0 -115
- package/src/simmer.mjs +0 -241
- package/src/strategy.mjs +0 -235
- package/src/subscription.mjs +0 -212
- package/src/templates.mjs +0 -260
- package/src/think-engine.mjs +0 -428
- package/src/tui.mjs +0 -276
- package/src/update-check.mjs +0 -35
- package/src/wave-planner.mjs +0 -294
package/src/settings-tui.mjs
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
// settings-tui.mjs — Interactive settings menu for `dual-brain settings`
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { createInterface } from 'node:readline';
|
|
5
|
-
|
|
6
|
-
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
7
|
-
const c = {
|
|
8
|
-
bold: s => `\x1b[1m${s}\x1b[0m`,
|
|
9
|
-
dim: s => `\x1b[2m${s}\x1b[0m`,
|
|
10
|
-
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
11
|
-
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
12
|
-
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
13
|
-
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// ─── readline helper ──────────────────────────────────────────────────────────
|
|
17
|
-
async function prompt(rl, question) {
|
|
18
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ─── Config helpers ───────────────────────────────────────────────────────────
|
|
22
|
-
function loadCurrentConfig(cwd) {
|
|
23
|
-
try {
|
|
24
|
-
const p = join(cwd, '.dualbrain', 'config.json');
|
|
25
|
-
return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : {};
|
|
26
|
-
} catch { return {}; }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function saveConfig(cfg, cwd) {
|
|
30
|
-
const dir = join(cwd, '.dualbrain');
|
|
31
|
-
mkdirSync(dir, { recursive: true });
|
|
32
|
-
writeFileSync(join(dir, 'config.json'), JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ─── Dial position map ────────────────────────────────────────────────────────
|
|
36
|
-
const DIAL_POSITIONS = {
|
|
37
|
-
1: { label: 'Frugal', workStyle: 'frugal', models: { search: 'haiku', execute: 'haiku', think: 'sonnet', review: 'sonnet' }, thinkEnabled: false, budget: 3 },
|
|
38
|
-
2: { label: 'Save Usage', workStyle: 'conservative', models: { search: 'haiku', execute: 'sonnet', think: 'sonnet', review: 'sonnet' }, thinkEnabled: 'auto', budget: null },
|
|
39
|
-
3: { label: 'Balanced', workStyle: 'balanced', models: { search: 'haiku', execute: 'sonnet', think: 'opus', review: 'sonnet' }, thinkEnabled: true, budget: null },
|
|
40
|
-
4: { label: 'Quality', workStyle: 'quality', models: { search: 'sonnet',execute: 'sonnet', think: 'opus', review: 'opus' }, thinkEnabled: true, budget: null },
|
|
41
|
-
5: { label: 'Maximum', workStyle: 'aggressive', models: { search: 'sonnet',execute: 'opus', think: 'opus', review: 'opus' }, thinkEnabled: true, budget: null },
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function saveDialPosition(position, cwd) {
|
|
45
|
-
const dial = DIAL_POSITIONS[position];
|
|
46
|
-
if (!dial) return;
|
|
47
|
-
const cfg = loadCurrentConfig(cwd);
|
|
48
|
-
cfg.workStyle = dial.workStyle;
|
|
49
|
-
cfg.models = { ...(cfg.models ?? {}), ...dial.models };
|
|
50
|
-
cfg.routing = cfg.routing ?? {};
|
|
51
|
-
cfg.routing.thinkEnabled = dial.thinkEnabled === 'auto' ? true : dial.thinkEnabled;
|
|
52
|
-
if (dial.budget !== null) {
|
|
53
|
-
cfg.budget = cfg.budget ?? {};
|
|
54
|
-
cfg.budget.sessionLimitUsd = dial.budget;
|
|
55
|
-
} else {
|
|
56
|
-
if (cfg.budget) delete cfg.budget.sessionLimitUsd;
|
|
57
|
-
}
|
|
58
|
-
cfg.dialPosition = position;
|
|
59
|
-
saveConfig(cfg, cwd);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ─── Header helpers ───────────────────────────────────────────────────────────
|
|
63
|
-
function inferDialLabel(cfg) {
|
|
64
|
-
const pos = cfg.dialPosition;
|
|
65
|
-
if (pos && DIAL_POSITIONS[pos]) return DIAL_POSITIONS[pos].label;
|
|
66
|
-
const ws = cfg.workStyle ?? '';
|
|
67
|
-
const map = { frugal: 'Frugal', conservative: 'Save Usage', balanced: 'Balanced', quality: 'Quality', aggressive: 'Maximum' };
|
|
68
|
-
return map[ws] ?? 'Balanced';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function inferSubLabel(cwd) {
|
|
72
|
-
try {
|
|
73
|
-
const p = join(cwd, '.dualbrain', 'subscription.json');
|
|
74
|
-
if (!existsSync(p)) return 'unknown';
|
|
75
|
-
const { subscription } = JSON.parse(readFileSync(p, 'utf8'));
|
|
76
|
-
const labels = {
|
|
77
|
-
'claude-pro': 'Claude Pro', 'claude-max-5x': 'Claude Max 5x',
|
|
78
|
-
'claude-max-20x': 'Claude Max 20x', 'chatgpt-plus': 'ChatGPT Plus',
|
|
79
|
-
'chatgpt-pro': 'ChatGPT Pro', 'dual-pro': 'Both Pro', 'dual-max': 'Both Max',
|
|
80
|
-
};
|
|
81
|
-
return labels[subscription] ?? subscription;
|
|
82
|
-
} catch { return 'unknown'; }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ─── Subscreens ───────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
export async function dialScreen(rl, cwd) {
|
|
88
|
-
const cfg = loadCurrentConfig(cwd);
|
|
89
|
-
const cur = cfg.dialPosition ?? 3;
|
|
90
|
-
console.log('');
|
|
91
|
-
console.log(c.bold(' Routing Dial'));
|
|
92
|
-
console.log('');
|
|
93
|
-
console.log(` Current: ${c.cyan(`[${cur}] ${DIAL_POSITIONS[cur]?.label ?? '?'}`)}`);
|
|
94
|
-
console.log('');
|
|
95
|
-
console.log(' 1) Frugal — minimize token usage');
|
|
96
|
-
console.log(' 2) Save Usage — prefer cheaper models');
|
|
97
|
-
console.log(' 3) Balanced — smart defaults');
|
|
98
|
-
console.log(' 4) Quality — best available for each task');
|
|
99
|
-
console.log(' 5) Maximum — always use most capable');
|
|
100
|
-
console.log('');
|
|
101
|
-
const ans = (await prompt(rl, ` Enter number (1-5) or [esc] to cancel: `)).trim();
|
|
102
|
-
if (ans === '\x1b' || ans === '' || ans === 'esc') return;
|
|
103
|
-
const n = parseInt(ans, 10);
|
|
104
|
-
if (n >= 1 && n <= 5) {
|
|
105
|
-
saveDialPosition(n, cwd);
|
|
106
|
-
console.log(c.green(` Dial set to [${n}] ${DIAL_POSITIONS[n].label}`));
|
|
107
|
-
} else {
|
|
108
|
-
console.log(c.red(' Invalid choice.'));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export async function routingScreen(rl, cwd) {
|
|
113
|
-
const cfg = loadCurrentConfig(cwd);
|
|
114
|
-
const models = cfg.models ?? {};
|
|
115
|
-
console.log('');
|
|
116
|
-
console.log(c.bold(' Tier Assignments'));
|
|
117
|
-
console.log('');
|
|
118
|
-
for (const [tier, model] of Object.entries(models)) {
|
|
119
|
-
console.log(` ${tier.padEnd(8)}: ${c.cyan(model)}`);
|
|
120
|
-
}
|
|
121
|
-
console.log('');
|
|
122
|
-
console.log(c.bold(' Learned Preferences') + c.dim(' (from routing advisor)'));
|
|
123
|
-
console.log('');
|
|
124
|
-
let stats = { topPerformers: [], totalObservations: 0 };
|
|
125
|
-
try {
|
|
126
|
-
const { getRoutingStats } = await import('./routing-advisor.mjs');
|
|
127
|
-
stats = getRoutingStats(cwd);
|
|
128
|
-
} catch {}
|
|
129
|
-
if (stats.topPerformers.length === 0) {
|
|
130
|
-
console.log(c.dim(' No observations yet.'));
|
|
131
|
-
} else {
|
|
132
|
-
for (const p of stats.topPerformers.slice(0, 5)) {
|
|
133
|
-
console.log(` ${p.cell.padEnd(22)} → ${c.cyan(p.model)} (EMA ${p.ema.toFixed(2)}, n=${p.observations})`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
console.log('');
|
|
137
|
-
const ans = (await prompt(rl, ' [o] Override tier [r] Reset learned data [esc] back: ')).trim().toLowerCase();
|
|
138
|
-
if (ans === 'r') {
|
|
139
|
-
try {
|
|
140
|
-
const { resetAdvisor } = await import('./routing-advisor.mjs');
|
|
141
|
-
resetAdvisor(cwd);
|
|
142
|
-
console.log(c.green(' Routing advisor state cleared.'));
|
|
143
|
-
} catch { console.log(c.red(' Failed to reset.')); }
|
|
144
|
-
} else if (ans === 'o') {
|
|
145
|
-
const tier = (await prompt(rl, ' Tier to override (search/execute/think/review): ')).trim();
|
|
146
|
-
const model = (await prompt(rl, ' Model (haiku/sonnet/opus): ')).trim();
|
|
147
|
-
if (tier && model) {
|
|
148
|
-
const cfg2 = loadCurrentConfig(cwd);
|
|
149
|
-
cfg2.models = cfg2.models ?? {};
|
|
150
|
-
cfg2.models[tier] = model;
|
|
151
|
-
saveConfig(cfg2, cwd);
|
|
152
|
-
console.log(c.green(` ${tier} → ${model} saved.`));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export async function thinkScreen(rl, cwd) {
|
|
158
|
-
let metrics = { hits: 0, misses: 0, totalTokens: 0 };
|
|
159
|
-
try {
|
|
160
|
-
const p = join(cwd, '.dualbrain', 'think-metrics.json');
|
|
161
|
-
if (existsSync(p)) metrics = JSON.parse(readFileSync(p, 'utf8'));
|
|
162
|
-
} catch {}
|
|
163
|
-
const cfg = loadCurrentConfig(cwd);
|
|
164
|
-
const enabled = cfg.routing?.thinkEnabled !== false;
|
|
165
|
-
const total = metrics.hits + metrics.misses;
|
|
166
|
-
const hitRate = total > 0 ? Math.round((metrics.hits / total) * 100) : 0;
|
|
167
|
-
console.log('');
|
|
168
|
-
console.log(c.bold(' Think Pre-flight'));
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log(` Status: ${enabled ? c.green('enabled') : c.red('disabled')}`);
|
|
171
|
-
console.log(` Hit rate: ${hitRate}% (${metrics.hits} hits / ${metrics.misses} misses)`);
|
|
172
|
-
console.log(` Tokens: ~${((metrics.totalTokens ?? 0) / 1000).toFixed(0)}K`);
|
|
173
|
-
console.log(` Auto-disable threshold: 30%`);
|
|
174
|
-
console.log('');
|
|
175
|
-
const ans = (await prompt(rl, ' [t] Toggle [r] Reset metrics [esc] back: ')).trim().toLowerCase();
|
|
176
|
-
if (ans === 't') {
|
|
177
|
-
const cfg2 = loadCurrentConfig(cwd);
|
|
178
|
-
cfg2.routing = cfg2.routing ?? {};
|
|
179
|
-
cfg2.routing.thinkEnabled = !enabled;
|
|
180
|
-
saveConfig(cfg2, cwd);
|
|
181
|
-
console.log(c.green(` Think ${!enabled ? 'enabled' : 'disabled'}.`));
|
|
182
|
-
} else if (ans === 'r') {
|
|
183
|
-
try {
|
|
184
|
-
const p = join(cwd, '.dualbrain', 'think-metrics.json');
|
|
185
|
-
writeFileSync(p, JSON.stringify({ hits: 0, misses: 0, totalTokens: 0 }, null, 2) + '\n');
|
|
186
|
-
console.log(c.green(' Think metrics reset.'));
|
|
187
|
-
} catch { console.log(c.red(' Failed to reset.')); }
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function budgetScreen(rl, cwd) {
|
|
192
|
-
let budget = { spent: 0, remaining: 10, limit: 10, warning: false };
|
|
193
|
-
try {
|
|
194
|
-
const { loadGovernanceState, checkBudget } = await import('./governance.mjs');
|
|
195
|
-
const cfg = loadCurrentConfig(cwd);
|
|
196
|
-
budget = checkBudget(cwd, cfg);
|
|
197
|
-
} catch {}
|
|
198
|
-
const pct = budget.limit > 0 ? Math.round((budget.spent / budget.limit) * 100) : 0;
|
|
199
|
-
const cfg = loadCurrentConfig(cwd);
|
|
200
|
-
const warnAt = cfg.budget?.warnAtPercent ?? 80;
|
|
201
|
-
console.log('');
|
|
202
|
-
console.log(c.bold(' Budget'));
|
|
203
|
-
console.log('');
|
|
204
|
-
console.log(` Session limit: $${budget.limit.toFixed(2)} (estimated)`);
|
|
205
|
-
console.log(` Current session: $${budget.spent.toFixed(2)} spent (${pct}%)`);
|
|
206
|
-
console.log(` Warning at: ${warnAt}%`);
|
|
207
|
-
console.log('');
|
|
208
|
-
const ans = (await prompt(rl, ' [l] Set limit [w] Set warning % [esc] back: ')).trim().toLowerCase();
|
|
209
|
-
if (ans === 'l') {
|
|
210
|
-
const val = (await prompt(rl, ' New session limit ($): ')).trim();
|
|
211
|
-
const n = parseFloat(val);
|
|
212
|
-
if (!isNaN(n) && n >= 0) {
|
|
213
|
-
const cfg2 = loadCurrentConfig(cwd);
|
|
214
|
-
cfg2.budget = cfg2.budget ?? {};
|
|
215
|
-
cfg2.budget.sessionLimitUsd = n;
|
|
216
|
-
saveConfig(cfg2, cwd);
|
|
217
|
-
console.log(c.green(` Session limit set to $${n}.`));
|
|
218
|
-
} else { console.log(c.red(' Invalid value.')); }
|
|
219
|
-
} else if (ans === 'w') {
|
|
220
|
-
const val = (await prompt(rl, ' Warn at percent (0-100): ')).trim();
|
|
221
|
-
const n = parseInt(val, 10);
|
|
222
|
-
if (!isNaN(n) && n >= 0 && n <= 100) {
|
|
223
|
-
const cfg2 = loadCurrentConfig(cwd);
|
|
224
|
-
cfg2.budget = cfg2.budget ?? {};
|
|
225
|
-
cfg2.budget.warnAtPercent = n;
|
|
226
|
-
saveConfig(cfg2, cwd);
|
|
227
|
-
console.log(c.green(` Warning threshold set to ${n}%.`));
|
|
228
|
-
} else { console.log(c.red(' Invalid value.')); }
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function subscriptionScreen(rl, cwd) {
|
|
233
|
-
let curSub = 'unknown';
|
|
234
|
-
try {
|
|
235
|
-
const p = join(cwd, '.dualbrain', 'subscription.json');
|
|
236
|
-
if (existsSync(p)) curSub = JSON.parse(readFileSync(p, 'utf8')).subscription ?? 'unknown';
|
|
237
|
-
} catch {}
|
|
238
|
-
const subs = [
|
|
239
|
-
['claude-pro', 'Claude Pro ($20/mo)'],
|
|
240
|
-
['claude-max-5x', 'Claude Max 5x ($100/mo)'],
|
|
241
|
-
['claude-max-20x', 'Claude Max 20x ($200/mo)'],
|
|
242
|
-
['chatgpt-plus', 'ChatGPT Plus ($20/mo)'],
|
|
243
|
-
['chatgpt-pro', 'ChatGPT Pro ($200/mo)'],
|
|
244
|
-
['dual-pro', 'Both Pro tiers'],
|
|
245
|
-
['dual-max', 'Both Max tiers'],
|
|
246
|
-
];
|
|
247
|
-
console.log('');
|
|
248
|
-
console.log(c.bold(' Subscription'));
|
|
249
|
-
console.log('');
|
|
250
|
-
console.log(` Current: ${c.cyan(curSub)}`);
|
|
251
|
-
console.log('');
|
|
252
|
-
subs.forEach(([key, label], i) => console.log(` ${i + 1}) ${label}`));
|
|
253
|
-
console.log('');
|
|
254
|
-
const ans = (await prompt(rl, ' Enter number or [esc] to cancel: ')).trim();
|
|
255
|
-
if (ans === '' || ans === 'esc' || ans === '\x1b') return;
|
|
256
|
-
const n = parseInt(ans, 10);
|
|
257
|
-
if (n >= 1 && n <= subs.length) {
|
|
258
|
-
const [subType, label] = subs[n - 1];
|
|
259
|
-
try {
|
|
260
|
-
const { saveUserSubscription } = await import('./subscription.mjs');
|
|
261
|
-
saveUserSubscription(subType, cwd);
|
|
262
|
-
console.log(c.green(` Subscription set to: ${label}`));
|
|
263
|
-
} catch { console.log(c.red(' Failed to save subscription.')); }
|
|
264
|
-
} else { console.log(c.red(' Invalid choice.')); }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function resetScreen(rl, cwd) {
|
|
268
|
-
let obs = 0;
|
|
269
|
-
try {
|
|
270
|
-
const { getRoutingStats } = await import('./routing-advisor.mjs');
|
|
271
|
-
obs = getRoutingStats(cwd).totalObservations;
|
|
272
|
-
} catch {}
|
|
273
|
-
console.log('');
|
|
274
|
-
console.log(c.bold(c.red(' Reset Learned Data')));
|
|
275
|
-
console.log('');
|
|
276
|
-
console.log(' This will clear:');
|
|
277
|
-
console.log(` - Routing advisor state (${obs} observations)`);
|
|
278
|
-
console.log(' - Think metrics');
|
|
279
|
-
console.log(' - Outcome history');
|
|
280
|
-
console.log('');
|
|
281
|
-
const ans = (await prompt(rl, ' Are you sure? (y/N): ')).trim().toLowerCase();
|
|
282
|
-
if (ans !== 'y') { console.log(c.dim(' Cancelled.')); return; }
|
|
283
|
-
let cleared = 0;
|
|
284
|
-
const targets = ['routing-state.json', 'routing-weights.json', 'think-metrics.json', 'outcomes.json'];
|
|
285
|
-
for (const f of targets) {
|
|
286
|
-
try {
|
|
287
|
-
const p = join(cwd, '.dualbrain', f);
|
|
288
|
-
if (existsSync(p)) { writeFileSync(p, '{}\n'); cleared++; }
|
|
289
|
-
} catch {}
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
const { resetAdvisor } = await import('./routing-advisor.mjs');
|
|
293
|
-
resetAdvisor(cwd);
|
|
294
|
-
} catch {}
|
|
295
|
-
console.log(c.green(` Cleared. (${cleared} files reset)`));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ─── Main menu ────────────────────────────────────────────────────────────────
|
|
299
|
-
export async function runSettings(cwd) {
|
|
300
|
-
cwd = cwd ?? process.cwd();
|
|
301
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
302
|
-
|
|
303
|
-
const box = (lines) => {
|
|
304
|
-
const W = 65;
|
|
305
|
-
const hr = '─'.repeat(W - 2);
|
|
306
|
-
console.log(`╭${hr}╮`);
|
|
307
|
-
for (const l of lines) {
|
|
308
|
-
const visible = l.replace(/\x1b\[[0-9;]*m/g, '');
|
|
309
|
-
const pad = W - 2 - visible.length;
|
|
310
|
-
console.log(`│ ${l}${' '.repeat(Math.max(0, pad - 1))}│`);
|
|
311
|
-
}
|
|
312
|
-
console.log(`╰${hr}╯`);
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const showMenu = () => {
|
|
316
|
-
const cfg = loadCurrentConfig(cwd);
|
|
317
|
-
const profile = inferDialLabel(cfg);
|
|
318
|
-
const sub = inferSubLabel(cwd);
|
|
319
|
-
let obs = 0;
|
|
320
|
-
try {
|
|
321
|
-
const p = join(cwd, '.dualbrain', 'routing-state.json');
|
|
322
|
-
if (existsSync(p)) {
|
|
323
|
-
const state = JSON.parse(readFileSync(p, 'utf8'));
|
|
324
|
-
for (const models of Object.values(state)) {
|
|
325
|
-
for (const e of Object.values(models)) obs += e.observations ?? 0;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} catch {}
|
|
329
|
-
const learning = cfg.routing?.learningEnabled !== false
|
|
330
|
-
? c.green(`active (${obs} observations)`)
|
|
331
|
-
: c.dim('disabled');
|
|
332
|
-
|
|
333
|
-
console.log('');
|
|
334
|
-
box([
|
|
335
|
-
c.bold(' dual-brain settings'),
|
|
336
|
-
'',
|
|
337
|
-
` Profile: ${c.cyan(profile.padEnd(20))} Subscription: ${c.cyan(sub)}`,
|
|
338
|
-
` Learning: ${learning}`,
|
|
339
|
-
'',
|
|
340
|
-
'─'.repeat(63),
|
|
341
|
-
'',
|
|
342
|
-
` ${c.bold('[d]')} Dial Adjust routing aggression`,
|
|
343
|
-
` ${c.bold('[r]')} Routing Model preferences & learned data`,
|
|
344
|
-
` ${c.bold('[t]')} Think Pre-flight settings & metrics`,
|
|
345
|
-
` ${c.bold('[b]')} Budget Limits and session caps`,
|
|
346
|
-
` ${c.bold('[s]')} Subscription Change plan type`,
|
|
347
|
-
` ${c.bold('[x]')} Reset Clear learned data`,
|
|
348
|
-
'',
|
|
349
|
-
` ${c.dim('[q]')} quit`,
|
|
350
|
-
'',
|
|
351
|
-
]);
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
let running = true;
|
|
355
|
-
while (running) {
|
|
356
|
-
showMenu();
|
|
357
|
-
const key = (await prompt(rl, ' > ')).trim().toLowerCase();
|
|
358
|
-
switch (key) {
|
|
359
|
-
case 'd': await dialScreen(rl, cwd); break;
|
|
360
|
-
case 'r': await routingScreen(rl, cwd); break;
|
|
361
|
-
case 't': await thinkScreen(rl, cwd); break;
|
|
362
|
-
case 'b': await budgetScreen(rl, cwd); break;
|
|
363
|
-
case 's': await subscriptionScreen(rl, cwd); break;
|
|
364
|
-
case 'x': await resetScreen(rl, cwd); break;
|
|
365
|
-
case 'q': case '': running = false; break;
|
|
366
|
-
default: console.log(c.dim(' Unknown option.'));
|
|
367
|
-
}
|
|
368
|
-
if (running && key !== '') await prompt(rl, c.dim('\n Press enter to continue...'));
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
rl.close();
|
|
372
|
-
console.log(c.dim('\n Settings closed.\n'));
|
|
373
|
-
}
|
package/src/setup-flow.mjs
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
// setup-flow.mjs — Interactive first-run setup for dual-brain
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { createInterface } from 'node:readline';
|
|
5
|
-
import { execSync } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
8
|
-
const c = {
|
|
9
|
-
bold: s => `\x1b[1m${s}\x1b[0m`,
|
|
10
|
-
dim: s => `\x1b[2m${s}\x1b[0m`,
|
|
11
|
-
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
12
|
-
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
13
|
-
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
14
|
-
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// ── Detection ─────────────────────────────────────────────────────────────────
|
|
18
|
-
export function detectEnvironment(cwd) {
|
|
19
|
-
const tryCmd = cmd => { try { execSync(cmd, { stdio: 'pipe' }); return true; } catch { return false; } };
|
|
20
|
-
|
|
21
|
-
let language = 'unknown';
|
|
22
|
-
if (existsSync(join(cwd, 'package.json'))) language = 'node';
|
|
23
|
-
else if (existsSync(join(cwd, 'pyproject.toml')) ||
|
|
24
|
-
existsSync(join(cwd, 'setup.py'))) language = 'python';
|
|
25
|
-
else if (existsSync(join(cwd, 'go.mod'))) language = 'go';
|
|
26
|
-
else if (existsSync(join(cwd, 'Cargo.toml'))) language = 'rust';
|
|
27
|
-
else if (existsSync(join(cwd, 'pom.xml'))) language = 'java';
|
|
28
|
-
|
|
29
|
-
let gitBranch = null;
|
|
30
|
-
try { gitBranch = execSync('git -C "' + cwd + '" branch --show-current', { stdio: 'pipe' }).toString().trim(); } catch {}
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
claude: tryCmd('claude --version'),
|
|
34
|
-
codex: tryCmd('codex --version'),
|
|
35
|
-
git: !!gitBranch,
|
|
36
|
-
gitBranch: gitBranch || null,
|
|
37
|
-
language,
|
|
38
|
-
existingConfig: existsSync(join(cwd, '.dualbrain', 'config.json')),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ── Welcome banner ────────────────────────────────────────────────────────────
|
|
43
|
-
export function renderWelcome(detected) {
|
|
44
|
-
const row = (ok, label) => c.cyan('│') + ` ${ok ? c.green('✓') : c.dim('✗')} ${ok ? label : c.dim(label)}`.padEnd(49) + c.cyan('│');
|
|
45
|
-
const bar = s => c.cyan('│') + s.padEnd(49) + c.cyan('│');
|
|
46
|
-
return [
|
|
47
|
-
c.cyan('╭' + '─'.repeat(49) + '╮'),
|
|
48
|
-
bar(''),
|
|
49
|
-
bar(` ${c.bold('dual-brain')} — intelligent model orchestration`),
|
|
50
|
-
bar(''),
|
|
51
|
-
bar(' Detected:'),
|
|
52
|
-
row(detected.claude, 'Claude CLI available'),
|
|
53
|
-
row(detected.codex, 'Codex CLI available'),
|
|
54
|
-
row(detected.git, `Git repository (${detected.gitBranch || 'no branch'} branch)`),
|
|
55
|
-
row(detected.language !== 'unknown', `${detected.language} project`),
|
|
56
|
-
bar(''),
|
|
57
|
-
c.cyan('╰' + '─'.repeat(49) + '╯'),
|
|
58
|
-
].join('\n');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const SUB_LABELS = {
|
|
62
|
-
'claude-pro': 'Claude Pro', 'claude-max-5x': 'Claude Max 5x',
|
|
63
|
-
'claude-max-20x': 'Claude Max 20x', 'chatgpt-plus': 'ChatGPT Plus',
|
|
64
|
-
'chatgpt-pro': 'ChatGPT Pro', 'dual-pro': 'Both Pro tiers', 'dual-max': 'Max + Pro tiers',
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// ── Confirmation display ──────────────────────────────────────────────────────
|
|
68
|
-
export function renderConfirmation(config) {
|
|
69
|
-
const row = (k, v) => ` ${c.dim(k.padEnd(16))} ${c.cyan(v)}`;
|
|
70
|
-
return [
|
|
71
|
-
'', c.bold(' Configuration:'), '',
|
|
72
|
-
row('Subscription:', SUB_LABELS[config.subscription] || config.subscription),
|
|
73
|
-
row('Work style:', config.workStyle),
|
|
74
|
-
row('Primary model:', config.models.execute),
|
|
75
|
-
row('Think agent:', config.routing.thinkEnabled ? 'enabled' : 'disabled'),
|
|
76
|
-
row('Learning:', config.routing.learningEnabled ? 'on' : 'off'),
|
|
77
|
-
'',
|
|
78
|
-
].join('\n');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ── Config builder ────────────────────────────────────────────────────────────
|
|
82
|
-
export function buildConfig(answers, detected) {
|
|
83
|
-
const { subscription = 'claude-pro', workStyle = 'balanced', advanced = {}, setupMode = 'quick' } = answers;
|
|
84
|
-
const dual = subscription.startsWith('dual-');
|
|
85
|
-
const isMax = subscription.includes('max') || subscription === 'chatgpt-pro';
|
|
86
|
-
const topModel = isMax ? 'opus' : 'sonnet';
|
|
87
|
-
const exploreRate = { aggressive: 0.3, conservative: 0.1, auto: 0.25 }[workStyle] ?? 0.2;
|
|
88
|
-
return {
|
|
89
|
-
version: 1, subscription, workStyle,
|
|
90
|
-
providers: { claude: subscription.startsWith('claude-') || dual, openai: subscription.startsWith('chatgpt-') || dual },
|
|
91
|
-
routing: {
|
|
92
|
-
thinkEnabled: advanced.thinkEnabled ?? true,
|
|
93
|
-
cascadeEnabled: advanced.cascadeEnabled ?? true,
|
|
94
|
-
learningEnabled: advanced.learningEnabled ?? true,
|
|
95
|
-
explorationRate: advanced.explorationRate ?? exploreRate,
|
|
96
|
-
},
|
|
97
|
-
models: advanced.models || { search: 'haiku', execute: 'sonnet', think: topModel, review: topModel },
|
|
98
|
-
budget: { sessionLimitTokens: advanced.sessionLimitTokens ?? null, warnAtPercent: advanced.warnAtPercent ?? 80 },
|
|
99
|
-
configuredAt: new Date().toISOString(),
|
|
100
|
-
setupMode,
|
|
101
|
-
detectedEnv: { claude: detected.claude, codex: detected.codex, language: detected.language },
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Save config ───────────────────────────────────────────────────────────────
|
|
106
|
-
export function saveConfig(config, cwd) {
|
|
107
|
-
const dir = join(cwd, '.dualbrain');
|
|
108
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
109
|
-
writeFileSync(join(dir, 'config.json'), JSON.stringify(config, null, 2), 'utf8');
|
|
110
|
-
const orchPath = join(cwd, '.claude', 'orchestrator.json');
|
|
111
|
-
if (existsSync(orchPath)) {
|
|
112
|
-
try {
|
|
113
|
-
const orch = JSON.parse(readFileSync(orchPath, 'utf8'));
|
|
114
|
-
if (!orch.providers) orch.providers = {};
|
|
115
|
-
orch.providers.claude = { ...(orch.providers.claude || {}), enabled: config.providers.claude, subscription: config.subscription };
|
|
116
|
-
orch.providers.openai = { ...(orch.providers.openai || {}), enabled: config.providers.openai };
|
|
117
|
-
if (config.routing) orch.routing = { ...(orch.routing || {}), ...config.routing };
|
|
118
|
-
writeFileSync(orchPath, JSON.stringify(orch, null, 2), 'utf8');
|
|
119
|
-
} catch { /* non-fatal */ }
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── Readline prompt helper ────────────────────────────────────────────────────
|
|
124
|
-
async function ask(rl, question, options) {
|
|
125
|
-
const lines = options.map((o, i) => ` ${c.cyan(String(i + 1) + ')')} ${o.label}${o.description ? c.dim(' — ' + o.description) : ''}`);
|
|
126
|
-
const prompt = `\n${c.bold(question)}\n${lines.join('\n')}\n${c.dim('> ')}`;
|
|
127
|
-
return new Promise(resolve => {
|
|
128
|
-
rl.question(prompt, answer => {
|
|
129
|
-
const trimmed = answer.trim();
|
|
130
|
-
const idx = parseInt(trimmed, 10) - 1;
|
|
131
|
-
if (idx >= 0 && idx < options.length) resolve(options[idx].value);
|
|
132
|
-
else resolve(options[0].value);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function askYN(rl, question, defaultYes = true) {
|
|
138
|
-
return new Promise(resolve => {
|
|
139
|
-
rl.question(`\n${c.bold(question)} ${c.dim(defaultYes ? '(Y/n)' : '(y/N)')} `, answer => {
|
|
140
|
-
const t = answer.trim().toLowerCase();
|
|
141
|
-
if (!t) resolve(defaultYes);
|
|
142
|
-
else resolve(t === 'y' || t === 'yes');
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ── Main entry point ──────────────────────────────────────────────────────────
|
|
148
|
-
export async function runSetup(cwd, options = {}) {
|
|
149
|
-
const detected = detectEnvironment(cwd);
|
|
150
|
-
|
|
151
|
-
// Non-TTY fast path — stdin is piped or in CI
|
|
152
|
-
if (!process.stdin.isTTY && !options.nonInteractive) {
|
|
153
|
-
process.stderr.write('[dual-brain] Non-interactive terminal detected. Use --non-interactive flag or run in a TTY.\n');
|
|
154
|
-
const config = buildConfig({ subscription: 'claude-pro', workStyle: 'balanced' }, detected);
|
|
155
|
-
saveConfig(config, cwd);
|
|
156
|
-
return config;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Non-interactive fast path
|
|
160
|
-
if (options.nonInteractive) {
|
|
161
|
-
const config = buildConfig({
|
|
162
|
-
subscription: options.subscription || 'claude-pro',
|
|
163
|
-
workStyle: options.workStyle || 'balanced',
|
|
164
|
-
setupMode: 'non-interactive',
|
|
165
|
-
}, detected);
|
|
166
|
-
saveConfig(config, cwd);
|
|
167
|
-
return config;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Already configured?
|
|
171
|
-
if (detected.existingConfig && !options.reconfigure) {
|
|
172
|
-
console.log('\n' + c.yellow('dual-brain is already configured.') + ' Pass --reconfigure to change settings.\n');
|
|
173
|
-
return JSON.parse(readFileSync(join(cwd, '.dualbrain', 'config.json'), 'utf8'));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('\n' + renderWelcome(detected) + '\n');
|
|
177
|
-
|
|
178
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
179
|
-
const close = () => rl.close();
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const mode = await ask(rl, 'Setup mode:', [
|
|
183
|
-
{ label: 'Quick setup', value: 'quick', description: '3 questions, ~20 seconds' },
|
|
184
|
-
{ label: 'Advanced', value: 'advanced', description: 'full control over routing, budgets, models' },
|
|
185
|
-
]);
|
|
186
|
-
const subscription = await ask(rl, 'Your AI subscription:', [
|
|
187
|
-
{ label: 'Claude Pro ($20/mo)', value: 'claude-pro' },
|
|
188
|
-
{ label: 'Claude Max 5x ($100/mo)', value: 'claude-max-5x' },
|
|
189
|
-
{ label: 'Claude Max 20x ($200/mo)', value: 'claude-max-20x' },
|
|
190
|
-
{ label: 'ChatGPT Plus ($20/mo)', value: 'chatgpt-plus' },
|
|
191
|
-
{ label: 'ChatGPT Pro ($200/mo)', value: 'chatgpt-pro' },
|
|
192
|
-
{ label: 'Both providers (Pro tiers)', value: 'dual-pro' },
|
|
193
|
-
{ label: 'Both providers (Max tiers)', value: 'dual-max' },
|
|
194
|
-
]);
|
|
195
|
-
const workStyle = await ask(rl, 'How should dual-brain route your work?', [
|
|
196
|
-
{ label: 'Balanced', value: 'balanced', description: 'smart defaults, asks before expensive ops' },
|
|
197
|
-
{ label: 'Conservative', value: 'conservative', description: 'minimize tokens, prefer cheaper models' },
|
|
198
|
-
{ label: 'Aggressive', value: 'aggressive', description: 'best model available, maximize quality' },
|
|
199
|
-
{ label: 'Full auto', value: 'auto', description: 'never ask, optimize silently' },
|
|
200
|
-
]);
|
|
201
|
-
let advanced = {};
|
|
202
|
-
if (mode === 'advanced') {
|
|
203
|
-
const thinkEnabled = await askYN(rl, 'Enable think agent?', true);
|
|
204
|
-
const cascadeEnabled = await askYN(rl, 'Enable cascade routing?', true);
|
|
205
|
-
const learningEnabled = await askYN(rl, 'Enable learning (improves routing over time)?', true);
|
|
206
|
-
const explorationRate = await ask(rl, 'Routing exploration rate:', [
|
|
207
|
-
{ label: 'Low (0.1)', value: 0.1, description: 'rarely tries new routes' },
|
|
208
|
-
{ label: 'Medium (0.2)', value: 0.2, description: 'balanced' },
|
|
209
|
-
{ label: 'High (0.3)', value: 0.3, description: 'frequently explores alternatives' },
|
|
210
|
-
]);
|
|
211
|
-
advanced = { thinkEnabled, cascadeEnabled, learningEnabled, explorationRate };
|
|
212
|
-
}
|
|
213
|
-
const config = buildConfig({ subscription, workStyle, advanced, setupMode: mode }, detected);
|
|
214
|
-
console.log(renderConfirmation(config));
|
|
215
|
-
if (!await askYN(rl, 'Save and start?', true)) {
|
|
216
|
-
console.log('\n' + c.yellow('Setup cancelled.') + '\n');
|
|
217
|
-
close(); return null;
|
|
218
|
-
}
|
|
219
|
-
saveConfig(config, cwd);
|
|
220
|
-
console.log('\n' + c.green('✓') + ' ' + c.bold('dual-brain configured.') + ' Config saved to ' + c.cyan('.dualbrain/config.json') + '\n');
|
|
221
|
-
close(); return config;
|
|
222
|
-
} catch (err) { close(); throw err; }
|
|
223
|
-
}
|