dual-brain 0.2.30 → 0.3.1

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 (312) 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/precompact.mjs +3 -3
  240. package/hooks/session-end.mjs +3 -3
  241. package/hooks/task-classifier.mjs +328 -0
  242. package/hooks/vibe-router.mjs +387 -0
  243. package/install.mjs +2 -2
  244. package/package.json +29 -153
  245. package/src/agents/registry.mjs +0 -405
  246. package/src/awareness.mjs +0 -425
  247. package/src/brief.mjs +0 -266
  248. package/src/calibration.mjs +0 -148
  249. package/src/checkpoint.mjs +0 -109
  250. package/src/ci-triage.mjs +0 -191
  251. package/src/cognitive-loop.mjs +0 -562
  252. package/src/collaboration.mjs +0 -545
  253. package/src/context-intel.mjs +0 -158
  254. package/src/context.mjs +0 -389
  255. package/src/continuity.mjs +0 -298
  256. package/src/cost-tracker.mjs +0 -184
  257. package/src/debrief.mjs +0 -228
  258. package/src/decide.mjs +0 -1099
  259. package/src/decompose.mjs +0 -331
  260. package/src/detect.mjs +0 -702
  261. package/src/dispatch.mjs +0 -1447
  262. package/src/doctor.mjs +0 -1607
  263. package/src/envelope.mjs +0 -139
  264. package/src/failure-memory.mjs +0 -178
  265. package/src/fx.mjs +0 -276
  266. package/src/governance.mjs +0 -279
  267. package/src/handoff.mjs +0 -87
  268. package/src/head-protocol.mjs +0 -128
  269. package/src/head.mjs +0 -952
  270. package/src/health.mjs +0 -528
  271. package/src/inbox.mjs +0 -195
  272. package/src/index.mjs +0 -44
  273. package/src/install-hooks.mjs +0 -100
  274. package/src/integrity.mjs +0 -245
  275. package/src/intelligence.mjs +0 -447
  276. package/src/ledger.mjs +0 -196
  277. package/src/living-docs.mjs +0 -210
  278. package/src/memory-tiers.mjs +0 -193
  279. package/src/models.mjs +0 -363
  280. package/src/narrative.mjs +0 -169
  281. package/src/nextstep.mjs +0 -100
  282. package/src/observer.mjs +0 -241
  283. package/src/outcome.mjs +0 -400
  284. package/src/pipeline.mjs +0 -1711
  285. package/src/playbook.mjs +0 -257
  286. package/src/pr-agent.mjs +0 -214
  287. package/src/predictive.mjs +0 -250
  288. package/src/profile.mjs +0 -1411
  289. package/src/prompt-audit.mjs +0 -231
  290. package/src/prompt-intel.mjs +0 -325
  291. package/src/provider-context.mjs +0 -257
  292. package/src/receipt.mjs +0 -344
  293. package/src/recommendations.mjs +0 -296
  294. package/src/redact.mjs +0 -192
  295. package/src/replit.mjs +0 -1210
  296. package/src/repo.mjs +0 -445
  297. package/src/revert.mjs +0 -149
  298. package/src/routing-advisor.mjs +0 -204
  299. package/src/self-correct.mjs +0 -147
  300. package/src/session-lock.mjs +0 -160
  301. package/src/session.mjs +0 -1655
  302. package/src/settings-tui.mjs +0 -373
  303. package/src/setup-flow.mjs +0 -223
  304. package/src/signal.mjs +0 -115
  305. package/src/simmer.mjs +0 -241
  306. package/src/strategy.mjs +0 -235
  307. package/src/subscription.mjs +0 -212
  308. package/src/templates.mjs +0 -260
  309. package/src/think-engine.mjs +0 -428
  310. package/src/tui.mjs +0 -276
  311. package/src/update-check.mjs +0 -35
  312. 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
- }