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.
Files changed (309) hide show
  1. package/.dual-brain/docs/claude-code-extension-points.md +32 -0
  2. package/.dual-brain/docs/data-tools-capabilities.md +181 -0
  3. package/.dual-brain/docs/ecosystem-tools.md +91 -0
  4. package/.dual-brain/docs/panel-handoff.md +124 -0
  5. package/.dual-brain/docs/ruflo-analysis.md +48 -0
  6. package/bin/dual-brain.mjs +56 -56
  7. package/dist/mcp-server/index.d.ts +27 -0
  8. package/dist/mcp-server/index.js +359 -0
  9. package/dist/mcp-server/index.js.map +1 -0
  10. package/dist/src/agent-protocol.d.ts +163 -0
  11. package/dist/src/agent-protocol.js +368 -0
  12. package/dist/src/agent-protocol.js.map +1 -0
  13. package/dist/src/agents/registry.d.ts +52 -0
  14. package/dist/src/agents/registry.js +393 -0
  15. package/dist/src/agents/registry.js.map +1 -0
  16. package/dist/src/awareness.d.ts +93 -0
  17. package/dist/src/awareness.js +406 -0
  18. package/dist/src/awareness.js.map +1 -0
  19. package/dist/src/brief.d.ts +48 -0
  20. package/dist/src/brief.js +179 -0
  21. package/dist/src/brief.js.map +1 -0
  22. package/dist/src/calibration.d.ts +32 -0
  23. package/dist/src/calibration.js +133 -0
  24. package/dist/src/calibration.js.map +1 -0
  25. package/dist/src/checkpoint.d.ts +33 -0
  26. package/dist/src/checkpoint.js +99 -0
  27. package/dist/src/checkpoint.js.map +1 -0
  28. package/dist/src/ci-triage.d.ts +33 -0
  29. package/dist/src/ci-triage.js +193 -0
  30. package/dist/src/ci-triage.js.map +1 -0
  31. package/dist/src/cognitive-loop.d.ts +56 -0
  32. package/dist/src/cognitive-loop.js +495 -0
  33. package/dist/src/cognitive-loop.js.map +1 -0
  34. package/dist/src/collaboration.d.ts +147 -0
  35. package/dist/src/collaboration.js +438 -0
  36. package/dist/src/collaboration.js.map +1 -0
  37. package/dist/src/context-intel.d.ts +47 -0
  38. package/dist/src/context-intel.js +156 -0
  39. package/dist/src/context-intel.js.map +1 -0
  40. package/dist/src/context.d.ts +53 -0
  41. package/dist/src/context.js +332 -0
  42. package/dist/src/context.js.map +1 -0
  43. package/dist/src/continuity.d.ts +89 -0
  44. package/dist/src/continuity.js +230 -0
  45. package/dist/src/continuity.js.map +1 -0
  46. package/dist/src/cost-tracker.d.ts +47 -0
  47. package/dist/src/cost-tracker.js +170 -0
  48. package/dist/src/cost-tracker.js.map +1 -0
  49. package/dist/src/debrief.d.ts +53 -0
  50. package/dist/src/debrief.js +222 -0
  51. package/dist/src/debrief.js.map +1 -0
  52. package/dist/src/decide.d.ts +96 -0
  53. package/dist/src/decide.js +744 -0
  54. package/dist/src/decide.js.map +1 -0
  55. package/dist/src/decompose.d.ts +39 -0
  56. package/dist/src/decompose.js +218 -0
  57. package/dist/src/decompose.js.map +1 -0
  58. package/dist/src/detect.d.ts +91 -0
  59. package/dist/src/detect.js +544 -0
  60. package/dist/src/detect.js.map +1 -0
  61. package/dist/src/dispatch.d.ts +154 -0
  62. package/dist/src/dispatch.js +1306 -0
  63. package/dist/src/dispatch.js.map +1 -0
  64. package/dist/src/doctor.d.ts +421 -0
  65. package/dist/src/doctor.js +1689 -0
  66. package/dist/src/doctor.js.map +1 -0
  67. package/dist/src/engine.d.ts +70 -0
  68. package/dist/src/engine.js +155 -0
  69. package/dist/src/engine.js.map +1 -0
  70. package/dist/src/envelope.d.ts +36 -0
  71. package/dist/src/envelope.js +80 -0
  72. package/dist/src/envelope.js.map +1 -0
  73. package/dist/src/failure-memory.d.ts +55 -0
  74. package/dist/src/failure-memory.js +175 -0
  75. package/dist/src/failure-memory.js.map +1 -0
  76. package/dist/src/fx.d.ts +87 -0
  77. package/dist/src/fx.js +272 -0
  78. package/dist/src/fx.js.map +1 -0
  79. package/dist/src/governance.d.ts +93 -0
  80. package/dist/src/governance.js +261 -0
  81. package/dist/src/governance.js.map +1 -0
  82. package/dist/src/handoff.d.ts +11 -0
  83. package/dist/src/handoff.js +90 -0
  84. package/dist/src/handoff.js.map +1 -0
  85. package/dist/src/head-protocol.d.ts +76 -0
  86. package/dist/src/head-protocol.js +109 -0
  87. package/dist/src/head-protocol.js.map +1 -0
  88. package/dist/src/head.d.ts +222 -0
  89. package/dist/src/head.js +765 -0
  90. package/dist/src/head.js.map +1 -0
  91. package/dist/src/health.d.ts +132 -0
  92. package/dist/src/health.js +435 -0
  93. package/dist/src/health.js.map +1 -0
  94. package/dist/src/inbox.d.ts +70 -0
  95. package/dist/src/inbox.js +218 -0
  96. package/dist/src/inbox.js.map +1 -0
  97. package/dist/src/index.d.ts +33 -0
  98. package/dist/src/index.js +38 -0
  99. package/dist/src/index.js.map +1 -0
  100. package/dist/src/install-hooks.d.ts +13 -0
  101. package/dist/src/install-hooks.js +88 -0
  102. package/dist/src/install-hooks.js.map +1 -0
  103. package/dist/src/integrity.d.ts +59 -0
  104. package/dist/src/integrity.js +206 -0
  105. package/dist/src/integrity.js.map +1 -0
  106. package/dist/src/intelligence.d.ts +104 -0
  107. package/dist/src/intelligence.js +391 -0
  108. package/dist/src/intelligence.js.map +1 -0
  109. package/dist/src/ledger.d.ts +54 -0
  110. package/dist/src/ledger.js +179 -0
  111. package/dist/src/ledger.js.map +1 -0
  112. package/dist/src/living-docs.d.ts +14 -0
  113. package/dist/src/living-docs.js +197 -0
  114. package/dist/src/living-docs.js.map +1 -0
  115. package/dist/src/memory-tiers.d.ts +37 -0
  116. package/dist/src/memory-tiers.js +160 -0
  117. package/dist/src/memory-tiers.js.map +1 -0
  118. package/dist/src/model-profiles.d.ts +65 -0
  119. package/dist/src/model-profiles.js +568 -0
  120. package/dist/src/model-profiles.js.map +1 -0
  121. package/dist/src/models.d.ts +58 -0
  122. package/dist/src/models.js +327 -0
  123. package/dist/src/models.js.map +1 -0
  124. package/dist/src/narrative.d.ts +54 -0
  125. package/dist/src/narrative.js +163 -0
  126. package/dist/src/narrative.js.map +1 -0
  127. package/dist/src/nextstep.d.ts +16 -0
  128. package/dist/src/nextstep.js +103 -0
  129. package/dist/src/nextstep.js.map +1 -0
  130. package/dist/src/observer.d.ts +18 -0
  131. package/dist/src/observer.js +251 -0
  132. package/dist/src/observer.js.map +1 -0
  133. package/dist/src/outcome.d.ts +110 -0
  134. package/dist/src/outcome.js +377 -0
  135. package/dist/src/outcome.js.map +1 -0
  136. package/dist/src/pipeline.d.ts +167 -0
  137. package/dist/src/pipeline.js +1503 -0
  138. package/dist/src/pipeline.js.map +1 -0
  139. package/dist/src/playbook.d.ts +59 -0
  140. package/dist/src/playbook.js +238 -0
  141. package/dist/src/playbook.js.map +1 -0
  142. package/dist/src/pr-agent.d.ts +97 -0
  143. package/dist/src/pr-agent.js +195 -0
  144. package/dist/src/pr-agent.js.map +1 -0
  145. package/dist/src/predictive.d.ts +57 -0
  146. package/dist/src/predictive.js +230 -0
  147. package/dist/src/predictive.js.map +1 -0
  148. package/dist/src/profile.d.ts +294 -0
  149. package/dist/src/profile.js +1347 -0
  150. package/dist/src/profile.js.map +1 -0
  151. package/dist/src/prompt-audit.d.ts +22 -0
  152. package/dist/src/prompt-audit.js +194 -0
  153. package/dist/src/prompt-audit.js.map +1 -0
  154. package/dist/src/prompt-intel.d.ts +12 -0
  155. package/dist/src/prompt-intel.js +321 -0
  156. package/dist/src/prompt-intel.js.map +1 -0
  157. package/dist/src/provider-context.d.ts +121 -0
  158. package/dist/src/provider-context.js +222 -0
  159. package/dist/src/provider-context.js.map +1 -0
  160. package/dist/src/provider-manager.d.ts +92 -0
  161. package/dist/src/provider-manager.js +428 -0
  162. package/dist/src/provider-manager.js.map +1 -0
  163. package/dist/src/receipt.d.ts +87 -0
  164. package/dist/src/receipt.js +326 -0
  165. package/dist/src/receipt.js.map +1 -0
  166. package/dist/src/recommendations.d.ts +13 -0
  167. package/dist/src/recommendations.js +291 -0
  168. package/dist/src/recommendations.js.map +1 -0
  169. package/dist/src/redact.d.ts +15 -0
  170. package/dist/src/redact.js +129 -0
  171. package/dist/src/redact.js.map +1 -0
  172. package/dist/src/replit.d.ts +397 -0
  173. package/dist/src/replit.js +1160 -0
  174. package/dist/src/replit.js.map +1 -0
  175. package/dist/src/repo.d.ts +149 -0
  176. package/dist/src/repo.js +416 -0
  177. package/dist/src/repo.js.map +1 -0
  178. package/dist/src/revert.d.ts +30 -0
  179. package/dist/src/revert.js +166 -0
  180. package/dist/src/revert.js.map +1 -0
  181. package/dist/src/room.d.ts +102 -0
  182. package/dist/src/room.js +212 -0
  183. package/dist/src/room.js.map +1 -0
  184. package/dist/src/routing-advisor.d.ts +57 -0
  185. package/dist/src/routing-advisor.js +221 -0
  186. package/dist/src/routing-advisor.js.map +1 -0
  187. package/dist/src/self-correct.d.ts +40 -0
  188. package/dist/src/self-correct.js +137 -0
  189. package/dist/src/self-correct.js.map +1 -0
  190. package/dist/src/session-lock.d.ts +35 -0
  191. package/dist/src/session-lock.js +134 -0
  192. package/dist/src/session-lock.js.map +1 -0
  193. package/dist/src/session.d.ts +267 -0
  194. package/dist/src/session.js +1660 -0
  195. package/dist/src/session.js.map +1 -0
  196. package/dist/src/settings-tui.d.ts +5 -0
  197. package/dist/src/settings-tui.js +422 -0
  198. package/dist/src/settings-tui.js.map +1 -0
  199. package/dist/src/setup-flow.d.ts +63 -0
  200. package/dist/src/setup-flow.js +233 -0
  201. package/dist/src/setup-flow.js.map +1 -0
  202. package/dist/src/signal.d.ts +19 -0
  203. package/dist/src/signal.js +122 -0
  204. package/dist/src/signal.js.map +1 -0
  205. package/dist/src/simmer.d.ts +85 -0
  206. package/dist/src/simmer.js +224 -0
  207. package/dist/src/simmer.js.map +1 -0
  208. package/dist/src/state-export.d.ts +129 -0
  209. package/dist/src/state-export.js +233 -0
  210. package/dist/src/state-export.js.map +1 -0
  211. package/dist/src/strategy.d.ts +54 -0
  212. package/dist/src/strategy.js +95 -0
  213. package/dist/src/strategy.js.map +1 -0
  214. package/dist/src/subscription.d.ts +40 -0
  215. package/dist/src/subscription.js +189 -0
  216. package/dist/src/subscription.js.map +1 -0
  217. package/dist/src/templates.d.ts +208 -0
  218. package/dist/src/templates.js +238 -0
  219. package/dist/src/templates.js.map +1 -0
  220. package/dist/src/test.d.ts +9 -0
  221. package/dist/src/test.js +1173 -0
  222. package/dist/src/test.js.map +1 -0
  223. package/dist/src/think-engine.d.ts +67 -0
  224. package/dist/src/think-engine.js +412 -0
  225. package/dist/src/think-engine.js.map +1 -0
  226. package/dist/src/tui.d.ts +71 -0
  227. package/dist/src/tui.js +242 -0
  228. package/dist/src/tui.js.map +1 -0
  229. package/dist/src/types.d.ts +177 -0
  230. package/dist/src/types.js +6 -0
  231. package/dist/src/types.js.map +1 -0
  232. package/dist/src/update-check.d.ts +7 -0
  233. package/dist/src/update-check.js +36 -0
  234. package/dist/src/update-check.js.map +1 -0
  235. package/dist/src/wave-planner.d.ts +30 -0
  236. package/dist/src/wave-planner.js +281 -0
  237. package/dist/src/wave-planner.js.map +1 -0
  238. package/hooks/head-guard.sh +41 -0
  239. package/hooks/task-classifier.mjs +328 -0
  240. package/hooks/vibe-router.mjs +387 -0
  241. package/package.json +29 -153
  242. package/src/agents/registry.mjs +0 -405
  243. package/src/awareness.mjs +0 -425
  244. package/src/brief.mjs +0 -266
  245. package/src/calibration.mjs +0 -148
  246. package/src/checkpoint.mjs +0 -109
  247. package/src/ci-triage.mjs +0 -191
  248. package/src/cognitive-loop.mjs +0 -562
  249. package/src/collaboration.mjs +0 -545
  250. package/src/context-intel.mjs +0 -158
  251. package/src/context.mjs +0 -389
  252. package/src/continuity.mjs +0 -298
  253. package/src/cost-tracker.mjs +0 -184
  254. package/src/debrief.mjs +0 -228
  255. package/src/decide.mjs +0 -1099
  256. package/src/decompose.mjs +0 -331
  257. package/src/detect.mjs +0 -702
  258. package/src/dispatch.mjs +0 -1447
  259. package/src/doctor.mjs +0 -1607
  260. package/src/envelope.mjs +0 -139
  261. package/src/failure-memory.mjs +0 -178
  262. package/src/fx.mjs +0 -276
  263. package/src/governance.mjs +0 -279
  264. package/src/handoff.mjs +0 -87
  265. package/src/head-protocol.mjs +0 -128
  266. package/src/head.mjs +0 -952
  267. package/src/health.mjs +0 -528
  268. package/src/inbox.mjs +0 -195
  269. package/src/index.mjs +0 -44
  270. package/src/install-hooks.mjs +0 -100
  271. package/src/integrity.mjs +0 -245
  272. package/src/intelligence.mjs +0 -447
  273. package/src/ledger.mjs +0 -196
  274. package/src/living-docs.mjs +0 -210
  275. package/src/memory-tiers.mjs +0 -193
  276. package/src/models.mjs +0 -363
  277. package/src/narrative.mjs +0 -169
  278. package/src/nextstep.mjs +0 -100
  279. package/src/observer.mjs +0 -241
  280. package/src/outcome.mjs +0 -400
  281. package/src/pipeline.mjs +0 -1711
  282. package/src/playbook.mjs +0 -257
  283. package/src/pr-agent.mjs +0 -214
  284. package/src/predictive.mjs +0 -250
  285. package/src/profile.mjs +0 -1411
  286. package/src/prompt-audit.mjs +0 -231
  287. package/src/prompt-intel.mjs +0 -325
  288. package/src/provider-context.mjs +0 -257
  289. package/src/receipt.mjs +0 -344
  290. package/src/recommendations.mjs +0 -296
  291. package/src/redact.mjs +0 -192
  292. package/src/replit.mjs +0 -1210
  293. package/src/repo.mjs +0 -445
  294. package/src/revert.mjs +0 -149
  295. package/src/routing-advisor.mjs +0 -204
  296. package/src/self-correct.mjs +0 -147
  297. package/src/session-lock.mjs +0 -160
  298. package/src/session.mjs +0 -1655
  299. package/src/settings-tui.mjs +0 -373
  300. package/src/setup-flow.mjs +0 -223
  301. package/src/signal.mjs +0 -115
  302. package/src/simmer.mjs +0 -241
  303. package/src/strategy.mjs +0 -235
  304. package/src/subscription.mjs +0 -212
  305. package/src/templates.mjs +0 -260
  306. package/src/think-engine.mjs +0 -428
  307. package/src/tui.mjs +0 -276
  308. package/src/update-check.mjs +0 -35
  309. package/src/wave-planner.mjs +0 -294
@@ -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
- }
@@ -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
- }