aura-code 0.6.3 → 0.10.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 (265) hide show
  1. package/README.md +47 -164
  2. package/dist/agent/affect.d.ts +13 -0
  3. package/dist/agent/affect.js +34 -0
  4. package/dist/agent/affect.js.map +1 -0
  5. package/dist/agent/compactor.d.ts +27 -4
  6. package/dist/agent/compactor.js +185 -54
  7. package/dist/agent/compactor.js.map +1 -1
  8. package/dist/agent/confess.d.ts +35 -0
  9. package/dist/agent/confess.js +296 -0
  10. package/dist/agent/confess.js.map +1 -0
  11. package/dist/agent/context.d.ts +0 -4
  12. package/dist/agent/context.js +0 -38
  13. package/dist/agent/context.js.map +1 -1
  14. package/dist/agent/domain-expertise.d.ts +22 -0
  15. package/dist/agent/domain-expertise.js +110 -0
  16. package/dist/agent/domain-expertise.js.map +1 -0
  17. package/dist/agent/executive-queue.d.ts +26 -0
  18. package/dist/agent/executive-queue.js +50 -0
  19. package/dist/agent/executive-queue.js.map +1 -0
  20. package/dist/agent/generational-flush.d.ts +28 -0
  21. package/dist/agent/generational-flush.js +48 -0
  22. package/dist/agent/generational-flush.js.map +1 -0
  23. package/dist/agent/loop-profile.d.ts +50 -0
  24. package/dist/agent/loop-profile.js +88 -0
  25. package/dist/agent/loop-profile.js.map +1 -0
  26. package/dist/agent/loop.d.ts +14 -5
  27. package/dist/agent/loop.js +167 -39
  28. package/dist/agent/loop.js.map +1 -1
  29. package/dist/agent/memory-consolidate.d.ts +14 -0
  30. package/dist/agent/memory-consolidate.js +141 -0
  31. package/dist/agent/memory-consolidate.js.map +1 -0
  32. package/dist/agent/mixture.d.ts +33 -0
  33. package/dist/agent/mixture.js +138 -0
  34. package/dist/agent/mixture.js.map +1 -0
  35. package/dist/agent/spawner.js +2 -2
  36. package/dist/agent/spawner.js.map +1 -1
  37. package/dist/agent/system-prompt.d.ts +1 -1
  38. package/dist/agent/system-prompt.js +50 -22
  39. package/dist/agent/system-prompt.js.map +1 -1
  40. package/dist/agent/unified-memory.d.ts +15 -0
  41. package/dist/agent/unified-memory.js +164 -0
  42. package/dist/agent/unified-memory.js.map +1 -0
  43. package/dist/checkpoints/engine.d.ts +44 -0
  44. package/dist/checkpoints/engine.js +324 -0
  45. package/dist/checkpoints/engine.js.map +1 -0
  46. package/dist/cli/command-palette.d.ts +26 -0
  47. package/dist/cli/command-palette.js +133 -0
  48. package/dist/cli/command-palette.js.map +1 -0
  49. package/dist/cli/context-health.d.ts +51 -0
  50. package/dist/cli/context-health.js +187 -0
  51. package/dist/cli/context-health.js.map +1 -0
  52. package/dist/cli/diamond.d.ts +67 -0
  53. package/dist/cli/diamond.js +222 -12
  54. package/dist/cli/diamond.js.map +1 -1
  55. package/dist/cli/dic.d.ts +2 -0
  56. package/dist/cli/dic.js +106 -0
  57. package/dist/cli/dic.js.map +1 -0
  58. package/dist/cli/diff-view.d.ts +18 -0
  59. package/dist/cli/diff-view.js +114 -0
  60. package/dist/cli/diff-view.js.map +1 -0
  61. package/dist/cli/display.d.ts +18 -4
  62. package/dist/cli/display.js +74 -151
  63. package/dist/cli/display.js.map +1 -1
  64. package/dist/cli/help-data.d.ts +7 -0
  65. package/dist/cli/help-data.js +80 -0
  66. package/dist/cli/help-data.js.map +1 -0
  67. package/dist/cli/index.js +1002 -852
  68. package/dist/cli/index.js.map +1 -1
  69. package/dist/cli/markdown.d.ts +5 -0
  70. package/dist/cli/markdown.js +128 -0
  71. package/dist/cli/markdown.js.map +1 -0
  72. package/dist/cli/model-select.d.ts +39 -0
  73. package/dist/cli/model-select.js +65 -0
  74. package/dist/cli/model-select.js.map +1 -0
  75. package/dist/cli/tui.d.ts +34 -0
  76. package/dist/cli/tui.js +1176 -0
  77. package/dist/cli/tui.js.map +1 -0
  78. package/dist/config/defaults.d.ts +6 -17
  79. package/dist/config/defaults.js +25 -46
  80. package/dist/config/defaults.js.map +1 -1
  81. package/dist/config/project-config.d.ts +2 -6
  82. package/dist/config/project-config.js +2 -2
  83. package/dist/config/project-config.js.map +1 -1
  84. package/dist/doctor/checks.d.ts +18 -0
  85. package/dist/doctor/checks.js +524 -0
  86. package/dist/doctor/checks.js.map +1 -0
  87. package/dist/doctor/index.d.ts +4 -0
  88. package/dist/doctor/index.js +229 -0
  89. package/dist/doctor/index.js.map +1 -0
  90. package/dist/doctor/repair.d.ts +19 -0
  91. package/dist/doctor/repair.js +126 -0
  92. package/dist/doctor/repair.js.map +1 -0
  93. package/dist/doctor/types.d.ts +38 -0
  94. package/dist/doctor/types.js +10 -0
  95. package/dist/doctor/types.js.map +1 -0
  96. package/dist/dream/dream.d.ts +79 -59
  97. package/dist/dream/dream.js +324 -144
  98. package/dist/dream/dream.js.map +1 -1
  99. package/dist/dream/episode.d.ts +17 -0
  100. package/dist/dream/episode.js +116 -0
  101. package/dist/dream/episode.js.map +1 -0
  102. package/dist/kanban/engine.d.ts +26 -0
  103. package/dist/kanban/engine.js +208 -0
  104. package/dist/kanban/engine.js.map +1 -0
  105. package/dist/kanban/types.d.ts +19 -0
  106. package/dist/kanban/types.js +9 -1
  107. package/dist/kanban/types.js.map +1 -1
  108. package/dist/machina/render-html.js +4 -2
  109. package/dist/machina/render-html.js.map +1 -1
  110. package/dist/machina/render-terminal.js +11 -4
  111. package/dist/machina/render-terminal.js.map +1 -1
  112. package/dist/machina/repair.d.ts +23 -0
  113. package/dist/machina/repair.js +98 -0
  114. package/dist/machina/repair.js.map +1 -0
  115. package/dist/machina/spec.js +12 -12
  116. package/dist/machina/spec.js.map +1 -1
  117. package/dist/machina/verify.d.ts +13 -1
  118. package/dist/machina/verify.js +12 -2
  119. package/dist/machina/verify.js.map +1 -1
  120. package/dist/mining/extract.d.ts +34 -0
  121. package/dist/mining/extract.js +229 -0
  122. package/dist/mining/extract.js.map +1 -0
  123. package/dist/mining/refine.d.ts +28 -0
  124. package/dist/mining/refine.js +256 -0
  125. package/dist/mining/refine.js.map +1 -0
  126. package/dist/perception/extractor.js +5 -3
  127. package/dist/perception/extractor.js.map +1 -1
  128. package/dist/perception/graph-store.d.ts +0 -11
  129. package/dist/perception/graph-store.js +0 -33
  130. package/dist/perception/graph-store.js.map +1 -1
  131. package/dist/perception/index.d.ts +1 -1
  132. package/dist/perception/index.js +1 -2
  133. package/dist/perception/index.js.map +1 -1
  134. package/dist/plugins/commands.d.ts +38 -0
  135. package/dist/plugins/commands.js +134 -0
  136. package/dist/plugins/commands.js.map +1 -0
  137. package/dist/plugins/frontmatter.d.ts +14 -0
  138. package/dist/plugins/frontmatter.js +78 -0
  139. package/dist/plugins/frontmatter.js.map +1 -0
  140. package/dist/plugins/hooks.d.ts +12 -0
  141. package/dist/plugins/hooks.js +148 -0
  142. package/dist/plugins/hooks.js.map +1 -0
  143. package/dist/plugins/loader.d.ts +7 -0
  144. package/dist/plugins/loader.js +255 -0
  145. package/dist/plugins/loader.js.map +1 -0
  146. package/dist/plugins/market.d.ts +23 -0
  147. package/dist/plugins/market.js +281 -0
  148. package/dist/plugins/market.js.map +1 -0
  149. package/dist/plugins/types.d.ts +90 -0
  150. package/dist/plugins/types.js +20 -0
  151. package/dist/plugins/types.js.map +1 -0
  152. package/dist/providers/anthropic-oauth-draft.d.ts +119 -0
  153. package/dist/providers/anthropic-oauth-draft.js +414 -0
  154. package/dist/providers/anthropic-oauth-draft.js.map +1 -0
  155. package/dist/providers/anthropic.d.ts +4 -1
  156. package/dist/providers/anthropic.js +35 -7
  157. package/dist/providers/anthropic.js.map +1 -1
  158. package/dist/providers/factory.d.ts +43 -19
  159. package/dist/providers/factory.js +183 -129
  160. package/dist/providers/factory.js.map +1 -1
  161. package/dist/providers/google.js +9 -22
  162. package/dist/providers/google.js.map +1 -1
  163. package/dist/providers/live-models.d.ts +23 -0
  164. package/dist/providers/live-models.js +145 -0
  165. package/dist/providers/live-models.js.map +1 -0
  166. package/dist/providers/openai-compatible.d.ts +1 -12
  167. package/dist/providers/openai-compatible.js +71 -108
  168. package/dist/providers/openai-compatible.js.map +1 -1
  169. package/dist/providers/resilient-factory.js +1 -1
  170. package/dist/providers/resilient-factory.js.map +1 -1
  171. package/dist/repl/queue.d.ts +22 -0
  172. package/dist/repl/queue.js +165 -0
  173. package/dist/repl/queue.js.map +1 -0
  174. package/dist/repl/side-channel.d.ts +23 -0
  175. package/dist/repl/side-channel.js +63 -0
  176. package/dist/repl/side-channel.js.map +1 -0
  177. package/dist/research/council.d.ts +2 -0
  178. package/dist/research/council.js +29 -4
  179. package/dist/research/council.js.map +1 -1
  180. package/dist/ruby/alternator.d.ts +11 -30
  181. package/dist/ruby/alternator.js +59 -38
  182. package/dist/ruby/alternator.js.map +1 -1
  183. package/dist/ruby/episode-capture.d.ts +0 -10
  184. package/dist/ruby/episode-capture.js +0 -33
  185. package/dist/ruby/episode-capture.js.map +1 -1
  186. package/dist/ruby/types.js +1 -1
  187. package/dist/ruby/types.js.map +1 -1
  188. package/dist/safety/path-jail.d.ts +17 -0
  189. package/dist/safety/path-jail.js +100 -0
  190. package/dist/safety/path-jail.js.map +1 -0
  191. package/dist/safety/permissions.d.ts +12 -21
  192. package/dist/safety/permissions.js +81 -216
  193. package/dist/safety/permissions.js.map +1 -1
  194. package/dist/safety/ssrf.d.ts +29 -0
  195. package/dist/safety/ssrf.js +185 -0
  196. package/dist/safety/ssrf.js.map +1 -0
  197. package/dist/server/index.js +64 -10
  198. package/dist/server/index.js.map +1 -1
  199. package/dist/setup/first-run.d.ts +0 -24
  200. package/dist/setup/first-run.js +10 -344
  201. package/dist/setup/first-run.js.map +1 -1
  202. package/dist/setup/key-store.d.ts +12 -0
  203. package/dist/setup/key-store.js +108 -0
  204. package/dist/setup/key-store.js.map +1 -0
  205. package/dist/setup/provider-registry.js +31 -18
  206. package/dist/setup/provider-registry.js.map +1 -1
  207. package/dist/setup/provider-test.d.ts +7 -0
  208. package/dist/setup/provider-test.js +73 -10
  209. package/dist/setup/provider-test.js.map +1 -1
  210. package/dist/setup/provider-wizard.d.ts +4 -1
  211. package/dist/setup/provider-wizard.js +92 -26
  212. package/dist/setup/provider-wizard.js.map +1 -1
  213. package/dist/setup/xiaomi.d.ts +1 -1
  214. package/dist/setup/xiaomi.js +3 -3
  215. package/dist/setup/xiaomi.js.map +1 -1
  216. package/dist/tools/browser.js +14 -10
  217. package/dist/tools/browser.js.map +1 -1
  218. package/dist/tools/clipboard.js +7 -1
  219. package/dist/tools/clipboard.js.map +1 -1
  220. package/dist/tools/dictate.d.ts +60 -0
  221. package/dist/tools/dictate.js +983 -0
  222. package/dist/tools/dictate.js.map +1 -0
  223. package/dist/tools/dictate_patched.js +1057 -0
  224. package/dist/tools/edit-file.js +10 -2
  225. package/dist/tools/edit-file.js.map +1 -1
  226. package/dist/tools/http-request.js +14 -4
  227. package/dist/tools/http-request.js.map +1 -1
  228. package/dist/tools/image-read.js +34 -2
  229. package/dist/tools/image-read.js.map +1 -1
  230. package/dist/tools/index.d.ts +16 -1
  231. package/dist/tools/index.js +59 -8
  232. package/dist/tools/index.js.map +1 -1
  233. package/dist/tools/mcp.js +12 -0
  234. package/dist/tools/mcp.js.map +1 -1
  235. package/dist/tools/read-file.js +10 -14
  236. package/dist/tools/read-file.js.map +1 -1
  237. package/dist/tools/telegram-audio-policy.d.ts +30 -0
  238. package/dist/tools/telegram-audio-policy.js +49 -0
  239. package/dist/tools/telegram-audio-policy.js.map +1 -0
  240. package/dist/tools/telegram-bot.js +1136 -537
  241. package/dist/tools/telegram-bot.js.map +1 -1
  242. package/dist/tools/telegram-voice.d.ts +18 -5
  243. package/dist/tools/telegram-voice.js +54 -12
  244. package/dist/tools/telegram-voice.js.map +1 -1
  245. package/dist/tools/telegram.d.ts +3 -3
  246. package/dist/tools/telegram.js +5 -84
  247. package/dist/tools/telegram.js.map +1 -1
  248. package/dist/tools/tools.d.ts +2 -14
  249. package/dist/tools/tools.js +79 -144
  250. package/dist/tools/tools.js.map +1 -1
  251. package/dist/tools/web-fetch.js +39 -7
  252. package/dist/tools/web-fetch.js.map +1 -1
  253. package/dist/tools/web-search.d.ts +0 -20
  254. package/dist/tools/web-search.js +40 -137
  255. package/dist/tools/web-search.js.map +1 -1
  256. package/dist/util/errors.js +1 -15
  257. package/dist/util/errors.js.map +1 -1
  258. package/dist/util/json-repair.d.ts +11 -0
  259. package/dist/util/json-repair.js +31 -0
  260. package/dist/util/json-repair.js.map +1 -0
  261. package/dist/util/rate-limiter.js +2 -8
  262. package/dist/util/rate-limiter.js.map +1 -1
  263. package/dist/viz/index.js +793 -792
  264. package/dist/viz/index.js.map +1 -1
  265. package/package.json +12 -32
package/dist/cli/index.js CHANGED
@@ -43,17 +43,25 @@ const fs = __importStar(require("fs"));
43
43
  const minimist_1 = __importDefault(require("minimist"));
44
44
  const chalk_1 = __importDefault(require("chalk"));
45
45
  const factory_js_1 = require("../providers/factory.js");
46
+ const live_models_js_1 = require("../providers/live-models.js");
47
+ void (0, live_models_js_1.refreshLiveModels)().catch(() => { }); // fire-and-forget at module load — see comment history for why this isn't awaited
48
+ process.on('unhandledRejection', (reason) => {
49
+ console.error(chalk_1.default.hex('#b15439')(' \u2717 Unhandled rejection: ' + String(reason)));
50
+ });
46
51
  const resilient_factory_js_1 = require("../providers/resilient-factory.js");
47
52
  const context_js_1 = require("../agent/context.js");
48
53
  const index_js_1 = require("../viz/index.js");
49
54
  const loop_js_1 = require("../agent/loop.js");
50
55
  const permissions_js_1 = require("../safety/permissions.js");
51
56
  const display_js_1 = require("./display.js");
57
+ const tui_js_1 = require("./tui.js");
52
58
  const index_js_2 = require("../server/index.js");
53
59
  const project_config_js_1 = require("../config/project-config.js");
60
+ const package_json_1 = __importDefault(require("../../package.json"));
54
61
  const defaults_js_1 = require("../config/defaults.js");
55
62
  const session_store_js_1 = require("../agent/session-store.js");
56
63
  const global_config_js_1 = require("../setup/global-config.js");
64
+ const key_store_js_1 = require("../setup/key-store.js");
57
65
  const first_run_js_1 = require("../setup/first-run.js");
58
66
  const provider_wizard_js_1 = require("../setup/provider-wizard.js");
59
67
  const index_js_3 = require("../orchestration/index.js");
@@ -63,21 +71,16 @@ const proposer_js_1 = require("../harness/proposer.js");
63
71
  const engine_js_1 = require("../workflows/engine.js");
64
72
  const engine_js_2 = require("../architect/engine.js");
65
73
  const diamond_js_1 = require("./diamond.js");
66
- const episode_capture_js_1 = require("../ruby/episode-capture.js");
67
- const model_selector_js_1 = require("../ruby/model-selector.js");
68
- const stats_js_1 = require("../ruby/stats.js");
69
- const alternator_js_1 = require("../ruby/alternator.js");
70
- const types_js_1 = require("../ruby/types.js");
71
- const load_env_js_1 = require("../util/load-env.js");
72
- const factory_js_2 = require("../providers/factory.js");
73
- // Load API keys from ~/.hermes/.env, ~/.config/aura-code/.env, and project .env
74
- (0, load_env_js_1.bootstrapAuraEnv)(process.cwd());
74
+ const model_select_js_1 = require("./model-select.js");
75
+ const context_health_js_1 = require("./context-health.js");
76
+ const index_js_5 = require("../doctor/index.js");
77
+ const help_data_js_1 = require("./help-data.js");
75
78
  // ─────────────────────────────────────────────────────────────────────────────
76
79
  // Parse args
77
80
  // ─────────────────────────────────────────────────────────────────────────────
78
81
  const argv = (0, minimist_1.default)(process.argv.slice(2), {
79
82
  string: ['model', 'm', 'api-key', 'base-url', 'mode', 'cwd', 'rate-limit-rpm', 'rate-limit-tpm', 'max-retries', 'max-verify-retries', 'max-turns', 'fallback', 'resume', 'chat-id', 'profile', 'test-command', 'workflow', 'resume-workflow', 'workflow-name', 'apply-harness', 'blueprint', 'build'],
80
- boolean: ['help', 'h', 'version', 'v', 'auto', 'readonly', 'models', 'no-session', 'no-setup', 'reset-setup', 'setup', 'orchestrate', 'plan', 'architect', 'list-sessions', 'new-session', 'verify', 'analyze', 'stats', 'workflows', 'propose-harness', 'blueprints', 'once'],
83
+ boolean: ['help', 'h', 'version', 'v', 'auto', 'readonly', 'models', 'no-session', 'no-setup', 'reset-setup', 'orchestrate', 'plan', 'architect', 'list-sessions', 'new-session', 'verify', 'analyze', 'workflows', 'propose-harness', 'blueprints', 'moa', 'doctor'],
81
84
  alias: { m: 'model', h: 'help', v: 'version' },
82
85
  default: {
83
86
  model: process.env.AURA_MODEL,
@@ -94,6 +97,9 @@ const cliMaxRetries = num(argv['max-retries']) ?? num(process.env.AURA_MAX_RETRI
94
97
  const cliMaxVerifyRetries = num(argv['max-verify-retries']);
95
98
  const cliMaxTurns = num(argv['max-turns']);
96
99
  const cliVerify = argv.verify === true;
100
+ // Voice output: speak task summaries aloud. Enabled by --speak or AURA_SPEAK=1;
101
+ // toggled at runtime in the REPL with :speak. Mutable so :speak can flip it.
102
+ let speakEnabled = argv.speak === true || process.env.AURA_SPEAK === '1';
97
103
  const cliProfile = typeof argv.profile === 'string' ? argv.profile : undefined;
98
104
  const cliTestCommand = typeof argv['test-command'] === 'string' ? argv['test-command'] : undefined;
99
105
  const cliRpm = num(argv['rate-limit-rpm']) ?? num(process.env.AURA_API_RPM);
@@ -121,21 +127,21 @@ if (argv.models) {
121
127
  return acc;
122
128
  }, {});
123
129
  for (const [provider, models] of Object.entries(byProvider)) {
124
- console.log(chalk_1.default.hex('#8a7768')(` ${provider}`));
130
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${provider}`));
125
131
  for (const m of models) {
126
- console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex('#4e3d30')(m.speed)}`);
132
+ console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(m.speed)}`);
127
133
  }
128
134
  }
129
- console.log(chalk_1.default.hex('#4e3d30')('\n Use --model <id> or set AURA_MODEL env var'));
130
- console.log(chalk_1.default.hex('#4e3d30')(' For Ollama: --model ollama/llama3.2'));
131
- console.log(chalk_1.default.hex('#4e3d30')(' For OpenRouter: --model openrouter/<provider>/<name>\n'));
135
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)('\n Use --model <id> or set AURA_MODEL env var'));
136
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' For Ollama: --model ollama/llama3.2'));
137
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' For OpenRouter: --model openrouter/<provider>/<name>\n'));
132
138
  process.exit(0);
133
139
  }
134
140
  if (argv['list-sessions']) {
135
141
  const root = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
136
142
  const sessions = session_store_js_1.sessionStore.listSessions(root);
137
143
  if (sessions.length === 0) {
138
- console.log(chalk_1.default.hex('#8a7768')('\n No saved sessions for this project.\n'));
144
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved sessions for this project.\n'));
139
145
  }
140
146
  else {
141
147
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved sessions:\n'));
@@ -143,8 +149,8 @@ if (argv['list-sessions']) {
143
149
  const updated = new Date(s.updatedAt).toLocaleString();
144
150
  const turns = Math.floor(s.history.length / 2);
145
151
  console.log(` ${chalk_1.default.hex('#cc785c')(s.id.padEnd(20))} ` +
146
- `${chalk_1.default.hex('#ede0cc')(s.title.slice(0, 45).padEnd(46))} ` +
147
- `${chalk_1.default.hex('#4e3d30')(`${turns}t · ${updated}`)}`);
152
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(s.title.slice(0, 45).padEnd(46))} ` +
153
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${turns}t · ${updated}`)}`);
148
154
  }
149
155
  console.log();
150
156
  }
@@ -154,53 +160,39 @@ if (argv.analyze) {
154
160
  const report = (0, weakness_miner_js_1.mineWeaknesses)();
155
161
  const outPath = (0, weakness_miner_js_1.saveReport)(report);
156
162
  console.log(chalk_1.default.hex('#cc785c').bold('\n Weakness Analysis Report\n'));
157
- console.log(chalk_1.default.hex('#8a7768')(` Sessions analyzed: ${report.sessionsAnalyzed}`));
158
- console.log(chalk_1.default.hex('#8a7768')(` Report saved to: ${outPath}\n`));
163
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Sessions analyzed: ${report.sessionsAnalyzed}`));
164
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Report saved to: ${outPath}\n`));
159
165
  if (report.patterns.length === 0) {
160
166
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ No recurring weakness patterns detected. Agent behavior looks healthy.\n'));
161
167
  }
162
168
  else {
163
169
  for (const p of report.patterns) {
164
170
  console.log(chalk_1.default.hex('#b15439').bold(` ✗ ${p.pattern} (${p.frequency} occurrences)`));
165
- console.log(chalk_1.default.hex('#8a7768')(` ${p.description}`));
171
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${p.description}`));
166
172
  if (p.occurrences[0]) {
167
- console.log(chalk_1.default.hex('#4e3d30')(` Example task: "${p.occurrences[0].exampleTask.slice(0, 80)}"`));
168
- console.log(chalk_1.default.hex('#4e3d30')(` Example failure: ${p.occurrences[0].exampleFailure.slice(0, 100)}`));
173
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Example task: "${p.occurrences[0].exampleTask.slice(0, 80)}"`));
174
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Example failure: ${p.occurrences[0].exampleFailure.slice(0, 100)}`));
169
175
  }
170
176
  console.log(chalk_1.default.hex('#cc785c')(` Suggestion: ${p.promptPatch.slice(0, 120)}...`));
171
177
  console.log();
172
178
  }
173
- console.log(chalk_1.default.hex('#8a7768')(` ${report.summary}\n`));
179
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${report.summary}\n`));
174
180
  }
175
181
  process.exit(0);
176
182
  }
177
- if (argv.stats) {
178
- (async () => {
179
- const root = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
180
- const episodes = await (0, episode_capture_js_1.loadEpisodes)(root);
181
- const output = (0, stats_js_1.formatStats)(episodes);
182
- // Print each line, highlighting the header
183
- for (const line of output.split('\n')) {
184
- if (line.trim() === 'Aura Stats') {
185
- console.log(chalk_1.default.hex('#cc785c').bold(line));
186
- }
187
- else if (line.includes(':')) {
188
- const colonIdx = line.indexOf(':');
189
- const label = line.slice(0, colonIdx + 1);
190
- const value = line.slice(colonIdx + 1);
191
- console.log(chalk_1.default.hex('#8a7768')(label) + chalk_1.default.hex('#ede0cc')(value));
192
- }
193
- else {
194
- console.log(line);
195
- }
196
- }
197
- process.exit(0);
198
- })();
183
+ if (argv.doctor === true) {
184
+ const cwd = typeof argv.cwd === 'string' ? path.resolve(argv.cwd) : process.cwd();
185
+ const fix = process.argv.includes('--fix');
186
+ const offline = process.argv.includes('--offline');
187
+ (0, index_js_5.runDoctor)({ projectRoot: cwd, fix, offline }).then(report => {
188
+ console.log((0, index_js_5.formatDoctorReport)(report));
189
+ process.exit(report.summary.error > 0 ? 1 : 0);
190
+ }).catch(e => { console.error('Doctor error:', String(e)); process.exit(1); });
199
191
  }
200
192
  if (argv['propose-harness']) {
201
193
  // Ensure a weakness report exists — mine if needed
202
194
  if (!fs.existsSync((0, weakness_miner_js_1.reportPath)())) {
203
- console.log(chalk_1.default.hex('#8a7768')('\n No weakness report found — mining sessions first...\n'));
195
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No weakness report found — mining sessions first...\n'));
204
196
  const report = (0, weakness_miner_js_1.mineWeaknesses)();
205
197
  (0, weakness_miner_js_1.saveReport)(report);
206
198
  }
@@ -212,13 +204,13 @@ if (argv['propose-harness']) {
212
204
  console.log(chalk_1.default.hex('#cc785c').bold('\n Harness Proposals\n'));
213
205
  for (const p of proposals) {
214
206
  console.log(chalk_1.default.hex('#cc785c')(` ${p.id}`));
215
- console.log(chalk_1.default.hex('#8a7768')(` Pattern: ${p.pattern} (${p.description.slice(0, 60)})`));
216
- console.log(chalk_1.default.hex('#8a7768')(` Section: ${p.targetSection}`));
217
- console.log(chalk_1.default.hex('#4e3d30')(` Patch: ${p.patchText.slice(0, 80)}...`));
207
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Pattern: ${p.pattern} (${p.description.slice(0, 60)})`));
208
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Section: ${p.targetSection}`));
209
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Patch: ${p.patchText.slice(0, 80)}...`));
218
210
  console.log();
219
211
  }
220
212
  console.log(chalk_1.default.hex('#5a9e6e')(` ${proposals.length} proposal(s) saved to ~/.aura/harness/proposals/`));
221
- console.log(chalk_1.default.hex('#8a7768')(' Apply with: ruby --apply-harness <id>\n'));
213
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Apply with: ruby --apply-harness <id>\n'));
222
214
  }
223
215
  process.exit(0);
224
216
  }
@@ -239,7 +231,7 @@ if (argv.workflows) {
239
231
  (async () => {
240
232
  const workflows = await (0, engine_js_1.listWorkflows)();
241
233
  if (workflows.length === 0) {
242
- console.log(chalk_1.default.hex('#8a7768')('\n No saved workflows.\n'));
234
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved workflows.\n'));
243
235
  }
244
236
  else {
245
237
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved workflows:\n'));
@@ -249,9 +241,9 @@ if (argv.workflows) {
249
241
  const totalSteps = ws.definition.steps.length;
250
242
  const statusColor = ws.status === 'done' ? '#5a9e6e' : ws.status === 'failed' ? '#b15439' : '#cc785c';
251
243
  console.log(` ${chalk_1.default.hex('#cc785c')(ws.definition.id.padEnd(24))} ` +
252
- `${chalk_1.default.hex('#ede0cc')(ws.definition.name.slice(0, 36).padEnd(37))} ` +
244
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(ws.definition.name.slice(0, 36).padEnd(37))} ` +
253
245
  `${chalk_1.default.hex(statusColor)(ws.status.padEnd(8))} ` +
254
- `${chalk_1.default.hex('#4e3d30')(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
246
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
255
247
  }
256
248
  console.log();
257
249
  }
@@ -263,7 +255,7 @@ if (argv.blueprints) {
263
255
  (async () => {
264
256
  const bps = await (0, engine_js_2.listBlueprints)();
265
257
  if (bps.length === 0) {
266
- console.log(chalk_1.default.hex('#8a7768')('\n No saved blueprints.\n'));
258
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved blueprints.\n'));
267
259
  }
268
260
  else {
269
261
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved blueprints:\n'));
@@ -274,8 +266,8 @@ if (argv.blueprints) {
274
266
  const statusColor = bp.status === 'complete' ? '#5a9e6e' : bp.status === 'building' ? '#cc9e5c' : '#cc785c';
275
267
  console.log(` ${chalk_1.default.hex(statusColor)(bp.status.padEnd(10))} ` +
276
268
  `${chalk_1.default.hex('#cc785c')(bp.id.slice(0, 16).padEnd(18))} ` +
277
- `${chalk_1.default.hex('#ede0cc')(bp.task.slice(0, 40).padEnd(41))} ` +
278
- `${chalk_1.default.hex('#4e3d30')(`${builtCount}/${totalFiles} files · ${created}`)}`);
269
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(bp.task.slice(0, 40).padEnd(41))} ` +
270
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${builtCount}/${totalFiles} files · ${created}`)}`);
279
271
  }
280
272
  console.log();
281
273
  }
@@ -292,53 +284,53 @@ if (typeof argv.blueprint === 'string' && argv.blueprint) {
292
284
  }
293
285
  const statusColor = bp.status === 'complete' ? '#5a9e6e' : bp.status === 'building' ? '#cc9e5c' : '#cc785c';
294
286
  console.log(chalk_1.default.hex('#cc785c').bold('\n Blueprint\n'));
295
- console.log(` ${chalk_1.default.hex('#8a7768')('ID:')} ${chalk_1.default.hex('#cc785c')(bp.id)}`);
296
- console.log(` ${chalk_1.default.hex('#8a7768')('Task:')} ${chalk_1.default.hex('#ede0cc')(bp.task)}`);
297
- console.log(` ${chalk_1.default.hex('#8a7768')('Status:')} ${chalk_1.default.hex(statusColor)(bp.status)}`);
298
- console.log(` ${chalk_1.default.hex('#8a7768')('Steps:')} ${bp.estimatedSteps}`);
299
- console.log(` ${chalk_1.default.hex('#8a7768')('Created:')} ${new Date(bp.createdAt).toLocaleString()}`);
287
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('ID:')} ${chalk_1.default.hex('#cc785c')(bp.id)}`);
288
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('Task:')} ${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(bp.task)}`);
289
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('Status:')} ${chalk_1.default.hex(statusColor)(bp.status)}`);
290
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('Steps:')} ${bp.estimatedSteps}`);
291
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('Created:')} ${new Date(bp.createdAt).toLocaleString()}`);
300
292
  if (bp.builtAt)
301
- console.log(` ${chalk_1.default.hex('#8a7768')('Built:')} ${new Date(bp.builtAt).toLocaleString()}`);
293
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('Built:')} ${new Date(bp.builtAt).toLocaleString()}`);
302
294
  if (bp.files.length > 0) {
303
295
  console.log(chalk_1.default.hex('#cc785c').bold('\n Files:\n'));
304
296
  for (const f of bp.files) {
305
- const fileStatusColor = f.status === 'built' ? '#5a9e6e' : f.status === 'skipped' ? '#8a7768' : '#cc785c';
297
+ const fileStatusColor = f.status === 'built' ? '#5a9e6e' : f.status === 'skipped' ? '#a68a2a' : '#cc785c';
306
298
  console.log(` ${chalk_1.default.hex(fileStatusColor)(f.status.padEnd(8))} ${chalk_1.default.hex('#cc785c')(f.path)}`);
307
- console.log(` ${chalk_1.default.hex('#8a7768')(f.purpose)}`);
299
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(f.purpose)}`);
308
300
  if (f.exports.length > 0) {
309
- console.log(` ${chalk_1.default.hex('#4e3d30')(`exports: ${f.exports.join(', ')}`)}`);
301
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`exports: ${f.exports.join(', ')}`)}`);
310
302
  }
311
303
  if (f.interfaces.length > 0) {
312
- console.log(` ${chalk_1.default.hex('#4e3d30')(`interfaces: ${f.interfaces.join(', ')}`)}`);
304
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`interfaces: ${f.interfaces.join(', ')}`)}`);
313
305
  }
314
306
  }
315
307
  }
316
308
  if (bp.dataModels.length > 0) {
317
309
  console.log(chalk_1.default.hex('#cc785c').bold('\n Data Models:\n'));
318
310
  for (const dm of bp.dataModels) {
319
- console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex('#8a7768')(dm.description)}`);
311
+ console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(dm.description)}`);
320
312
  for (const field of dm.fields) {
321
- console.log(` ${chalk_1.default.hex('#4e3d30')(field)}`);
313
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(field)}`);
322
314
  }
323
315
  }
324
316
  }
325
317
  if (bp.dependencies.length > 0) {
326
318
  console.log(chalk_1.default.hex('#cc785c').bold('\n Dependencies:\n'));
327
319
  for (const dep of bp.dependencies) {
328
- console.log(` ${chalk_1.default.hex('#4e3d30')(dep)}`);
320
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(dep)}`);
329
321
  }
330
322
  }
331
323
  if (bp.risks.length > 0) {
332
324
  console.log(chalk_1.default.hex('#b15439').bold('\n Risks:\n'));
333
325
  for (const risk of bp.risks) {
334
- console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex('#8a7768')(risk)}`);
326
+ console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(risk)}`);
335
327
  }
336
328
  }
337
329
  if (bp.deviations.length > 0) {
338
330
  console.log(chalk_1.default.hex('#cc9e5c').bold('\n Deviations:\n'));
339
331
  for (const dev of bp.deviations) {
340
332
  const time = new Date(dev.recordedAt).toLocaleString();
341
- console.log(` ${chalk_1.default.hex('#cc9e5c')('→')} ${chalk_1.default.hex('#8a7768')(dev.description)} ${chalk_1.default.hex('#4e3d30')(`(${time})`)}`);
333
+ console.log(` ${chalk_1.default.hex('#cc9e5c')('→')} ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(dev.description)} ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`(${time})`)}`);
342
334
  }
343
335
  }
344
336
  console.log();
@@ -353,7 +345,7 @@ if (argv.help) {
353
345
  // When run with no args + a TTY or piped input, fall through to the REPL/wizard.
354
346
  // Skip this gate when --reset-setup is set (the wizard should fire even if env
355
347
  // vars make needsWizard() return false).
356
- if (argv._.length === 0 && !argv.interactive && process.stdin.isTTY !== true && !argv['reset-setup']) {
348
+ if (argv._.length === 0 && !argv.interactive && !argv.doctor && process.stdin.isTTY !== true && !argv['reset-setup']) {
357
349
  if (!(0, first_run_js_1.needsWizard)({})) {
358
350
  printHelp();
359
351
  process.exit(0);
@@ -364,21 +356,39 @@ if (argv._.length === 0 && !argv.interactive && process.stdin.isTTY !== true &&
364
356
  // ─────────────────────────────────────────────────────────────────────────────
365
357
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
366
358
  const fileConfig = (0, project_config_js_1.loadProjectConfig)(cwd);
367
- // Pull global config (saved by the first-run wizard) so the user doesn't have
368
- // to re-set their provider on every run.
359
+ // No TLS patching needed the DigiCert root CA used by the MiMo endpoint
360
+ // is already trusted by Node's built-in certificate store.
361
+ // Load persisted API keys (~/.aura/keys.json) into process.env before any
362
+ // provider is built — so keys set once with :apikey survive across sessions
363
+ // and every provider sees them, without depending on shell rc files. Real
364
+ // environment values are never overridden.
365
+ (0, key_store_js_1.loadKeysIntoEnv)();
366
+ // Pull global config (saved by the setup wizard) so the user doesn't have
367
+ // to re-set their provider on every run. provider.json is the fuller record
368
+ // from the same wizard save — it also carries the API key and base URL, so a
369
+ // choice made last session works next session without any env vars.
369
370
  const globalCfg = (0, global_config_js_1.loadGlobalConfig)();
370
- // Effective model = CLI > AURA_MODEL env > global wizard > .aura.json project default
371
+ const savedProvider = (0, provider_wizard_js_1.loadProviderConfig)();
372
+ // Effective model = CLI > AURA_MODEL env > .aura.json > global config > undefined
371
373
  const cliModel = typeof argv.model === 'string' ? argv.model : undefined;
372
- // Tracks whether the user has explicitly chosen a model (--model flag OR :model REPL command).
373
- // The RubyAlternator must not override an explicit user choice.
374
- let userSetModel = !!cliModel;
375
- const effectiveModel = cliModel
376
- ?? process.env.AURA_MODEL
377
- ?? globalCfg?.defaultModel
378
- ?? fileConfig.model;
379
- // Effective base URL = CLI > .aura.json > global config > undefined
374
+ const effectiveModel = cliModel ?? fileConfig.model ?? globalCfg?.defaultModel
375
+ ?? savedProvider?.model ?? process.env.AURA_MODEL;
376
+ // The saved provider record only applies when we're actually running the
377
+ // model it was saved with — its baseUrl/apiKey belong to that provider.
378
+ const savedProviderApplies = !!savedProvider && effectiveModel === savedProvider.model;
379
+ // Effective base URL = CLI > .aura.json > global config > undefined.
380
+ // CRITICAL: the global config's baseUrl belongs to the provider the wizard
381
+ // configured it must NOT be forced onto a DIFFERENT model. Otherwise picking
382
+ // `-m deepseek/...` while the global default is a GLM/Z.ai endpoint sends the
383
+ // DeepSeek key to Z.ai → 401. So only inherit globalCfg.baseUrl when the
384
+ // effective model is actually that global default model (same provider).
380
385
  const cliBaseUrl = typeof argv['base-url'] === 'string' ? argv['base-url'] : undefined;
381
- const effectiveBaseUrl = cliBaseUrl ?? fileConfig.baseUrl ?? globalCfg?.baseUrl;
386
+ const globalBaseUrlApplies = !!globalCfg?.baseUrl &&
387
+ !!globalCfg?.defaultModel &&
388
+ effectiveModel === globalCfg.defaultModel;
389
+ const effectiveBaseUrl = cliBaseUrl ?? fileConfig.baseUrl
390
+ ?? (globalBaseUrlApplies ? globalCfg.baseUrl : undefined)
391
+ ?? (savedProviderApplies ? savedProvider.baseUrl : undefined);
382
392
  const resolved = (0, project_config_js_1.resolveConfig)({ ...fileConfig, model: effectiveModel, baseUrl: effectiveBaseUrl }, {
383
393
  model: cliModel,
384
394
  baseUrl: cliBaseUrl,
@@ -397,7 +407,11 @@ const permissionLevel = resolved.mode;
397
407
  const runtimeConfig = {
398
408
  model: resolved.model,
399
409
  baseUrl: resolved.baseUrl,
400
- apiKey: typeof argv['api-key'] === 'string' ? argv['api-key'] : undefined,
410
+ // --api-key wins; otherwise the key the wizard saved with this model last
411
+ // session. Env vars only apply when neither is present (factory fallback).
412
+ apiKey: typeof argv['api-key'] === 'string'
413
+ ? argv['api-key']
414
+ : (savedProviderApplies ? savedProvider.apiKey : undefined),
401
415
  };
402
416
  // ── Profile: local → Ollama defaults ─────────────────────────────────────────
403
417
  if (cliProfile === 'local') {
@@ -407,18 +421,12 @@ if (cliProfile === 'local') {
407
421
  }
408
422
  }
409
423
  function buildProvider(display) {
410
- // Use runtimeConfig.model (mutable via :model command) falling back to resolved.model.
411
- const model = runtimeConfig.model ?? resolved.model;
412
- const transport = (0, factory_js_2.resolveProviderTransport)(model, {
413
- baseUrl: runtimeConfig.baseUrl ?? resolved.baseUrl ?? undefined,
414
- apiKey: runtimeConfig.apiKey,
415
- });
416
- // Keep resolved in sync so external reads (e.g. header display) reflect the switch.
417
- resolved.model = model;
424
+ // Caller guarantees resolved.model is set (guarded in main()).
425
+ const model = resolved.model;
418
426
  return (0, resilient_factory_js_1.createResilientProvider)({
419
427
  model,
420
- apiKey: transport.apiKey,
421
- baseUrl: transport.baseUrl,
428
+ apiKey: runtimeConfig.apiKey,
429
+ baseUrl: runtimeConfig.baseUrl ?? undefined,
422
430
  }, {
423
431
  rpm: resolved.rateLimitRpm,
424
432
  tpm: resolved.rateLimitTpm,
@@ -430,9 +438,6 @@ function buildProvider(display) {
430
438
  // Main
431
439
  // ─────────────────────────────────────────────────────────────────────────────
432
440
  async function main() {
433
- (0, diamond_js_1.renderDiamond)();
434
- await new Promise(r => setTimeout(r, 1500));
435
- process.stdout.write('\x1b[2J\x1b[H');
436
441
  const display = (0, display_js_1.createTerminalDisplay)();
437
442
  // ── First-run wizard ───────────────────────────────────────────────────────
438
443
  // Skip if: --no-setup flag, --api-key on CLI, env var set, or global config exists.
@@ -452,50 +457,45 @@ async function main() {
452
457
  const cliModel = typeof argv.model === 'string' ? argv.model : undefined;
453
458
  const skipSetup = argv['no-setup'] === true || argv.help === true || argv.h === true || argv.models === true || argv.version === true || argv.v === true;
454
459
  const resetSetup = argv['reset-setup'] === true;
455
- const cliSetup = argv['setup'] === true;
456
460
  if (resetSetup) {
457
- // Wipe global config so the wizard fires unconditionally.
461
+ // Wipe both saved configs so the wizard fires unconditionally and no
462
+ // stale provider record (baseUrl/apiKey) survives the reset.
458
463
  try {
459
464
  fs.unlinkSync((0, global_config_js_1.globalConfigPath)());
460
465
  }
461
466
  catch { /* not present */ }
462
- }
463
- // --setup flag: run provider wizard before entering REPL
464
- if (cliSetup && !skipSetup) {
465
- const cfg = await (0, provider_wizard_js_1.runProviderWizard)();
466
- if (cfg) {
467
- resolved.model = cfg.model;
468
- resolved.baseUrl = cfg.baseUrl;
469
- runtimeConfig.model = cfg.model;
470
- runtimeConfig.baseUrl = cfg.baseUrl;
471
- runtimeConfig.apiKey = cfg.apiKey;
467
+ try {
468
+ fs.unlinkSync(path.join(path.dirname((0, global_config_js_1.globalConfigPath)()), 'provider.json'));
472
469
  }
470
+ catch { /* not present */ }
473
471
  }
474
472
  // When --reset-setup is set, force the wizard to fire (overrides env-var
475
473
  // detection — the user explicitly wants to reconfigure).
476
- const shouldRunWizard = !skipSetup && !cliSetup && isInteractive && (resetSetup || (0, first_run_js_1.needsWizard)({ cliApiKey, cliModel }));
474
+ const shouldRunWizard = !skipSetup && isInteractive && (resetSetup || (0, first_run_js_1.needsWizard)({ cliApiKey, cliModel }));
477
475
  if (shouldRunWizard) {
478
476
  // If stdin is not a TTY and there's nothing piped in, the wizard will
479
477
  // hang. Skip with a helpful message instead.
480
478
  if (process.stdin.isTTY !== true && !process.stdin.readable) {
481
479
  console.error(chalk_1.default.hex('#b15439')('\n ✗ No interactive input available.'));
482
- console.error(chalk_1.default.hex('#8a7768')(' Set an API key env var (e.g. export OPENAI_API_KEY=...)'));
483
- console.error(chalk_1.default.hex('#8a7768')(' or pass --api-key <key> --model <id> on the command line,\n'));
480
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Set an API key env var (e.g. export OPENAI_API_KEY=...)'));
481
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' or pass --api-key <key> --model <id> on the command line,\n'));
484
482
  process.exit(1);
485
483
  }
486
- const cfg = await (0, first_run_js_1.runFirstRunWizard)();
484
+ // Full provider wizard: pick provider + model, detect/enter key, and
485
+ // TEST the connection (URL normalization + response validation) before
486
+ // saving. The choice is persisted and restored on every later run.
487
+ const cfg = await (0, provider_wizard_js_1.runProviderWizard)();
487
488
  if (!cfg) {
488
489
  console.error(chalk_1.default.hex('#b15439')('\n ✗ Setup cancelled. Set an API key env var (e.g. export OPENAI_API_KEY=...) or run with --api-key.\n'));
489
490
  process.exit(1);
490
491
  }
491
- // Re-resolve with the new global config
492
- const fresh = (0, global_config_js_1.loadGlobalConfig)();
493
- if (fresh) {
494
- resolved.model = fresh.defaultModel;
495
- resolved.baseUrl = fresh.baseUrl;
496
- runtimeConfig.model = fresh.defaultModel;
497
- runtimeConfig.baseUrl = fresh.baseUrl;
498
- }
492
+ // Apply the wizard's choice to this session (it already saved to disk).
493
+ resolved.model = cfg.model;
494
+ resolved.baseUrl = cfg.baseUrl || undefined;
495
+ runtimeConfig.model = cfg.model;
496
+ runtimeConfig.baseUrl = cfg.baseUrl || undefined;
497
+ if (cfg.apiKey)
498
+ runtimeConfig.apiKey = cfg.apiKey;
499
499
  }
500
500
  let ctx;
501
501
  try {
@@ -508,9 +508,9 @@ async function main() {
508
508
  // ── Guard: we need a model before we can build a provider ─────────────────
509
509
  if (!resolved.model) {
510
510
  console.error(chalk_1.default.hex('#b15439')('\n ✗ No model configured.'));
511
- console.error(chalk_1.default.hex('#8a7768')(' Run `aura` with no args in a TTY to launch the setup wizard,'));
512
- console.error(chalk_1.default.hex('#8a7768')(' or pass --model <id> --api-key <key> on the command line,'));
513
- console.error(chalk_1.default.hex('#8a7768')(' or set the model in .aura.json (`"model": "..."`).'));
511
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Run `aura` with no args in a TTY to launch the setup wizard,'));
512
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' or pass --model <id> --api-key <key> on the command line,'));
513
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' or set the model in .aura.json (`"model": "..."`).'));
514
514
  process.exit(1);
515
515
  }
516
516
  let provider;
@@ -573,32 +573,20 @@ async function main() {
573
573
  }
574
574
  // Legacy sessionPath kept for single-task one-shot mode
575
575
  const sessionPath = noSession ? undefined : path.join(session_store_js_1.sessionStore.defaultDir(), projectRoot.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80), 'latest.json');
576
- // ── ASCII art banner ────────────────────────────────────────────────────────
577
- const orange = chalk_1.default.hex('#E8771A');
578
- const pink1 = chalk_1.default.hex('#F5715B'); // Smooth transition step
579
- const pink2 = chalk_1.default.hex('#FF6B9D');
580
- const red1 = chalk_1.default.hex('#E64A69'); // Smooth transition step
581
- const red2 = chalk_1.default.hex('#CC2936');
582
- if ((process.stdout.columns ?? 80) < 80) {
583
- console.log(chalk_1.default.hex('#CC2936').bold('AURA CODE'));
584
- }
585
- else {
586
- console.log('');
587
- console.log(orange(' █████╗ ██╗ ██╗██████╗ █████╗ ██████╗ ██████╗ ██████╗ ███████╗'));
588
- console.log(pink1(' ██╔══██╗██║ ██║██╔══██╗██╔══██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝'));
589
- console.log(pink2(' ███████║██║ ██║██████╔╝███████║ ██║ ██║ ██║██║ ██║█████╗ '));
590
- console.log(red1(' ██╔══██║██║ ██║██╔══██╗██╔══██║ ██║ ██║ ██║██║ ██║██╔══╝ '));
591
- console.log(red2(' ██║ ██║╚██████╔╝██║ ██║██║ ██║ ╚██████╗╚██████╔╝██████╔╝███████╗'));
592
- console.log(red2(' ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝'));
593
- console.log('');
594
- console.log(chalk_1.default.hex('#4e3d30')(' "I don\'t try. I verify."'));
595
- console.log('');
596
- }
597
- console.log('');
598
- await new Promise(r => setTimeout(r, 50));
599
- display.header(`Aura — ${ctx.name}`, `${provider.name} · ${runtimeConfig.model} · ${ctx.language} · ${permissionLevel} mode` +
600
- (fileConfig.model ? ` · .aura.json loaded` : '') +
601
- (activeChatId ? ` · chat ${activeChatId}` : ''));
576
+ // ── Startup banner ──────────────────────────────────────────────────────────
577
+ (0, diamond_js_1.renderBanner)({
578
+ version: package_json_1.default.version,
579
+ title: ctx.name,
580
+ provider: provider.name,
581
+ model: runtimeConfig.model,
582
+ language: ctx.language,
583
+ mode: permissionLevel,
584
+ cwd: projectRoot,
585
+ extras: [
586
+ ...(fileConfig.model ? ['.aura.json loaded'] : []),
587
+ ...(activeChatId ? [`chat ${activeChatId}`] : []),
588
+ ],
589
+ });
602
590
  const cumulative = { turns: 0, toolCalls: 0, inputTokens: 0, outputTokens: 0, costUsd: 0 };
603
591
  // ── --build <id>: build from a saved blueprint in dependency order ───────────
604
592
  if (typeof argv.build === 'string' && argv.build) {
@@ -608,8 +596,8 @@ async function main() {
608
596
  process.exit(1);
609
597
  }
610
598
  display.header('Architect Builder', `Building from blueprint: ${bp.id}`);
611
- console.log(chalk_1.default.hex('#8a7768')(` Task: ${bp.task}`));
612
- console.log(chalk_1.default.hex('#8a7768')(` Files: ${bp.files.filter(f => f.status === 'planned').length} to build\n`));
599
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Task: ${bp.task}`));
600
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Files: ${bp.files.filter(f => f.status === 'planned').length} to build\n`));
613
601
  await (0, engine_js_2.updateBlueprintStatus)(bp.id, 'building');
614
602
  // Build files in order — planned files only
615
603
  const plannedFiles = bp.files.filter(f => f.status === 'planned');
@@ -668,7 +656,7 @@ async function main() {
668
656
  const stepTasks = argv._.map(String);
669
657
  if (stepTasks.length === 0) {
670
658
  console.error(chalk_1.default.hex('#b15439')('\n ✗ No step tasks provided.'));
671
- console.error(chalk_1.default.hex('#8a7768')(' Usage: ruby --workflow <name> "step 1" "step 2" ...\n'));
659
+ console.error(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Usage: ruby --workflow <name> "step 1" "step 2" ...\n'));
672
660
  process.exit(1);
673
661
  }
674
662
  const steps = stepTasks.map((task, i) => ({
@@ -708,9 +696,9 @@ async function main() {
708
696
  console.error(chalk_1.default.hex('#b15439').bold(`\n ✗ ${finalState.outcome}\n`));
709
697
  }
710
698
  const totalTokens = finalState.totalTokens ?? 0;
711
- console.log(chalk_1.default.hex('#4e3d30')(` ↳ ${totalTokens.toLocaleString()} tokens · ${finalState.stepStates.length} steps · status: ${finalState.status}`));
699
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ↳ ${totalTokens.toLocaleString()} tokens · ${finalState.stepStates.length} steps · status: ${finalState.status}`));
712
700
  if (finalState.status === 'failed') {
713
- console.log(chalk_1.default.hex('#8a7768')(`\n Resume with: ruby --resume-workflow ${finalState.definition.id}\n`));
701
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Resume with: ruby --resume-workflow ${finalState.definition.id}\n`));
714
702
  process.exit(1);
715
703
  }
716
704
  return;
@@ -753,9 +741,9 @@ async function main() {
753
741
  console.error(chalk_1.default.hex('#b15439').bold(`\n ✗ ${finalState.outcome}\n`));
754
742
  }
755
743
  const totalTokens = finalState.totalTokens ?? 0;
756
- console.log(chalk_1.default.hex('#4e3d30')(` ↳ ${totalTokens.toLocaleString()} tokens · ${finalState.stepStates.length} steps · status: ${finalState.status}`));
744
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ↳ ${totalTokens.toLocaleString()} tokens · ${finalState.stepStates.length} steps · status: ${finalState.status}`));
757
745
  if (finalState.status === 'failed') {
758
- console.log(chalk_1.default.hex('#8a7768')(`\n Resume with: ruby --resume-workflow ${finalState.definition.id}\n`));
746
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Resume with: ruby --resume-workflow ${finalState.definition.id}\n`));
759
747
  process.exit(1);
760
748
  }
761
749
  return;
@@ -763,77 +751,60 @@ async function main() {
763
751
  // ── Single task mode: aura "fix the bug" ──────────────────────────────────────
764
752
  if (argv._.length > 0) {
765
753
  const task = argv._.join(' ');
766
- console.log(chalk_1.default.hex('#8a7768')(`\n Task: ${chalk_1.default.hex('#ede0cc')(task)}\n`));
767
- // --architect: plan-only — decompose and display
754
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Task: ${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(task)}\n`));
755
+ // --architect: plan-only — decompose and display, then exit (no execution)
768
756
  if (argv.architect === true) {
769
757
  await runArchitectPlan(task, provider, ctx, display);
770
- if (argv.once)
771
- return;
772
- return startRepl();
758
+ return;
773
759
  }
774
760
  // --build: full orchestrated build — decompose + execute all specialists
775
761
  // When --build <id> is a string, build from a saved blueprint instead
776
762
  const doOrchestrate = argv.orchestrate === true || argv.build === true;
777
763
  if (doOrchestrate) {
778
764
  await runOrchestratedTask(task, provider, ctx, display, doOrchestrate);
779
- if (argv.once)
780
- return;
781
- return startRepl();
765
+ return;
782
766
  }
783
767
  try {
784
768
  let perception = await (0, index_js_4.loadPerception)(ctx.root);
785
769
  if (!perception || (0, index_js_4.isStale)(perception)) {
786
770
  display.agentThinking();
787
771
  perception = await (0, index_js_4.extractPerception)(ctx.root);
788
- // Persist so the viz dashboard and context summary stay populated
789
- (0, index_js_4.savePerception)(perception).catch(() => { });
790
- (0, index_js_4.saveGraphForViz)(perception).catch(() => { });
791
772
  }
792
773
  const decision = await (0, index_js_3.routeTask)({ provider, context: ctx, task, perception: perception ?? undefined });
793
774
  if (decision.shouldDecompose && decision.confidence > 0.8) {
794
775
  await runOrchestratedTask(task, provider, ctx, display, false, perception ?? undefined);
795
- if (argv.once)
796
- return;
797
- return startRepl();
776
+ return;
798
777
  }
799
778
  }
800
779
  catch {
801
780
  // Router failed — fall through to single agent
802
781
  }
803
- // ── Competence-based model selection ───────────────────────────────────────
804
- // Picks the best-performing model among already-configured, same-family
805
- // options based on task similarity to past episodes. Distinct from
806
- // RubyAlternator below this only chooses among models you've already
807
- // set up; it never tries a free local model first.
808
- // Only when the user has NOT explicitly specified --model or :model
809
- if (!userSetModel && !globalCfg?.defaultModel) {
810
- try {
811
- const availableModelIds = (0, factory_js_1.getAllModels)()
812
- .map(m => m.id)
813
- .filter(id => (0, factory_js_2.isModelConfigured)(id))
814
- .filter(id => (0, factory_js_2.modelProviderFamily)(id) === (0, factory_js_2.modelProviderFamily)(runtimeConfig.model ?? resolved.model));
815
- const suggested = await (0, model_selector_js_1.selectModel)(ctx.root, task, availableModelIds);
816
- if (suggested && suggested !== runtimeConfig.model) {
817
- console.log(chalk_1.default.hex('#4e3d30')(` Using ${suggested} (selected by competence history)\n`));
818
- runtimeConfig.model = suggested;
819
- runtimeConfig.baseUrl = undefined;
820
- runtimeConfig.apiKey = undefined;
821
- // Rebuild provider with the suggested model
822
- provider = buildProvider(display);
782
+ const doVerify = cliVerify || !!fileConfig.verify;
783
+ // --moa: mixture-of-agents Phase 2 only pays off for exploratory-shaped
784
+ // tasks (see docs/MIXTURE_OF_AGENTS.md), so it's gated on both the flag
785
+ // and the task shape. Other shapes fall through to the single-agent path.
786
+ if (argv.moa === true) {
787
+ const { classifyTask } = await Promise.resolve().then(() => __importStar(require('../agent/loop-profile.js')));
788
+ if (classifyTask(task) === 'exploratory') {
789
+ const { runMixtureOfAgents } = await Promise.resolve().then(() => __importStar(require('../agent/mixture.js')));
790
+ const moaResult = await runMixtureOfAgents({ provider, task, context: ctx, display });
791
+ if (activeChatId && !noSession) {
792
+ await session_store_js_1.sessionStore.upsertSession(projectRoot, activeChatId, moaResult.history, activeChatTitle);
823
793
  }
794
+ if (moaResult.success) {
795
+ display.summary(moaResult.summary, moaResult.turns, moaResult.toolCallCount);
796
+ printUsageFooter(display, moaResult.usage, moaResult.costUsd);
797
+ }
798
+ else {
799
+ display.error(moaResult.summary);
800
+ process.exit(1);
801
+ }
802
+ return;
824
803
  }
825
- catch {
826
- // Competence selection is best-effort; never block the user
827
- }
804
+ display.warning('--moa only applies to exploratory tasks (explain/analyze/investigate…) — running the normal single-agent path.');
828
805
  }
829
- const doVerify = cliVerify || !!fileConfig.verify;
830
- const rubyConfig = { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby };
831
- const episodeStart = Date.now();
832
806
  let result;
833
- let alternatorHandledEpisode = false;
834
807
  if (doVerify) {
835
- // Ruby-alternation doesn't support the verification wrapper yet —
836
- // a verified task always runs directly against the selected model.
837
808
  const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
838
809
  const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
839
810
  const testCommand = cliTestCommand ?? fileConfig.testCommand;
@@ -854,20 +825,6 @@ async function main() {
854
825
  });
855
826
  result = wrapperResult.loopResult;
856
827
  }
857
- else if (rubyConfig.enabled) {
858
- const alternator = new alternator_js_1.RubyAlternator({
859
- rubyConfig,
860
- largeModelProvider: provider,
861
- projectRoot: ctx.root,
862
- context: ctx,
863
- display,
864
- permissions,
865
- initialHistory: activeChatHistory,
866
- });
867
- const altResult = await alternator.run(task);
868
- result = altResult.loopResult;
869
- alternatorHandledEpisode = true; // alternator.run() already saved its own episode
870
- }
871
828
  else {
872
829
  result = await (0, loop_js_1.runAgentLoop)({
873
830
  provider, task, context: ctx, permissions, display,
@@ -880,254 +837,302 @@ async function main() {
880
837
  sessionPath,
881
838
  });
882
839
  }
883
- // Persist episode for Ruby Principle training data — skip when
884
- // RubyAlternator already saved its own (richer) episode above, to
885
- // avoid double-counting every task in the stats/dashboard.
886
- if (!alternatorHandledEpisode) {
887
- try {
888
- const { randomBytes } = await Promise.resolve().then(() => __importStar(require('crypto')));
889
- await (0, episode_capture_js_1.saveEpisode)(ctx.root, {
890
- id: randomBytes(4).toString('hex') + '-' + Date.now().toString(36),
891
- timestamp: Date.now(),
892
- task,
893
- projectRoot: ctx.root,
894
- rubyAttempted: false,
895
- rubySucceeded: false,
896
- largeModelUsed: runtimeConfig.model,
897
- largeModelOutput: result.summary,
898
- reviewerApproved: result.success,
899
- tokensUsed: { largeModel: result.usage.totalTokens },
900
- durationMs: Date.now() - episodeStart,
901
- taskCategory: 'other',
902
- });
903
- }
904
- catch {
905
- // Episode recording is best-effort; never block the user
906
- }
907
- }
908
- // Update history so REPL continues the conversation
909
- activeChatHistory = result.history;
910
840
  if (activeChatId && !noSession) {
911
841
  await session_store_js_1.sessionStore.upsertSession(projectRoot, activeChatId, result.history, activeChatTitle);
912
842
  }
843
+ {
844
+ const { recordEpisode } = await Promise.resolve().then(() => __importStar(require('../dream/episode.js')));
845
+ recordEpisode(ctx.root, {
846
+ task,
847
+ model: provider.model,
848
+ success: result.success,
849
+ tokens: (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0),
850
+ durationMs: 0,
851
+ });
852
+ }
913
853
  if (result.success) {
914
854
  display.summary(result.summary, result.turns, result.toolCallCount);
915
- printUsageFooter(display, result.usage, result.costUsd, {
916
- model: runtimeConfig.model ?? undefined,
917
- cumulativeTokens: result.usage.inputTokens + result.usage.outputTokens,
918
- history: result.history,
919
- });
855
+ printUsageFooter(display, result.usage, result.costUsd);
856
+ if (speakEnabled)
857
+ await speakSummary(result.summary);
920
858
  }
921
859
  else {
922
860
  display.error(result.summary);
861
+ process.exit(1);
923
862
  }
924
- if (argv.once)
925
- return;
926
- return startRepl();
927
- }
928
- // ── Interactive REPL mode (no task args) ────────────────────────────────────
929
- return startRepl();
930
- // ─────────────────────────────────────────────────────────────────────────────
931
- // startRepl interactive readline loop, reused after single-task completion
932
- // ─────────────────────────────────────────────────────────────────────────────
933
- async function startRepl() {
934
- if (activeChatHistory.length > 0) {
935
- console.log(chalk_1.default.hex('#8a7768')(` Continuing session with ${Math.floor(activeChatHistory.length / 2)} prior turns.`));
936
- }
937
- console.log(chalk_1.default.hex('#8a7768')(' Type a task, or :help for commands. Ctrl+C to exit.\n'));
938
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
939
- const ask = () => {
940
- const idTag = activeChatId ? chalk_1.default.hex('#4e3d30')(` [${activeChatId}]`) : '';
941
- rl.question(chalk_1.default.hex('#cc785c')(' ▸ ') + idTag + (idTag ? ' ' : ''), async (line) => {
942
- const input = line.trim();
943
- if (!input) {
944
- ask();
945
- return;
946
- }
947
- // Slash / colon commands
948
- const replCtx = {
949
- ctx, display,
950
- providerConfig: { model: resolved.model, apiKey: runtimeConfig.apiKey, baseUrl: runtimeConfig.baseUrl ?? undefined },
951
- permissions, cumulative,
952
- chatState: { projectRoot, activeChatId, activeChatHistory, activeChatTitle, noSession },
953
- rl,
954
- rubyEnabled: { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby }.enabled,
955
- };
956
- const cmdResult = await handleReplCommand(input, replCtx);
957
- if (cmdResult.handled) {
958
- // Commands may update chat state
959
- if (cmdResult.newChatId !== undefined)
960
- activeChatId = cmdResult.newChatId;
961
- if (cmdResult.newHistory !== undefined)
962
- activeChatHistory = cmdResult.newHistory;
963
- if (cmdResult.newTitle !== undefined)
964
- activeChatTitle = cmdResult.newTitle;
965
- // :ruby on/off persists by mutating the loaded config the gate reads.
966
- if (cmdResult.rubyEnabled !== undefined) {
967
- fileConfig.ruby = { ...fileConfig.ruby, enabled: cmdResult.rubyEnabled };
968
- }
969
- ask();
970
- return;
971
- }
972
- // Run taskpass current conversation history for stay-active mode
973
- // ── Competence-based model selection ───────────────────────────────
974
- // Picks the best-performing model among already-configured, same-family
975
- // options based on task similarity to past episodes. Distinct from
976
- // RubyAlternator below this only chooses among models you've already
977
- // set up; it never tries a free local model first.
978
- // Only when the user has NOT set a model explicitly via --model or :model
979
- if (!userSetModel && !globalCfg?.defaultModel) {
980
- try {
981
- const availableModelIds = (0, factory_js_1.getAllModels)()
982
- .map(m => m.id)
983
- .filter(id => (0, factory_js_2.isModelConfigured)(id))
984
- .filter(id => (0, factory_js_2.modelProviderFamily)(id) === (0, factory_js_2.modelProviderFamily)(runtimeConfig.model ?? resolved.model));
985
- const suggested = await (0, model_selector_js_1.selectModel)(ctx.root, input, availableModelIds);
986
- if (suggested && suggested !== runtimeConfig.model) {
987
- console.log(chalk_1.default.hex('#4e3d30')(` Using ${suggested} (selected by competence history)\n`));
988
- runtimeConfig.model = suggested;
989
- runtimeConfig.baseUrl = undefined;
990
- runtimeConfig.apiKey = undefined;
991
- }
992
- }
993
- catch {
994
- // Competence selection is best-effort; never block the user
995
- }
996
- }
997
- let result;
998
- try {
999
- const currentProvider = buildProvider(display);
1000
- const replConfirmFn = async (msg) => new Promise(res => {
1001
- rl.question(`\n⚠️ ${msg} [y/N] `, a => {
1002
- res(a.trim().toLowerCase() === 'y' || a.trim().toLowerCase() === 'yes');
1003
- });
1004
- });
1005
- const doVerify = argv.verify === true || !!fileConfig.verify;
1006
- const rubyConfig = { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby };
1007
- if (doVerify) {
1008
- // Ruby-alternation doesn't support the verification wrapper yet —
1009
- // a verified task always runs directly against the selected model.
1010
- const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
1011
- const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
1012
- const testCommand = cliTestCommand ?? fileConfig.testCommand;
1013
- const wrapperResult = await runWithVerification({
1014
- loopOpts: {
1015
- provider: currentProvider, task: input,
1016
- context: ctx, permissions, display,
1017
- initialHistory: activeChatHistory,
1018
- maxTurns: resolved.maxTurns,
1019
- confirmFn: replConfirmFn,
1020
- spawnConfig: {
1021
- apiKey: runtimeConfig.apiKey,
1022
- baseUrl: runtimeConfig.baseUrl ?? undefined,
1023
- },
1024
- sessionPath,
1025
- },
1026
- config: { enabled: true, maxRetries, testCommand },
1027
- projectRoot: ctx.root,
1028
- display,
1029
- });
1030
- result = wrapperResult.loopResult;
1031
- }
1032
- else if (rubyConfig.enabled) {
1033
- const alternator = new alternator_js_1.RubyAlternator({
1034
- rubyConfig,
1035
- largeModelProvider: currentProvider,
1036
- projectRoot: ctx.root,
1037
- context: ctx,
1038
- display,
1039
- permissions,
1040
- confirmFn: replConfirmFn,
1041
- initialHistory: activeChatHistory,
1042
- });
1043
- const altResult = await alternator.run(input);
1044
- result = altResult.loopResult;
1045
- }
1046
- else {
1047
- result = await (0, loop_js_1.runAgentLoop)({
1048
- provider: currentProvider, task: input,
1049
- context: ctx, permissions, display,
1050
- initialHistory: activeChatHistory,
1051
- maxTurns: resolved.maxTurns,
1052
- confirmFn: replConfirmFn,
1053
- spawnConfig: {
1054
- apiKey: runtimeConfig.apiKey,
1055
- baseUrl: runtimeConfig.baseUrl ?? undefined,
1056
- },
1057
- sessionPath,
1058
- });
1059
- }
1060
- }
1061
- catch (err) {
1062
- const msg = err instanceof Error ? (err.stack || err.message) : String(err);
1063
- console.error(chalk_1.default.hex('#b15439')(`\n ✗ Unhandled error: ${msg}\n`));
1064
- // Suggest :provider on auth errors
1065
- if (/\b(401|403|unauthorized|invalid.*api.?key|authentication)\b/i.test(msg)) {
1066
- console.error(chalk_1.default.hex('#cc785c')(' 💡 Run :provider to reconfigure your API key\n'));
1067
- }
1068
- ask();
1069
- return;
1070
- }
1071
- // Update stay-active history
1072
- activeChatHistory = result.history;
1073
- // Persist session
1074
- if (activeChatId && !noSession) {
1075
- await session_store_js_1.sessionStore.upsertSession(projectRoot, activeChatId, activeChatHistory, activeChatTitle);
1076
- }
1077
- cumulative.turns += result.turns;
1078
- cumulative.toolCalls += result.toolCallCount;
1079
- cumulative.inputTokens += result.usage.inputTokens;
1080
- cumulative.outputTokens += result.usage.outputTokens;
1081
- cumulative.costUsd += result.costUsd;
1082
- if (result.success) {
1083
- display.summary(result.summary, result.turns, result.toolCallCount);
1084
- printUsageFooter(display, result.usage, result.costUsd, {
1085
- model: runtimeConfig.model ?? undefined,
1086
- cumulativeTokens: cumulative.inputTokens + cumulative.outputTokens,
1087
- history: activeChatHistory,
1088
- });
1089
- }
1090
- else {
1091
- display.error(result.summary);
1092
- }
1093
- ask();
1094
- });
863
+ return;
864
+ }
865
+ // ── Interactive REPL mode (TUI — fixed bottom input) ────────────────────────
866
+ // Enter the alternate screen buffer for the whole interactive session —
867
+ // see enterAltScreen()'s doc comment in tui.ts for why: redrawing the
868
+ // header in place on the NORMAL screen (needed so it stays pinned while
869
+ // reflecting live typing) leaves every intermediate redraw frame
870
+ // permanently baked into the real terminal's scrollback the moment it
871
+ // scrolls past the top of the viewport — the live view was always
872
+ // correct, but scrolling up through history showed it stacked dozens of
873
+ // times. The alt screen is an isolated buffer with no persistent
874
+ // scrollback, so this can't happen; leaving it (destroyTui()/Ctrl+C)
875
+ // restores the terminal exactly as it was, same as quitting vim or less.
876
+ // The banner was already drawn once above (renderBanner() is shared with
877
+ // one-shot mode, which does NOT use the alt screen), so it has to be
878
+ // redrawn here it rendered to the now-hidden normal screen, not this
879
+ // fresh alt-screen buffer.
880
+ (0, tui_js_1.enterAltScreen)();
881
+ const tuiBannerInfo = {
882
+ version: package_json_1.default.version,
883
+ title: ctx.name,
884
+ provider: provider.name,
885
+ model: runtimeConfig.model,
886
+ language: ctx.language,
887
+ mode: permissionLevel,
888
+ cwd: projectRoot,
889
+ extras: [
890
+ ...(fileConfig.model ? ['.aura.json loaded'] : []),
891
+ ...(activeChatId ? [`chat ${activeChatId}`] : []),
892
+ ],
893
+ };
894
+ (0, diamond_js_1.renderBanner)(tuiBannerInfo);
895
+ // The TUI keeps its own copy of the banner rows: when scroll mode hands
896
+ // the screen back, the live view is rebuilt from scratch, and on the alt
897
+ // screen there's no scrollback to recover the banner from.
898
+ (0, tui_js_1.setBannerLines)((0, diamond_js_1.buildBannerLines)(tuiBannerInfo));
899
+ // Use the TUI display for output
900
+ const tuiDisplay = (0, tui_js_1.createTuiDisplay)();
901
+ // Shared context-health tracker: the loop records compaction events and
902
+ // per-turn snapshots into it; the /context command reads it back. The
903
+ // system-prompt source starts empty — the loop calls updateSystem() with
904
+ // the real (task-embedded) prompt each run.
905
+ const healthTracker = new context_health_js_1.ContextHealthTracker(() => '', () => activeChatHistory, resolved.model ?? 'gpt-4o', resolved.model ?? 'gpt-4o');
906
+ // ── Right-panel content: "Try" suggestions only ─────────────────────────
907
+ // The panel deliberately carries no Commands/Skills listings — the
908
+ // command list lives in :help and doesn't need permanent sidebar space
909
+ // next to the input.
910
+ const panelSuggestions = [
911
+ activeChatHistory.length === 0 ? ':help see all commands' : ':doctor — scan for issues',
912
+ ctx.language ? `run: ${ctx.language === 'TypeScript' || ctx.language === 'JavaScript' ? 'npm test' : ':graph'}` : ':viz — memory dashboard',
913
+ ];
914
+ (0, tui_js_1.setPanelContent)({ suggestions: panelSuggestions });
915
+ // Unboxed metadata line under the input box, mirroring the reference's
916
+ // "Build · <model> · <mode>" line under its own input box.
917
+ (0, tui_js_1.setStatusLine)([provider.name, runtimeConfig.model, permissionLevel].filter(Boolean).join(' · '));
918
+ (0, tui_js_1.initTui)();
919
+ if (activeChatId)
920
+ (0, tui_js_1.setChatId)(activeChatId);
921
+ (0, tui_js_1.startInput)();
922
+ // Route permission confirmations through the TUI's own raw-mode-safe
923
+ // prompt instead of a plain readline.Interface, which forces stdin out
924
+ // of raw mode and fights the TUI's own input handler (see tui.ts).
925
+ (0, permissions_js_1.setConfirmHandler)(tui_js_1.askConfirm);
926
+ // Must come after initTui() writeOutput() assumes the "cursor at base
927
+ // row" invariant initTui() establishes; calling it any earlier corrupts
928
+ // that baseline.
929
+ if (activeChatHistory.length > 0) {
930
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Continuing session with ' + Math.floor(activeChatHistory.length / 2) + ' prior turns.'));
931
+ }
932
+ // Buffer for :btw and :stop typed during the agent loop
933
+ let pendingBtw = null;
934
+ let abortController = null;
935
+ (0, tui_js_1.setCallbacks)({
936
+ onEnter(line) {
937
+ processLine(line);
938
+ },
939
+ onStop() {
940
+ if (abortController)
941
+ abortController?.abort();
942
+ },
943
+ });
944
+ let tuiInputHistory = [];
945
+ async function processLine(input) {
946
+ const replCtx = {
947
+ rl: null,
948
+ ctx, display: tuiDisplay,
949
+ providerConfig: { model: resolved.model, apiKey: runtimeConfig.apiKey, baseUrl: runtimeConfig.baseUrl ?? undefined },
950
+ permissions, cumulative,
951
+ chatState: { projectRoot, activeChatId, activeChatHistory, activeChatTitle, noSession },
952
+ sessionPath,
953
+ healthTracker,
1095
954
  };
1096
- // Ctrl+C: if a task is running, prompt to force-quit; second Ctrl+C exits.
1097
- let ctrlC = 0;
1098
- rl.on('SIGINT', () => {
1099
- ctrlC++;
1100
- if (ctrlC === 1) {
1101
- console.log(chalk_1.default.hex('#cc785c')('\n ⏳ Press Ctrl+C again to exit (current task will keep running).'));
1102
- setTimeout(() => { ctrlC = 0; }, 3000);
955
+ // Check for REPL commands
956
+ const cmdResult = await handleReplCommand(input, replCtx);
957
+ if (cmdResult.handled) {
958
+ if (cmdResult.newChatId !== undefined)
959
+ activeChatId = cmdResult.newChatId;
960
+ if (cmdResult.newHistory !== undefined)
961
+ activeChatHistory = cmdResult.newHistory;
962
+ if (cmdResult.newTitle !== undefined)
963
+ activeChatTitle = cmdResult.newTitle;
964
+ if (activeChatId)
965
+ (0, tui_js_1.setChatId)(activeChatId);
966
+ return;
967
+ }
968
+ // Run task
969
+ let result;
970
+ abortController = new AbortController();
971
+ const abortSignal = abortController.signal;
972
+ try {
973
+ const currentProvider = buildProvider(tuiDisplay);
974
+ pendingBtw = null;
975
+ const doVerify = argv.verify === true || !!fileConfig.verify;
976
+ if (doVerify) {
977
+ const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
978
+ const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
979
+ const testCommand = cliTestCommand ?? fileConfig.testCommand;
980
+ const wrapperResult = await runWithVerification({
981
+ loopOpts: {
982
+ provider: currentProvider, task: input,
983
+ context: ctx, permissions, display: tuiDisplay,
984
+ initialHistory: activeChatHistory,
985
+ maxTurns: resolved.maxTurns,
986
+ abortSignal,
987
+ spawnConfig: {
988
+ apiKey: runtimeConfig.apiKey,
989
+ baseUrl: runtimeConfig.baseUrl ?? undefined,
990
+ },
991
+ sessionPath,
992
+ healthTracker,
993
+ },
994
+ config: { enabled: true, maxRetries, testCommand },
995
+ projectRoot: ctx.root,
996
+ display: tuiDisplay,
997
+ });
998
+ result = wrapperResult.loopResult;
1103
999
  }
1104
1000
  else {
1105
- console.log(chalk_1.default.hex('#4e3d30')('\n Aura closed.\n'));
1106
- process.exit(0);
1001
+ result = await (0, loop_js_1.runAgentLoop)({
1002
+ provider: currentProvider, task: input,
1003
+ context: ctx, permissions, display: tuiDisplay,
1004
+ initialHistory: activeChatHistory,
1005
+ maxTurns: resolved.maxTurns,
1006
+ spawnConfig: {
1007
+ apiKey: runtimeConfig.apiKey,
1008
+ baseUrl: runtimeConfig.baseUrl ?? undefined,
1009
+ },
1010
+ sessionPath,
1011
+ abortSignal,
1012
+ healthTracker,
1013
+ });
1107
1014
  }
1108
- });
1109
- ask();
1110
- rl.on('close', () => {
1111
- console.log(chalk_1.default.hex('#4e3d30')('\n Aura closed.\n'));
1112
- process.exit(0);
1113
- });
1015
+ }
1016
+ catch (err) {
1017
+ const msg = err instanceof Error ? (err.stack || err.message) : String(err);
1018
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex('#b15439')(' Unhandled error: ' + msg));
1019
+ return;
1020
+ }
1021
+ // Check if task was cancelled by user
1022
+ if (abortController?.signal.aborted && !result.success) {
1023
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex('#d4903a')(' ⏹ Task cancelled.'));
1024
+ // Don't record episode for cancelled tasks
1025
+ return;
1026
+ }
1027
+ // Update stay-active history
1028
+ try {
1029
+ activeChatHistory = result.history;
1030
+ // Persist session
1031
+ if (activeChatId && !noSession) {
1032
+ await session_store_js_1.sessionStore.upsertSession(projectRoot, activeChatId, activeChatHistory, activeChatTitle);
1033
+ }
1034
+ {
1035
+ const { recordEpisode } = await Promise.resolve().then(() => __importStar(require('../dream/episode.js')));
1036
+ recordEpisode(ctx.root, {
1037
+ task: input,
1038
+ model: runtimeConfig.model ?? resolved.model ?? 'unknown',
1039
+ success: result.success,
1040
+ tokens: (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0),
1041
+ durationMs: 0,
1042
+ });
1043
+ }
1044
+ cumulative.turns += result.turns;
1045
+ cumulative.toolCalls += result.toolCallCount;
1046
+ cumulative.inputTokens += result.usage.inputTokens;
1047
+ cumulative.outputTokens += result.usage.outputTokens;
1048
+ cumulative.costUsd += result.costUsd;
1049
+ if (result.success) {
1050
+ tuiDisplay.summary(result.summary, result.turns, result.toolCallCount);
1051
+ printUsageFooter(tuiDisplay, result.usage, result.costUsd);
1052
+ if (speakEnabled)
1053
+ await speakSummary(result.summary);
1054
+ }
1055
+ else {
1056
+ tuiDisplay.error(result.summary);
1057
+ }
1058
+ // Process any :btw that was typed during the loop
1059
+ if (pendingBtw) {
1060
+ const q = pendingBtw;
1061
+ pendingBtw = null;
1062
+ abortController = new AbortController();
1063
+ const { runBtwQuery, renderBtwAnswer } = await Promise.resolve().then(() => __importStar(require('../repl/side-channel.js')));
1064
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Side question: "' + q + '"'));
1065
+ const btwResult = await runBtwQuery(q, buildProvider(tuiDisplay), ctx);
1066
+ (0, tui_js_1.writeOutput)(renderBtwAnswer(btwResult.answer, btwResult.tokens));
1067
+ }
1068
+ }
1069
+ catch (err) {
1070
+ const msg = err instanceof Error ? (err.stack || err.message) : String(err);
1071
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex('#b15439')(' \u2717 Unhandled error after task completed: ' + msg));
1072
+ }
1114
1073
  }
1074
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Type a task, or :help for commands.'));
1075
+ }
1076
+ /**
1077
+ * Best-effort map from a model id to the env-var name its key lives under.
1078
+ * Delegates to the factory helper — custom providers from .aura.json are
1079
+ * covered because registerCustomProviders runs at startup.
1080
+ */
1081
+ function envNameForModel(model) {
1082
+ return (0, factory_js_1.apiKeyEnvVarForModel)(model);
1115
1083
  }
1116
1084
  function trySetModel(c, newModel) {
1117
1085
  const prevModel = runtimeConfig.model;
1086
+ const prevResolved = resolved.model;
1087
+ const prevApiKey = runtimeConfig.apiKey;
1118
1088
  const prevBaseUrl = runtimeConfig.baseUrl;
1089
+ const prevResolvedBaseUrl = resolved.baseUrl;
1119
1090
  runtimeConfig.model = newModel;
1120
- runtimeConfig.baseUrl = undefined;
1091
+ resolved.model = newModel; // buildProvider reads resolved.model — keep in sync
1092
+ const crossing = (0, model_select_js_1.isProviderChange)(prevModel ?? prevResolved, newModel);
1093
+ if (crossing) {
1094
+ // The old provider's key/baseUrl must not follow the model across a
1095
+ // provider boundary — createProvider gives config.apiKey priority over
1096
+ // the per-provider env fallback, so a stale key would be sent to the
1097
+ // NEW provider's endpoint. Clear both so the factory falls through to
1098
+ // the new provider's own getApiKey()/default-endpoint chain.
1099
+ runtimeConfig.apiKey = undefined;
1100
+ runtimeConfig.baseUrl = undefined;
1101
+ resolved.baseUrl = undefined;
1102
+ }
1121
1103
  try {
1122
1104
  const test = buildProvider(c.display);
1123
1105
  c.providerConfig.model = newModel;
1124
- userSetModel = true; // user explicitly chose — alternator must not override
1106
+ if (crossing) {
1107
+ c.providerConfig.apiKey = undefined;
1108
+ c.providerConfig.baseUrl = undefined;
1109
+ }
1125
1110
  console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Switched to ${test.name} · ${newModel}`));
1111
+ // Remember the choice for the next session. The saved baseUrl belongs to
1112
+ // the wizard-configured model — keep it only when switching back to that
1113
+ // model, otherwise the factory's per-provider default applies.
1114
+ try {
1115
+ (0, global_config_js_1.saveGlobalConfig)({
1116
+ provider: globalCfg?.provider ?? test.name,
1117
+ // loadGlobalConfig treats an empty apiKeyEnv as "not configured", so
1118
+ // resolve from the NEW model's provider — never inherit the old
1119
+ // provider's env name across a provider change (a silently wrong
1120
+ // apiKeyEnv on disk pairs the wrong key with the model on the next
1121
+ // startup).
1122
+ apiKeyEnv: (0, model_select_js_1.apiKeyEnvForModelSwitch)(newModel, prevModel ?? prevResolved, globalCfg?.apiKeyEnv),
1123
+ defaultModel: newModel,
1124
+ baseUrl: savedProvider && newModel === savedProvider.model ? savedProvider.baseUrl : undefined,
1125
+ });
1126
+ }
1127
+ catch { /* persistence is best-effort; the switch itself succeeded */ }
1126
1128
  return { ok: true };
1127
1129
  }
1128
1130
  catch (e) {
1129
1131
  runtimeConfig.model = prevModel; // rollback on error
1132
+ resolved.model = prevResolved;
1133
+ runtimeConfig.apiKey = prevApiKey;
1130
1134
  runtimeConfig.baseUrl = prevBaseUrl;
1135
+ resolved.baseUrl = prevResolvedBaseUrl;
1131
1136
  return { ok: false, err: String(e) };
1132
1137
  }
1133
1138
  }
@@ -1136,134 +1141,443 @@ function trySetModel(c, newModel) {
1136
1141
  * lets the user pick by number or type a custom model ID.
1137
1142
  */
1138
1143
  async function showModelSelector(c) {
1139
- const allModels = (0, factory_js_1.getAllModels)();
1140
- // Build flat numbered list grouped by provider
1141
- const entries = [];
1142
- let currentProvider = '';
1143
- for (const m of allModels) {
1144
- if (m.provider !== currentProvider) {
1145
- currentProvider = m.provider;
1146
- entries.push({ id: '', label: chalk_1.default.hex('#8a7768').bold(` ── ${currentProvider} ──`), provider: currentProvider });
1147
- }
1148
- entries.push({
1149
- id: m.id,
1150
- label: ` ${chalk_1.default.hex('#cc785c')(String(entries.length + 1).padStart(2))}. ${chalk_1.default.hex('#ede0cc')(m.name.padEnd(30))} ${chalk_1.default.hex('#4e3d30')(m.speed)}`,
1151
- provider: m.provider,
1152
- });
1153
- }
1154
- console.log(chalk_1.default.hex('#cc785c').bold('\n Model Selector\n'));
1155
- for (const e of entries) {
1156
- console.log(e.label);
1157
- }
1158
- console.log(chalk_1.default.hex('#4e3d30')(`\n Current: ${runtimeConfig.model}`));
1159
- console.log(chalk_1.default.hex('#4e3d30')(' Type a number, model ID, or press Enter to cancel:\n'));
1160
- return new Promise(resolve => {
1161
- c.rl.question(chalk_1.default.hex('#cc785c')(' ▸ '), (answer) => {
1162
- const choice = answer.trim();
1163
- if (!choice) {
1164
- console.log(chalk_1.default.hex('#4e3d30')(' Cancelled.\n'));
1165
- resolve();
1166
- return;
1144
+ // Same stdin-collision fix as :provider (see handleReplCommand): the TUI's
1145
+ // raw-mode stdin handler competes with any prompt for keystrokes, so stop
1146
+ // TUI input entirely (this also hides the main input box), let a plain
1147
+ // readline own stdin, then restart TUI input once the selector exits.
1148
+ const wasInputActive = tui_js_1.inputActive;
1149
+ if (wasInputActive)
1150
+ (0, tui_js_1.stopInput)();
1151
+ try {
1152
+ // Grouped list where section headers are display-only: they carry no
1153
+ // number, so numbering is gap-free and a header can never be selected.
1154
+ const rows = (0, model_select_js_1.buildModelRows)((0, factory_js_1.getAllModels)());
1155
+ console.log(chalk_1.default.hex('#cc785c').bold('\n Model Selector\n'));
1156
+ for (const r of rows) {
1157
+ if (r.kind === 'header') {
1158
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX).bold(` ── ${r.provider} ──`));
1167
1159
  }
1168
- // Try as a number
1169
- const num = parseInt(choice, 10);
1170
- if (!isNaN(num) && num >= 1 && num <= entries.length) {
1171
- const selected = entries[num - 1];
1172
- if (selected.id) {
1173
- trySetModel(c, selected.id);
1174
- }
1175
- else {
1176
- console.log(chalk_1.default.hex('#b15439')(' ✗ That\'s a section header, pick a model number.'));
1177
- }
1178
- resolve();
1160
+ else {
1161
+ console.log(` ${chalk_1.default.hex('#cc785c')(String(r.num).padStart(2))}. ${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(r.name.padEnd(30))} ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(r.speed)}`);
1162
+ }
1163
+ }
1164
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`\n Current: ${runtimeConfig.model}`));
1165
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Type a number, model ID, or press Enter to cancel:\n'));
1166
+ const answer = await new Promise(resolve => {
1167
+ const promptRl = c.rl;
1168
+ if (!promptRl) {
1169
+ // TUI mode: TUI input is stopped above, so a temporary readline can
1170
+ // own stdin without the two-readers-one-stream conflict.
1171
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1172
+ rl.question(chalk_1.default.hex('#cc785c')(' ▸ '), ans => { rl.close(); resolve(ans); });
1179
1173
  return;
1180
1174
  }
1181
- // Treat as a raw model ID
1182
- trySetModel(c, choice);
1183
- resolve();
1175
+ promptRl.question(chalk_1.default.hex('#cc785c')(' ▸ '), resolve);
1184
1176
  });
1185
- });
1177
+ const choice = answer.trim();
1178
+ if (!choice) {
1179
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Cancelled.\n'));
1180
+ return;
1181
+ }
1182
+ // Try as a number — every listed number is a real, selectable model
1183
+ const num = parseInt(choice, 10);
1184
+ if (!isNaN(num) && num >= 1 && num <= (0, model_select_js_1.modelCount)(rows)) {
1185
+ trySetModel(c, (0, model_select_js_1.modelIdForNumber)(rows, num));
1186
+ return;
1187
+ }
1188
+ // Treat as a raw model ID
1189
+ trySetModel(c, choice);
1190
+ }
1191
+ finally {
1192
+ if (wasInputActive)
1193
+ (0, tui_js_1.startInput)();
1194
+ }
1186
1195
  }
1187
1196
  async function handleReplCommand(input, c) {
1188
1197
  const unhandled = { handled: false };
1198
+ // ── :q — Task queue (with subcommands, keep bare :q as quit) ─────────────
1199
+ if (input.startsWith(':q ')) {
1200
+ const sub = input.slice(3).trimStart();
1201
+ const { addToQueue, loadQueue, removeFromQueue, clearQueue, runQueueItem, formatQueue } = await Promise.resolve().then(() => __importStar(require('../repl/queue.js')));
1202
+ if (sub.startsWith('add ')) {
1203
+ const prompt = sub.slice(4).trim();
1204
+ if (!prompt) {
1205
+ c.display.warning('Usage: :q add <prompt> -- add a task to the queue.');
1206
+ return { handled: true };
1207
+ }
1208
+ const item = addToQueue(prompt);
1209
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Queued #${loadQueue().length}: "${prompt.slice(0, 60)}"\n`));
1210
+ return { handled: true };
1211
+ }
1212
+ if (sub === 'list') {
1213
+ const items = loadQueue();
1214
+ console.log(formatQueue(items));
1215
+ return { handled: true };
1216
+ }
1217
+ if (sub.startsWith('run ')) {
1218
+ const n = parseInt(sub.slice(4).trim(), 10);
1219
+ if (isNaN(n) || n < 1) {
1220
+ c.display.warning('Usage: :q run <number> — run the task at that position (see :q list).');
1221
+ return { handled: true };
1222
+ }
1223
+ const items = loadQueue();
1224
+ if (n > items.length) {
1225
+ c.display.warning(`Queue only has ${items.length} item(s).`);
1226
+ return { handled: true };
1227
+ }
1228
+ c.display.agentThinking();
1229
+ const result = await runQueueItem(n - 1, buildProvider(c.display), c.ctx, c.permissions, c.display);
1230
+ if (!result) {
1231
+ c.display.warning('Could not run that item.');
1232
+ return { handled: true };
1233
+ }
1234
+ c.display.success(`Queue item #${n}: ${result.success ? 'done' : 'failed'}`);
1235
+ if (result.output) {
1236
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` ${result.output.slice(0, 240)}`));
1237
+ }
1238
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${result.turns} turn(s) · ${result.toolCalls} tool call(s).\n`));
1239
+ return { handled: true };
1240
+ }
1241
+ if (sub.startsWith('drop ')) {
1242
+ const n = parseInt(sub.slice(5).trim(), 10);
1243
+ if (isNaN(n) || n < 1) {
1244
+ c.display.warning('Usage: :q drop <number> — remove the task at that position.');
1245
+ return { handled: true };
1246
+ }
1247
+ const removed = removeFromQueue(n - 1);
1248
+ if (!removed) {
1249
+ c.display.warning(`No item at position ${n}.`);
1250
+ return { handled: true };
1251
+ }
1252
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Dropped #${n}: "${removed.prompt.slice(0, 60)}"\n`));
1253
+ return { handled: true };
1254
+ }
1255
+ if (sub === 'clear') {
1256
+ const count = loadQueue().length;
1257
+ if (count === 0) {
1258
+ c.display.warning('Queue is already empty.');
1259
+ return { handled: true };
1260
+ }
1261
+ clearQueue();
1262
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Queue cleared (${count} item(s) removed).\n`));
1263
+ return { handled: true };
1264
+ }
1265
+ c.display.warning('Usage: :q add <prompt> | :q list | :q run <n> | :q drop <n> | :q clear');
1266
+ return { handled: true };
1267
+ }
1189
1268
  if (input === ':quit' || input === ':q' || input === '/exit') {
1190
1269
  process.exit(0);
1191
1270
  }
1271
+ if (input === ':speak') {
1272
+ speakEnabled = !speakEnabled;
1273
+ console.log(chalk_1.default.hex(speakEnabled ? '#5a9e6e' : '#a68a2a')(` 🔊 Voice replies ${speakEnabled ? 'ON — Aura will read its answers aloud' : 'OFF'}.\n`));
1274
+ return { handled: true };
1275
+ }
1276
+ // :approve — flip the session into auto-approve (no per-command y/N prompt).
1277
+ // :approve → toggle auto ⇄ normal
1278
+ // :approve all → auto (approve everything for this session)
1279
+ // :approve off → back to normal (confirm destructive commands again)
1280
+ // Dangerous commands are still blocked either way.
1281
+ if (input === ':approve' || input === ':approve all' || input === ':approve off') {
1282
+ const cur = c.permissions.getLevel();
1283
+ let next;
1284
+ if (input === ':approve off')
1285
+ next = 'normal';
1286
+ else if (input === ':approve all')
1287
+ next = 'auto';
1288
+ else
1289
+ next = cur === 'auto' ? 'normal' : 'auto';
1290
+ c.permissions.setLevel(next);
1291
+ if (next === 'auto') {
1292
+ console.log(chalk_1.default.hex('#d4903a')(' ✅ Auto-approve ON — commands run without asking (dangerous ones still blocked). `:approve off` to re-enable prompts.\n'));
1293
+ }
1294
+ else {
1295
+ console.log(chalk_1.default.hex('#5a9e6e')(' 🔒 Auto-approve OFF — destructive commands will ask for confirmation again.\n'));
1296
+ }
1297
+ return { handled: true };
1298
+ }
1299
+ if (input === ':dream' || input === ':dream full') {
1300
+ const full = input === ':dream full';
1301
+ const { runDream } = await Promise.resolve().then(() => __importStar(require('../dream/dream.js')));
1302
+ c.display.agentThinking();
1303
+ const res = await runDream({ projectRoot: c.ctx.root, provider: buildProvider(c.display), full });
1304
+ if (res.skipped) {
1305
+ c.display.warning(res.providerError
1306
+ ? `Dream skipped (episodes preserved): ${res.providerError}`
1307
+ : (res.reason ?? 'Nothing to consolidate.'));
1308
+ }
1309
+ else {
1310
+ c.display.success(`Dream written: ${res.path} (${res.episodeCount} episodes${full ? ', full run' : ''})`);
1311
+ if (res.reconciled)
1312
+ c.display.success('Reconciliation also ran (>=3 dreams exist) -> dreams/.reconciled.md');
1313
+ }
1314
+ return { handled: true };
1315
+ }
1316
+ if (input.startsWith(':research ') || input === ':research') {
1317
+ const topic = input.slice(':research '.length).trim();
1318
+ if (!topic) {
1319
+ c.display.warning('Usage: :research <topic> -- runs a multi-step research pass and saves to research/*.md.');
1320
+ return { handled: true };
1321
+ }
1322
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Researching "${topic}"…\n`));
1323
+ try {
1324
+ const { runResearch } = await Promise.resolve().then(() => __importStar(require('../research/research.js')));
1325
+ const res = await runResearch({
1326
+ projectRoot: c.ctx.root,
1327
+ topic,
1328
+ provider: buildProvider(c.display),
1329
+ context: c.ctx,
1330
+ permissions: c.permissions,
1331
+ display: c.display,
1332
+ });
1333
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Research written: ${res.path}`));
1334
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${res.turns} turn(s) · ${res.toolCalls} tool call(s).\n`));
1335
+ }
1336
+ catch (e) {
1337
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1338
+ }
1339
+ return { handled: true };
1340
+ }
1341
+ if (input === ':confessions') {
1342
+ const { listConfessions } = await Promise.resolve().then(() => __importStar(require('../agent/confess.js')));
1343
+ const confs = listConfessions();
1344
+ if (confs.length === 0) {
1345
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No confessions yet. Run :confess after a high-token episode.\n'));
1346
+ }
1347
+ else {
1348
+ console.log(chalk_1.default.hex('#cc785c').bold(`\n ${confs.length} confession(s):\n`));
1349
+ for (const c of confs) {
1350
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${c.file}`));
1351
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${c.tokens.toLocaleString()} tokens burned → ${c.lesson.slice(0, 100)}`));
1352
+ }
1353
+ console.log('');
1354
+ }
1355
+ return { handled: true };
1356
+ }
1357
+ if (input === ':confess') {
1358
+ const { runConfession, findEpisodeToConfess } = await Promise.resolve().then(() => __importStar(require('../agent/confess.js')));
1359
+ const targetEp = findEpisodeToConfess(c.ctx.root);
1360
+ if (!targetEp) {
1361
+ console.log(chalk_1.default.hex('#cc9e5c')('\n No anomalous episode found. Confession is fully automatic — the system alone decides what to confess.\n'));
1362
+ return { handled: true };
1363
+ }
1364
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n 🙏 Confessing episode ${targetEp.id.slice(0, 8)}… — ${targetEp.task.slice(0, 60)} (${(targetEp.tokens / 1e6).toFixed(1)}M tok)\n`));
1365
+ try {
1366
+ // Use a different model than the one that made the mistake
1367
+ const confessorModel = targetEp.model.startsWith('deepseek') ? 'glm-5.2' : 'deepseek/deepseek-chat';
1368
+ const { createProvider } = await Promise.resolve().then(() => __importStar(require('../providers/factory.js')));
1369
+ const provider = createProvider({ model: confessorModel });
1370
+ const result = await runConfession({
1371
+ projectRoot: c.ctx.root,
1372
+ episodeId: targetEp.id,
1373
+ provider,
1374
+ });
1375
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Confession written: ${result.path}`));
1376
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Tokens burned: ${result.tokensBurned.toLocaleString()} | Confession cost: ${result.tokensSpent.toLocaleString()} (${confessorModel})`));
1377
+ console.log(chalk_1.default.hex('#cc9e6c')(' Permanent lesson:'));
1378
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` "${result.lesson}"\n`));
1379
+ }
1380
+ catch (e) {
1381
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1382
+ }
1383
+ return { handled: true };
1384
+ }
1385
+ if (input === ':rem') {
1386
+ const { getReconciledOrLatest } = await Promise.resolve().then(() => __importStar(require('../dream/dream.js')));
1387
+ const res = getReconciledOrLatest(c.ctx.root);
1388
+ if (!res) {
1389
+ c.display.warning('No dreams yet. Run :dream first.');
1390
+ }
1391
+ else {
1392
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n ${res.isReconciled ? 'Reconciled projection' : 'Latest dream (not yet reconciled)'}:\n`));
1393
+ console.log(res.content);
1394
+ }
1395
+ return { handled: true };
1396
+ }
1397
+ // ── :mine — Baby Ruby experience mining (src/mining/). The base pass is
1398
+ // zero-LLM (pure clustering over episodes/*.json); --refine additionally
1399
+ // runs Papa Ruby, one local-model call per qualifying concept, appending
1400
+ // accepted lessons to training-data/<date>.jsonl.
1401
+ if (input === ':mine' || input === ':mine --refine') {
1402
+ const refine = input.endsWith('--refine');
1403
+ const { mineExperience } = await Promise.resolve().then(() => __importStar(require('../mining/extract.js')));
1404
+ const mined = await mineExperience(c.ctx.root);
1405
+ if (mined.episodeCount === 0) {
1406
+ c.display.warning('No episodes to mine yet — run some tasks first.');
1407
+ return { handled: true };
1408
+ }
1409
+ console.log(chalk_1.default.hex('#cc785c').bold(`\n Mined ${mined.concepts.length} concept(s) from ${mined.episodeCount} episode(s) (${mined.unclustered} unclustered):\n`));
1410
+ for (const con of mined.concepts.slice(0, 15)) {
1411
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${con.concept}`) + chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` (${con.category} · ×${con.frequency} · conf ${con.confidence} · depth ${con.depth})`));
1412
+ if (con.keywords.length > 0)
1413
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` keywords: ${con.keywords.join(', ')}`));
1414
+ }
1415
+ if (mined.concepts.length > 15) {
1416
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` … and ${mined.concepts.length - 15} more.`));
1417
+ }
1418
+ if (refine) {
1419
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Refining with the local Ruby model (Papa Ruby)…'));
1420
+ const { refineConcepts } = await Promise.resolve().then(() => __importStar(require('../mining/refine.js')));
1421
+ const res = await refineConcepts({ projectRoot: c.ctx.root, concepts: mined.concepts });
1422
+ if (res.accepted.length > 0) {
1423
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ ${res.accepted.length} training example(s) appended: ${res.outputPath}`));
1424
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${res.rejected} rejected, ${res.skipped} below the confidence/frequency gate.\n`));
1425
+ }
1426
+ else {
1427
+ c.display.warning(`No concepts survived refinement — ${res.rejected} rejected, ${res.skipped} below the confidence/frequency gate.`);
1428
+ }
1429
+ }
1430
+ else if (mined.concepts.length > 0) {
1431
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)('\n :mine --refine judges these with the local Ruby model and writes training-data/*.jsonl\n'));
1432
+ }
1433
+ return { handled: true };
1434
+ }
1435
+ if (input === ':doctor' || input.startsWith(':doctor')) {
1436
+ const fix = input.includes('--fix');
1437
+ const offline = input.includes('--offline');
1438
+ c.display.agentThinking();
1439
+ const report = await (0, index_js_5.runDoctor)({ projectRoot: c.ctx.root, fix, offline });
1440
+ if (typeof tui_js_1.writeOutput === 'function') {
1441
+ (0, tui_js_1.writeOutput)((0, index_js_5.formatDoctorReport)(report));
1442
+ }
1443
+ else {
1444
+ console.log((0, index_js_5.formatDoctorReport)(report));
1445
+ }
1446
+ return { handled: true };
1447
+ }
1448
+ if (input.startsWith(':machina ') || input === ':machina') {
1449
+ const machinaTask = input.slice(':machina '.length).trim();
1450
+ if (!machinaTask) {
1451
+ c.display.warning('Usage: :machina <task> -- runs the task with self-verification (file/test checks + auto-retry).');
1452
+ return { handled: true };
1453
+ }
1454
+ const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
1455
+ const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
1456
+ const testCommand = cliTestCommand ?? fileConfig.testCommand;
1457
+ const wrapperResult = await runWithVerification({
1458
+ loopOpts: {
1459
+ provider: buildProvider(c.display), task: machinaTask,
1460
+ context: c.ctx, permissions: c.permissions, display: c.display,
1461
+ initialHistory: c.chatState.activeChatHistory,
1462
+ maxTurns: resolved.maxTurns,
1463
+ spawnConfig: {
1464
+ apiKey: c.providerConfig.apiKey,
1465
+ baseUrl: c.providerConfig.baseUrl,
1466
+ },
1467
+ sessionPath: c.sessionPath,
1468
+ },
1469
+ config: { enabled: true, maxRetries, testCommand },
1470
+ projectRoot: c.ctx.root,
1471
+ display: c.display,
1472
+ });
1473
+ const mResult = wrapperResult.loopResult;
1474
+ if (mResult.success) {
1475
+ c.display.summary(mResult.summary, mResult.turns, mResult.toolCallCount);
1476
+ printUsageFooter(c.display, mResult.usage, mResult.costUsd);
1477
+ }
1478
+ else {
1479
+ c.display.error(mResult.summary);
1480
+ }
1481
+ return { handled: true, newHistory: mResult.history };
1482
+ }
1483
+ if (input.startsWith(':council ') || input === ':council') {
1484
+ const councilTask = input.slice(':council '.length).trim();
1485
+ if (!councilTask) {
1486
+ c.display.warning('Usage: :council <task> -- runs 2-3 read-only domain specialists in parallel, then synthesizes their reports.');
1487
+ return { handled: true };
1488
+ }
1489
+ const { runMixtureOfAgents } = await Promise.resolve().then(() => __importStar(require('../agent/mixture.js')));
1490
+ const councilResult = await runMixtureOfAgents({
1491
+ provider: buildProvider(c.display), task: councilTask, context: c.ctx, display: c.display,
1492
+ });
1493
+ if (councilResult.success) {
1494
+ c.display.summary(councilResult.summary, councilResult.turns, councilResult.toolCallCount);
1495
+ printUsageFooter(c.display, councilResult.usage, councilResult.costUsd);
1496
+ }
1497
+ else {
1498
+ c.display.error(councilResult.summary);
1499
+ }
1500
+ return { handled: true };
1501
+ }
1502
+ // ── :ecclesia — 5-agent independent research council (research/council.ts).
1503
+ // Distinct from :council above (mixture-of-agents over the current task):
1504
+ // the Ecclesia runs N agents that research a topic WITHOUT seeing each
1505
+ // other's findings, then one synthesis call reconciles them into a verdict.
1506
+ if (input.startsWith(':ecclesia ') || input === ':ecclesia') {
1507
+ let topic = input.slice(':ecclesia '.length).trim();
1508
+ if (!topic) {
1509
+ c.display.warning('Usage: :ecclesia <topic> [--panel <model>] [--seats <n>] -- N independent research agents (default 5) + a synthesis verdict, saved to council/*.md|.html.');
1510
+ return { handled: true };
1511
+ }
1512
+ let panelModel;
1513
+ let panelSize;
1514
+ const panelMatch = topic.match(/\s--panel\s+(\S+)/);
1515
+ if (panelMatch) {
1516
+ panelModel = panelMatch[1];
1517
+ topic = topic.replace(panelMatch[0], '').trim();
1518
+ }
1519
+ const seatsMatch = topic.match(/\s--seats\s+(\d+)/);
1520
+ if (seatsMatch) {
1521
+ panelSize = Number(seatsMatch[1]);
1522
+ topic = topic.replace(seatsMatch[0], '').trim();
1523
+ }
1524
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Convening the Ecclesia on "${topic}"…\n`));
1525
+ try {
1526
+ const { runCouncil } = await Promise.resolve().then(() => __importStar(require('../research/council.js')));
1527
+ const res = await runCouncil({
1528
+ projectRoot: c.ctx.root, topic,
1529
+ synthesisProvider: buildProvider(c.display),
1530
+ context: c.ctx, permissions: c.permissions, display: c.display,
1531
+ panelSize, panelModel,
1532
+ configuredModel: c.providerConfig.model,
1533
+ });
1534
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Ecclesia verdict written: ${res.path}`));
1535
+ console.log(chalk_1.default.hex('#5a9e6e')(` HTML: ${res.htmlPath}`));
1536
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${res.panelSize} seats on ${res.panelModel}.`));
1537
+ if (res.agentFailures > 0) {
1538
+ c.display.warning(`${res.agentFailures} of ${res.panelSize} panel agent(s) failed — verdict is based on the rest.`);
1539
+ }
1540
+ }
1541
+ catch (e) {
1542
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1543
+ }
1544
+ return { handled: true };
1545
+ }
1546
+ // ── :btw — Side channel question (read-only, no history) ────────────────
1547
+ if (input.startsWith(':btw ')) {
1548
+ const question = input.slice(5).trim();
1549
+ if (!question) {
1550
+ c.display.warning('Usage: :btw <question> — ask a quick side question without interrupting the current task.');
1551
+ return { handled: true };
1552
+ }
1553
+ const { runBtwQuery, renderBtwAnswer } = await Promise.resolve().then(() => __importStar(require('../repl/side-channel.js')));
1554
+ c.display.agentThinking();
1555
+ const result = await runBtwQuery(question, buildProvider(c.display), c.ctx);
1556
+ console.log(renderBtwAnswer(result.answer, result.tokens));
1557
+ return { handled: true };
1558
+ }
1192
1559
  if (input === ':help' || input === '/help') {
1193
- console.log(chalk_1.default.hex('#8a7768')([
1194
- '',
1195
- ' ── Session ──────────────────────────────────────',
1196
- ' :id Show current chat ID',
1197
- ' :sessions List all saved sessions',
1198
- ' :resume Resume the latest session',
1199
- ' :resume <id> Resume a specific session by ID',
1200
- ' :new Start a new session (fresh history)',
1201
- ' :history Show turn count in current session',
1202
- ' :clear-history Wipe conversation history (keep session ID)',
1203
- ' :save [title] Rename / save current session',
1204
- ' :delete <id> Delete a saved session',
1205
- '',
1206
- ' ── Model / API ──────────────────────────────────',
1207
- ' :provider Provider setup wizard (configure provider, model, API key)',
1208
- ' :model Interactive model selector',
1209
- ' :model <id> Switch to a specific model',
1210
- ' :models List all available models',
1211
- ' :apikey <key> Set API key for current session',
1212
- '',
1213
- ' ── Workflows ─────────────────────────────────────',
1214
- ' :workflows List all saved workflows',
1215
- ' :workflow Create & run a multi-step workflow',
1216
- ' <name> "step1" "step2" ...',
1217
- ' :resume-workflow <id> Resume a paused/failed workflow',
1218
- '',
1219
- ' ── Context / Stats ──────────────────────────────',
1220
- ' :context Show loaded project context',
1221
- ' :graph Show codebase knowledge graph summary',
1222
- ' :graph refresh Extract + persist codebase graph',
1223
- ' :stats Show episode-level stats (completion rate, models, tokens)',
1224
- ' :plans List saved execution plans',
1225
- ' :viz, :dashboard Generate and open the memory dashboard',
1226
- ' :dream [experimental] Consolidate today\'s episodes into a dated dream (dreams/*.md)',
1227
- ' :dream full [experimental] Consolidate ALL episodes, ignoring the last-dream cutoff',
1228
- ' :rem [experimental] List dream files and open the most recent one',
1229
- ' :research <topic> Multi-step research pass, saved to research/*.md (inspired by DeerFlow)',
1230
- ' :council <topic> Ecclesia — 5 independent agents research the topic, synthesized into one verdict (council/*.md)',
1231
- ' :ruby [on|off] Toggle the Ruby Principle (default: off)',
1232
- ' ⚠ :machina The Abstract Agent Machine — formal model, verified against this source tree',
1233
- ' ⚠ :machina --html Also write the full writeup + diagram to docs/machina.html',
1234
- ' :design [slug|list|off] Set/list the design system auto-applied to UI builds',
1235
- ' /stats, /usage, :usage Show token + cost usage this session',
1236
- ' /clear, /reset Reset cumulative usage stats',
1237
- '',
1238
- ' ── General ──────────────────────────────────────',
1239
- ' :approve all Auto-approve all tool calls (no confirmations)',
1240
- ' :approve normal Require confirmation for destructive ops',
1241
- ' :approve read-only Block all write operations',
1242
- ' :quit, :q, /exit Exit',
1243
- '',
1244
- ' ⚠ = extremely high token usage — reads multiple source files and writes a careful long-form result',
1245
- '',
1246
- ].join('\n')));
1560
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(help_data_js_1.HELP_TEXT.join('\n')));
1247
1561
  return { handled: true };
1248
1562
  }
1249
1563
  // ── Session commands ─────────────────────────────────────────────────────
1250
1564
  if (input === ':id') {
1251
1565
  const cs = c.chatState;
1252
1566
  if (cs.activeChatId) {
1253
- console.log(chalk_1.default.hex('#8a7768')(`\n Chat ID: ${chalk_1.default.hex('#cc785c')(cs.activeChatId)}`));
1567
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Chat ID: ${chalk_1.default.hex('#cc785c')(cs.activeChatId)}`));
1254
1568
  if (cs.activeChatTitle)
1255
- console.log(chalk_1.default.hex('#8a7768')(` Title: ${cs.activeChatTitle}`));
1256
- console.log(chalk_1.default.hex('#4e3d30')(` Turns: ${Math.floor(cs.activeChatHistory.length / 2)}\n`));
1569
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Title: ${cs.activeChatTitle}`));
1570
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Turns: ${Math.floor(cs.activeChatHistory.length / 2)}\n`));
1257
1571
  }
1258
1572
  else {
1259
- console.log(chalk_1.default.hex('#8a7768')('\n No active session (--no-session mode).\n'));
1573
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No active session (--no-session mode).\n'));
1260
1574
  }
1261
1575
  return { handled: true };
1262
1576
  }
1263
1577
  if (input === ':sessions') {
1264
1578
  const sessions = session_store_js_1.sessionStore.listSessions(c.chatState.projectRoot);
1265
1579
  if (sessions.length === 0) {
1266
- console.log(chalk_1.default.hex('#8a7768')('\n No saved sessions.\n'));
1580
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved sessions.\n'));
1267
1581
  }
1268
1582
  else {
1269
1583
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved sessions:\n'));
@@ -1272,8 +1586,8 @@ async function handleReplCommand(input, c) {
1272
1586
  const turns = Math.floor(s.history.length / 2);
1273
1587
  const marker = s.id === c.chatState.activeChatId ? chalk_1.default.hex('#5a9e6e')(' ← current') : '';
1274
1588
  console.log(` ${chalk_1.default.hex('#cc785c')(s.id.padEnd(20))} ` +
1275
- `${chalk_1.default.hex('#ede0cc')(s.title.slice(0, 40).padEnd(41))} ` +
1276
- `${chalk_1.default.hex('#4e3d30')(`${turns}t · ${updated}`)}${marker}`);
1589
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(s.title.slice(0, 40).padEnd(41))} ` +
1590
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${turns}t · ${updated}`)}${marker}`);
1277
1591
  }
1278
1592
  console.log();
1279
1593
  }
@@ -1282,7 +1596,7 @@ async function handleReplCommand(input, c) {
1282
1596
  if (input === ':resume' || input === ':resume ') {
1283
1597
  const latest = session_store_js_1.sessionStore.findLatestSession(c.chatState.projectRoot);
1284
1598
  if (!latest) {
1285
- console.log(chalk_1.default.hex('#8a7768')('\n No saved sessions to resume.\n'));
1599
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved sessions to resume.\n'));
1286
1600
  return { handled: true };
1287
1601
  }
1288
1602
  console.log(chalk_1.default.hex('#5a9e6e')(`\n ↩ Resuming ${latest.id} — "${latest.title}" (${Math.floor(latest.history.length / 2)} turns)\n`));
@@ -1305,7 +1619,7 @@ async function handleReplCommand(input, c) {
1305
1619
  }
1306
1620
  if (input === ':history') {
1307
1621
  const turns = Math.floor(c.chatState.activeChatHistory.length / 2);
1308
- console.log(chalk_1.default.hex('#8a7768')(`\n Current session: ${turns} turn${turns !== 1 ? 's' : ''} in history.\n`));
1622
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Current session: ${turns} turn${turns !== 1 ? 's' : ''} in history.\n`));
1309
1623
  return { handled: true };
1310
1624
  }
1311
1625
  if (input === ':clear-history') {
@@ -1316,7 +1630,7 @@ async function handleReplCommand(input, c) {
1316
1630
  const title = input.startsWith(':save ') ? input.slice(':save '.length).trim() : undefined;
1317
1631
  const cs = c.chatState;
1318
1632
  if (!cs.activeChatId) {
1319
- console.log(chalk_1.default.hex('#8a7768')('\n No active session to save (--no-session mode).\n'));
1633
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No active session to save (--no-session mode).\n'));
1320
1634
  return { handled: true };
1321
1635
  }
1322
1636
  const session = await session_store_js_1.sessionStore.upsertSession(cs.projectRoot, cs.activeChatId, cs.activeChatHistory, title ?? cs.activeChatTitle);
@@ -1330,7 +1644,7 @@ async function handleReplCommand(input, c) {
1330
1644
  console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Deleted session ${id}\n`));
1331
1645
  if (id === c.chatState.activeChatId) {
1332
1646
  const newId = session_store_js_1.sessionStore.generateId();
1333
- console.log(chalk_1.default.hex('#8a7768')(` Starting new session: ${newId}\n`));
1647
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Starting new session: ${newId}\n`));
1334
1648
  return { handled: true, newChatId: newId, newHistory: [], newTitle: undefined };
1335
1649
  }
1336
1650
  }
@@ -1341,28 +1655,28 @@ async function handleReplCommand(input, c) {
1341
1655
  }
1342
1656
  // ── Model / API commands ─────────────────────────────────────────────────
1343
1657
  if (input === ':context') {
1344
- console.log(chalk_1.default.hex('#8a7768')(`\n Project: ${c.ctx.name} · ${c.ctx.language} · ${c.ctx.framework}`));
1345
- console.log(chalk_1.default.hex('#4e3d30')(` Root: ${c.ctx.root}\n`));
1658
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Project: ${c.ctx.name} · ${c.ctx.language} · ${c.ctx.framework}`));
1659
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Root: ${c.ctx.root}\n`));
1346
1660
  return { handled: true };
1347
1661
  }
1348
1662
  if (input === ':graph') {
1349
1663
  const summary = (0, context_js_1.loadGraphSummary)(c.ctx.root);
1350
1664
  if (!summary) {
1351
- console.log(chalk_1.default.hex('#8a7768')('\n No graph.json found. Run :graph refresh to extract.\n'));
1665
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No graph.json found. Run :graph refresh to extract.\n'));
1352
1666
  }
1353
1667
  else {
1354
1668
  console.log(chalk_1.default.hex('#cc785c').bold('\n Codebase Knowledge Graph\n'));
1355
- console.log(chalk_1.default.hex('#8a7768')(summary));
1669
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(summary));
1356
1670
  console.log();
1357
1671
  }
1358
1672
  return { handled: true };
1359
1673
  }
1360
1674
  if (input === ':viz' || input === ':dashboard') {
1361
- console.log(chalk_1.default.hex('#8a7768')('\n Generating dashboard…\n'));
1675
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Generating dashboard…\n'));
1362
1676
  try {
1363
1677
  const outPath = (0, index_js_1.generateDashboard)(c.ctx.root);
1364
1678
  console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Dashboard written to ${outPath}`));
1365
- console.log(chalk_1.default.hex('#8a7768')(' Opening in browser…\n'));
1679
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Opening in browser…\n'));
1366
1680
  (0, index_js_1.openDashboard)(outPath);
1367
1681
  }
1368
1682
  catch (e) {
@@ -1370,185 +1684,11 @@ async function handleReplCommand(input, c) {
1370
1684
  }
1371
1685
  return { handled: true };
1372
1686
  }
1373
- if (input === ':dream' || input === ':dream full') {
1374
- const full = input === ':dream full';
1375
- console.log(chalk_1.default.hex('#8a7768')('\n Dreaming — consolidating episodes…\n'));
1376
- try {
1377
- const { runDream } = await Promise.resolve().then(() => __importStar(require('../dream/dream.js')));
1378
- const { createResilientProvider } = await Promise.resolve().then(() => __importStar(require('../providers/resilient-factory.js')));
1379
- const provider = createResilientProvider({ model: c.providerConfig.model, apiKey: c.providerConfig.apiKey, baseUrl: c.providerConfig.baseUrl }, {}, c.display);
1380
- const res = await runDream({ projectRoot: c.ctx.root, provider, full });
1381
- if (res.skipped) {
1382
- console.log(chalk_1.default.hex('#cc9e5c')(` ⤳ Nothing to dream about — ${res.reason}.\n`));
1383
- }
1384
- else {
1385
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Dream written: ${res.path}`));
1386
- console.log(chalk_1.default.hex('#8a7768')(` Consolidated ${res.episodeCount} episode(s).\n`));
1387
- }
1388
- }
1389
- catch (e) {
1390
- console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1391
- }
1392
- return { handled: true };
1393
- }
1394
- if (input.startsWith(':research ')) {
1395
- const topic = input.slice(':research '.length).trim();
1396
- if (!topic) {
1397
- console.log(chalk_1.default.hex('#b15439')('\n Usage: :research <topic>\n'));
1398
- return { handled: true };
1399
- }
1400
- console.log(chalk_1.default.hex('#8a7768')(`\n Researching "${topic}"…\n`));
1401
- try {
1402
- const { runResearch } = await Promise.resolve().then(() => __importStar(require('../research/research.js')));
1403
- const { createResilientProvider } = await Promise.resolve().then(() => __importStar(require('../providers/resilient-factory.js')));
1404
- const provider = createResilientProvider({ model: c.providerConfig.model, apiKey: c.providerConfig.apiKey, baseUrl: c.providerConfig.baseUrl }, {}, c.display);
1405
- const res = await runResearch({
1406
- projectRoot: c.ctx.root,
1407
- topic,
1408
- provider,
1409
- context: c.ctx,
1410
- permissions: c.permissions,
1411
- display: c.display,
1412
- });
1413
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Research written: ${res.path}`));
1414
- console.log(chalk_1.default.hex('#8a7768')(` ${res.turns} turn(s) · ${res.toolCalls} tool call(s).\n`));
1415
- }
1416
- catch (e) {
1417
- console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1418
- }
1419
- return { handled: true };
1420
- }
1421
- if (input.startsWith(':council ')) {
1422
- let rest = input.slice(':council '.length).trim();
1423
- const wantsReader = /(^|\s)--reader(\s|$)/.test(rest);
1424
- rest = rest.replace(/(^|\s)--reader(\s|$)/g, ' ').trim();
1425
- const stray = rest.match(/(^|\s)(--\S+)/);
1426
- if (stray) {
1427
- console.log(chalk_1.default.hex('#b15439')(`\n ✗ Unknown flag: ${stray[2]}. Did you mean --reader?\n`));
1428
- return { handled: true };
1429
- }
1430
- const topic = rest;
1431
- if (!topic) {
1432
- console.log(chalk_1.default.hex('#b15439')('\n Usage: :council <topic> [--reader]\n'));
1433
- return { handled: true };
1434
- }
1435
- console.log(chalk_1.default.hex('#8a7768')(`\n Convening Ecclesia on "${topic}" — 5 independent agents…\n`));
1436
- try {
1437
- const { runCouncil } = await Promise.resolve().then(() => __importStar(require('../research/council.js')));
1438
- const { createResilientProvider } = await Promise.resolve().then(() => __importStar(require('../providers/resilient-factory.js')));
1439
- const synthesisProvider = createResilientProvider({ model: c.providerConfig.model, apiKey: c.providerConfig.apiKey, baseUrl: c.providerConfig.baseUrl }, {}, c.display);
1440
- const res = await runCouncil({
1441
- projectRoot: c.ctx.root,
1442
- topic,
1443
- synthesisProvider,
1444
- context: c.ctx,
1445
- permissions: c.permissions,
1446
- display: c.display,
1447
- });
1448
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Ecclesia verdict: ${res.path}`));
1449
- console.log(chalk_1.default.hex('#8a7768')(` ${res.panelSize} agents` + (res.agentFailures > 0 ? `, ${res.agentFailures} failed` : '') + `.\n`));
1450
- if (wantsReader) {
1451
- const { renderReaderFromMarkdown } = await Promise.resolve().then(() => __importStar(require('../viz/reader.js')));
1452
- const readerPath = renderReaderFromMarkdown(res.path);
1453
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Reader: ${readerPath}`));
1454
- console.log(chalk_1.default.hex('#8a7768')(' Opening in browser…\n'));
1455
- (0, index_js_1.openDashboard)(readerPath);
1456
- }
1457
- }
1458
- catch (e) {
1459
- console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1460
- }
1461
- return { handled: true };
1462
- }
1463
- if (input === ':rem') {
1464
- const dir = path.join(c.ctx.root, 'dreams');
1465
- let files = [];
1466
- try {
1467
- files = fs.readdirSync(dir).filter(f => f.endsWith('.md') && !f.startsWith('.')).sort().reverse();
1468
- }
1469
- catch { /* no dreams dir */ }
1470
- if (files.length === 0) {
1471
- console.log(chalk_1.default.hex('#8a7768')('\n No dreams yet. Run :dream after some work.\n'));
1472
- }
1473
- else {
1474
- console.log(chalk_1.default.hex('#cc785c').bold('\n Dreams\n'));
1475
- for (const f of files.slice(0, 20))
1476
- console.log(chalk_1.default.hex('#ede0cc')(` ${f}`));
1477
- // Prefer reconciled view if it exists
1478
- const reconciledPath = path.join(dir, '.reconciled.md');
1479
- const hasReconciled = fs.existsSync(reconciledPath);
1480
- if (hasReconciled) {
1481
- console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Reconciled memory (${files.length} dreams → projection):\n`));
1482
- console.log(chalk_1.default.hex('#ede0cc')(fs.readFileSync(reconciledPath, 'utf8')));
1483
- }
1484
- else {
1485
- const latest = path.join(dir, files[0]);
1486
- console.log(chalk_1.default.hex('#8a7768')(`\n Most recent: ${latest}\n`));
1487
- console.log(chalk_1.default.hex('#4e3d30')(fs.readFileSync(latest, 'utf8')));
1488
- if (files.length >= 3) {
1489
- console.log(chalk_1.default.hex('#8a7768')(' 💡 Run :dream to trigger memory reconciliation (≥3 dreams).\n'));
1490
- }
1491
- }
1492
- }
1493
- return { handled: true };
1494
- }
1495
- if (input === ':ruby' || input === ':ruby on' || input === ':ruby off') {
1496
- if (input === ':ruby') {
1497
- const state = c.rubyEnabled ? chalk_1.default.hex('#5a9e6e')('ON') : chalk_1.default.hex('#b15439')('OFF');
1498
- console.log(chalk_1.default.hex('#8a7768')(`\n Ruby Principle is ${state}. Use :ruby on / :ruby off to change.\n`));
1499
- return { handled: true };
1500
- }
1501
- const on = input === ':ruby on';
1502
- c.rubyEnabled = on;
1503
- const state = on ? chalk_1.default.hex('#5a9e6e')('ON') : chalk_1.default.hex('#b15439')('OFF');
1504
- console.log(chalk_1.default.hex('#8a7768')(`\n Ruby Principle is now ${state} for this session.\n`));
1505
- return { handled: true, rubyEnabled: on };
1506
- }
1507
- if (input === ':machina' || input === ':machina --html' || input === ':machina -html') {
1508
- const wantsHtml = input !== ':machina';
1509
- console.log(chalk_1.default.hex('#b15439')('\n ⚠ :machina reads several source files and writes a careful long-form result — extremely high token usage.\n'));
1510
- const { runMachina } = await Promise.resolve().then(() => __importStar(require('../machina/index.js')));
1511
- const res = runMachina({ outputRoot: c.ctx.root, writeHtml: wantsHtml });
1512
- console.log(res.terminalOutput);
1513
- if (res.htmlPath) {
1514
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Written: ${res.htmlPath}\n`));
1515
- }
1516
- return { handled: true };
1517
- }
1518
- if (input === ':design' || input.startsWith(':design ')) {
1519
- const { listDesignSlugs } = await Promise.resolve().then(() => __importStar(require('../agent/design.js')));
1520
- const slugs = listDesignSlugs(c.ctx.root);
1521
- const arg = input.slice(':design'.length).trim();
1522
- if (slugs.length === 0) {
1523
- console.log(chalk_1.default.hex('#b15439')('\n No design-systems/ folder found in this project.\n'));
1524
- return { handled: true };
1525
- }
1526
- if (arg === '' || arg === 'list') {
1527
- const current = process.env.AURA_DESIGN || 'terracotta';
1528
- console.log(chalk_1.default.hex('#cc785c').bold(`\n Design systems (${slugs.length}) — default: ${current}\n`));
1529
- console.log(chalk_1.default.hex('#ede0cc')(' ' + slugs.join(' ')));
1530
- console.log(chalk_1.default.hex('#8a7768')('\n :design <slug> sets the default · auto-applies on website/UI tasks.\n'));
1531
- return { handled: true };
1532
- }
1533
- if (arg === 'off') {
1534
- process.env.AURA_DESIGN = '__none__';
1535
- console.log(chalk_1.default.hex('#8a7768')('\n Design auto-injection disabled for this session.\n'));
1536
- return { handled: true };
1537
- }
1538
- if (slugs.includes(arg)) {
1539
- process.env.AURA_DESIGN = arg;
1540
- console.log(chalk_1.default.hex('#5a9e6e')(`\n Default design set to "${arg}" for this session.\n`));
1541
- }
1542
- else {
1543
- console.log(chalk_1.default.hex('#b15439')(`\n Unknown design "${arg}". Run :design to list available slugs.\n`));
1544
- }
1545
- return { handled: true };
1546
- }
1547
1687
  if (input === ':plans') {
1548
1688
  const { planStore } = await Promise.resolve().then(() => __importStar(require('../orchestration/plan-store.js')));
1549
1689
  const plans = await planStore.list();
1550
1690
  if (!plans.length) {
1551
- console.log(chalk_1.default.hex('#8a7768')('\n No execution plans found.\n'));
1691
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No execution plans found.\n'));
1552
1692
  }
1553
1693
  else {
1554
1694
  console.log(chalk_1.default.hex('#cc785c').bold('\n Execution plans:\n'));
@@ -1558,51 +1698,62 @@ async function handleReplCommand(input, c) {
1558
1698
  const statusColor = p.status === 'done' ? '#5a9e6e' : p.status === 'failed' ? '#b15439' : '#cc9e5c';
1559
1699
  console.log(` ${chalk_1.default.hex(statusColor)(p.status.padEnd(8))} ` +
1560
1700
  `${chalk_1.default.hex('#cc785c')(p.id.slice(0, 12).padEnd(14))} ` +
1561
- `${chalk_1.default.hex('#ede0cc')(p.goal.slice(0, 50).padEnd(51))} ` +
1562
- `${chalk_1.default.hex('#4e3d30')(`${p.steps.length}s · ${dur} · ${created}`)}`);
1701
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(p.goal.slice(0, 50).padEnd(51))} ` +
1702
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${p.steps.length}s · ${dur} · ${created}`)}`);
1563
1703
  }
1564
1704
  console.log();
1565
1705
  }
1566
1706
  return { handled: true };
1567
1707
  }
1568
1708
  if (input === ':graph refresh') {
1569
- console.log(chalk_1.default.hex('#8a7768')('\n Extracting codebase graph...\n'));
1709
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Refreshing codebase graph...\n'));
1710
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1570
1711
  try {
1571
- const perception = await (0, index_js_4.extractPerception)(c.ctx.root);
1572
- await (0, index_js_4.savePerception)(perception);
1573
- await (0, index_js_4.saveGraphForViz)(perception);
1574
- const nodeCount = perception.nodes.length;
1575
- const edgeCount = perception.edges.length;
1576
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Extracted ${nodeCount} nodes, ${edgeCount} edges.\n`));
1577
- }
1578
- catch (err) {
1579
- console.log(chalk_1.default.hex('#f85149')(` ✗ Extraction failed: ${err instanceof Error ? err.message : String(err)}\n`));
1712
+ execSync('python3 -c "' +
1713
+ 'import json,os,re,glob; ' +
1714
+ 'root=\\\"' + c.ctx.root + '/src\\\"; ' +
1715
+ 'print(\\\"Scanning\\\", root)" ', { stdio: 'inherit' });
1580
1716
  }
1717
+ catch { /* ignore */ }
1581
1718
  // Reload context graph
1582
1719
  c.ctx.graphSummary = (0, context_js_1.loadGraphSummary)(c.ctx.root);
1583
1720
  if (c.ctx.graphSummary) {
1584
1721
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Graph loaded and injected into context.\n'));
1585
1722
  }
1586
1723
  else {
1587
- console.log(chalk_1.default.hex('#8a7768')(' No graph.json found after refresh.\n'));
1724
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' No graph.json found after refresh. Run graphify extract first.\n'));
1588
1725
  }
1589
1726
  return { handled: true };
1590
1727
  }
1591
1728
  // ── Provider wizard command ────────────────────────────────────────────────
1592
1729
  if (input === ':provider' || input === '/provider') {
1593
- const cfg = await (0, provider_wizard_js_1.runProviderWizard)(c.rl);
1594
- if (cfg) {
1595
- // Update current session's provider without restart
1596
- runtimeConfig.model = cfg.model;
1597
- runtimeConfig.baseUrl = cfg.baseUrl;
1598
- runtimeConfig.apiKey = cfg.apiKey;
1599
- c.providerConfig.model = cfg.model;
1600
- c.providerConfig.baseUrl = cfg.baseUrl;
1601
- c.providerConfig.apiKey = cfg.apiKey;
1602
- // Keep resolved in sync
1603
- resolved.model = cfg.model;
1604
- resolved.baseUrl = cfg.baseUrl;
1605
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Now using ${cfg.provider} · ${cfg.model}`));
1730
+ // Fullscreen prompt approach was unreliable — wizard's readline and TUI's
1731
+ // stdin handler collide. Instead, stop TUI input, let readline own stdin
1732
+ // completely, then restart TUI input.
1733
+ const wasInputActive = tui_js_1.inputActive;
1734
+ if (wasInputActive)
1735
+ (0, tui_js_1.stopInput)();
1736
+ try {
1737
+ const cfg = await (0, provider_wizard_js_1.runProviderWizard)(undefined, undefined);
1738
+ if (cfg) {
1739
+ runtimeConfig.model = cfg.model;
1740
+ runtimeConfig.baseUrl = cfg.baseUrl;
1741
+ runtimeConfig.apiKey = cfg.apiKey;
1742
+ c.providerConfig.model = cfg.model;
1743
+ c.providerConfig.baseUrl = cfg.baseUrl;
1744
+ c.providerConfig.apiKey = cfg.apiKey;
1745
+ resolved.model = cfg.model;
1746
+ resolved.baseUrl = cfg.baseUrl;
1747
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex('#5a9e6e')(` ✓ Now using ${cfg.provider} · ${cfg.model}`));
1748
+ if (fileConfig.model && fileConfig.model !== cfg.model) {
1749
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ⚠ .aura.json pins model "${fileConfig.model}" — next startup in this project will use it.\n` +
1750
+ ` Remove the "model" field from .aura.json (or set it to ${cfg.model}) to keep this choice.`));
1751
+ }
1752
+ }
1753
+ }
1754
+ finally {
1755
+ if (wasInputActive)
1756
+ (0, tui_js_1.startInput)();
1606
1757
  }
1607
1758
  return { handled: true };
1608
1759
  }
@@ -1613,9 +1764,9 @@ async function handleReplCommand(input, c) {
1613
1764
  return acc;
1614
1765
  }, {});
1615
1766
  for (const [provider, models] of Object.entries(byProvider)) {
1616
- console.log(chalk_1.default.hex('#8a7768')(`\n ${provider}`));
1767
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n ${provider}`));
1617
1768
  for (const m of models) {
1618
- console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex('#4e3d30')(m.speed)}`);
1769
+ console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(m.speed)}`);
1619
1770
  }
1620
1771
  }
1621
1772
  console.log();
@@ -1638,7 +1789,22 @@ async function handleReplCommand(input, c) {
1638
1789
  const newKey = input.slice(sep.length).trim();
1639
1790
  runtimeConfig.apiKey = newKey;
1640
1791
  c.providerConfig.apiKey = newKey;
1641
- console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session.'));
1792
+ // Persist it so it survives across sessions (fixes "type the key every
1793
+ // time"). Save under the env-var name for the current provider, and also
1794
+ // set that env var live so getApiKey resolves it this session.
1795
+ const envName = globalCfg?.apiKeyEnv || envNameForModel(resolved.model ?? '');
1796
+ if (envName) {
1797
+ try {
1798
+ const p = (0, key_store_js_1.saveKey)(envName, newKey);
1799
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ API key saved as ${envName} → ${p} (persists across sessions).`));
1800
+ }
1801
+ catch (e) {
1802
+ console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session (could not persist: ' + String(e) + ').'));
1803
+ }
1804
+ }
1805
+ else {
1806
+ console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session.'));
1807
+ }
1642
1808
  return { handled: true };
1643
1809
  }
1644
1810
  if (input === '/clear' || input === '/reset') {
@@ -1650,42 +1816,10 @@ async function handleReplCommand(input, c) {
1650
1816
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Session stats reset'));
1651
1817
  return { handled: true };
1652
1818
  }
1653
- // ── Permission commands ──────────────────────────────────────────────────
1654
- if (input.startsWith(':approve ')) {
1655
- const raw = input.slice(':approve '.length).trim();
1656
- const mapped = raw === 'all' ? 'auto' : raw;
1657
- if (mapped !== 'auto' && mapped !== 'normal' && mapped !== 'read-only') {
1658
- console.log(chalk_1.default.hex('#b15439')(' ✗ Usage: :approve all | :approve normal | :approve read-only'));
1659
- return { handled: true };
1660
- }
1661
- c.permissions.setLevel(mapped);
1662
- console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Permission level set to ${mapped}`));
1663
- return { handled: true };
1664
- }
1665
- if (input === ':stats') {
1666
- // Episode-level stats (same as --stats CLI flag)
1667
- const episodes = await (0, episode_capture_js_1.loadEpisodes)(c.chatState.projectRoot);
1668
- const output = (0, stats_js_1.formatStats)(episodes);
1669
- for (const line of output.split('\n')) {
1670
- if (line.trim() === 'Aura Stats') {
1671
- console.log(chalk_1.default.hex('#cc785c').bold(line));
1672
- }
1673
- else if (line.includes(':')) {
1674
- const colonIdx = line.indexOf(':');
1675
- const label = line.slice(0, colonIdx + 1);
1676
- const value = line.slice(colonIdx + 1);
1677
- console.log(chalk_1.default.hex('#8a7768')(label) + chalk_1.default.hex('#ede0cc')(value));
1678
- }
1679
- else {
1680
- console.log(line);
1681
- }
1682
- }
1683
- return { handled: true };
1684
- }
1685
- if (input === '/stats' || input === '/usage' || input === ':usage') {
1819
+ if (input === '/stats' || input === '/usage') {
1686
1820
  const u = c.cumulative;
1687
1821
  const total = u.inputTokens + u.outputTokens;
1688
- console.log(chalk_1.default.hex('#8a7768')([
1822
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)([
1689
1823
  '',
1690
1824
  ` Session usage:`,
1691
1825
  ` Turns: ${u.turns}`,
@@ -1698,11 +1832,19 @@ async function handleReplCommand(input, c) {
1698
1832
  ].join('\n')));
1699
1833
  return { handled: true };
1700
1834
  }
1835
+ if (input === '/context') {
1836
+ const u = c.cumulative;
1837
+ const h = c.healthTracker.snapshot(u.inputTokens, u.outputTokens);
1838
+ h.turnCount = u.turns;
1839
+ h.toolCallCount = u.toolCalls;
1840
+ c.display.contextDashboard?.(h);
1841
+ return { handled: true };
1842
+ }
1701
1843
  // ── Workflow commands ──────────────────────────────────────────────────────
1702
1844
  if (input === ':workflows') {
1703
1845
  const workflows = await (0, engine_js_1.listWorkflows)();
1704
1846
  if (workflows.length === 0) {
1705
- console.log(chalk_1.default.hex('#8a7768')('\n No saved workflows.\n'));
1847
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved workflows.\n'));
1706
1848
  }
1707
1849
  else {
1708
1850
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved workflows:\n'));
@@ -1712,9 +1854,9 @@ async function handleReplCommand(input, c) {
1712
1854
  const totalSteps = ws.definition.steps.length;
1713
1855
  const statusColor = ws.status === 'done' ? '#5a9e6e' : ws.status === 'failed' ? '#b15439' : '#cc785c';
1714
1856
  console.log(` ${chalk_1.default.hex('#cc785c')(ws.definition.id.padEnd(24))} ` +
1715
- `${chalk_1.default.hex('#ede0cc')(ws.definition.name.slice(0, 36).padEnd(37))} ` +
1857
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(ws.definition.name.slice(0, 36).padEnd(37))} ` +
1716
1858
  `${chalk_1.default.hex(statusColor)(ws.status.padEnd(8))} ` +
1717
- `${chalk_1.default.hex('#4e3d30')(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
1859
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
1718
1860
  }
1719
1861
  console.log();
1720
1862
  }
@@ -1772,7 +1914,7 @@ async function handleReplCommand(input, c) {
1772
1914
  }
1773
1915
  else {
1774
1916
  console.log(chalk_1.default.hex('#b15439').bold(`\n ✗ ${finalState.outcome}`));
1775
- console.log(chalk_1.default.hex('#8a7768')(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1917
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1776
1918
  }
1777
1919
  return { handled: true };
1778
1920
  }
@@ -1810,7 +1952,7 @@ async function handleReplCommand(input, c) {
1810
1952
  }
1811
1953
  else {
1812
1954
  console.log(chalk_1.default.hex('#b15439').bold(`\n ✗ ${finalState.outcome}`));
1813
- console.log(chalk_1.default.hex('#8a7768')(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1955
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1814
1956
  }
1815
1957
  return { handled: true };
1816
1958
  }
@@ -1919,29 +2061,29 @@ async function runArchitectPlan(task, provider, ctx, display) {
1919
2061
  });
1920
2062
  // Display result
1921
2063
  console.log(chalk_1.default.hex('#cc785c').bold('\n Blueprint\n'));
1922
- console.log(chalk_1.default.hex('#ede0cc')(` Task: ${blueprint.task}`));
1923
- console.log(chalk_1.default.hex('#4e3d30')(` ID: ${blueprint.id}\n`));
2064
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` Task: ${blueprint.task}`));
2065
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ID: ${blueprint.id}\n`));
1924
2066
  if (blueprint.files.length > 0) {
1925
2067
  console.log(chalk_1.default.hex('#cc785c').bold(' Files:\n'));
1926
2068
  for (const f of blueprint.files) {
1927
2069
  console.log(` ${chalk_1.default.hex('#cc785c')(f.path)}`);
1928
- console.log(` ${chalk_1.default.hex('#8a7768')(f.purpose)}`);
2070
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(f.purpose)}`);
1929
2071
  if (f.exports.length > 0)
1930
- console.log(` ${chalk_1.default.hex('#4e3d30')(`exports: ${f.exports.join(', ')}`)}`);
2072
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`exports: ${f.exports.join(', ')}`)}`);
1931
2073
  if (f.interfaces.length > 0)
1932
- console.log(` ${chalk_1.default.hex('#4e3d30')(`interfaces: ${f.interfaces.join(', ')}`)}`);
2074
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`interfaces: ${f.interfaces.join(', ')}`)}`);
1933
2075
  }
1934
2076
  }
1935
2077
  if (blueprint.dataModels.length > 0) {
1936
2078
  console.log(chalk_1.default.hex('#cc785c').bold('\n Data Models:\n'));
1937
2079
  for (const dm of blueprint.dataModels) {
1938
- console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex('#8a7768')(dm.description)}`);
2080
+ console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(dm.description)}`);
1939
2081
  }
1940
2082
  }
1941
2083
  if (blueprint.risks.length > 0) {
1942
2084
  console.log(chalk_1.default.hex('#b15439').bold('\n Risks:\n'));
1943
2085
  for (const risk of blueprint.risks) {
1944
- console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex('#8a7768')(risk)}`);
2086
+ console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(risk)}`);
1945
2087
  }
1946
2088
  }
1947
2089
  console.log(chalk_1.default.hex('#5a9e6e')('\n Blueprint saved. No files were modified.'));
@@ -1961,16 +2103,20 @@ async function runOrchestratedTask(task, provider, ctx, display, forceOrchestrat
1961
2103
  // If --plan flag, show plan and ask for confirmation
1962
2104
  if (argv.plan === true) {
1963
2105
  display.showPlan?.(plan);
1964
- // Use a simple readline prompt for confirmation
1965
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2106
+ // Use a simple readline prompt for confirmation — reuse the REPL's
2107
+ // readline when one is active (second interface double-echoes and
2108
+ // pauses stdin on close).
2109
+ const sharedRl = (0, permissions_js_1.getSharedReadline)();
2110
+ const rl = sharedRl ?? readline.createInterface({ input: process.stdin, output: process.stdout });
1966
2111
  const approved = await new Promise(resolve => {
1967
2112
  rl.question(chalk_1.default.hex('#cc785c')('\n Run this plan? [y/N] '), answer => {
1968
- rl.close();
2113
+ if (!sharedRl)
2114
+ rl.close();
1969
2115
  resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
1970
2116
  });
1971
2117
  });
1972
2118
  if (!approved) {
1973
- console.log(chalk_1.default.hex('#4e3d30')(' Plan cancelled.\n'));
2119
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Plan cancelled.\n'));
1974
2120
  process.exit(0);
1975
2121
  }
1976
2122
  }
@@ -1994,51 +2140,49 @@ async function runOrchestratedTask(task, provider, ctx, display, forceOrchestrat
1994
2140
  display.summary(executedPlan.outcome, executedPlan.steps.length, 0);
1995
2141
  }
1996
2142
  const totalTokens = executedPlan.totalTokens ?? 0;
1997
- console.log(chalk_1.default.hex('#4e3d30')(` ↳ ${totalTokens.toLocaleString()} tokens · ${executedPlan.steps.length} steps · status: ${executedPlan.status}`));
2143
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ↳ ${totalTokens.toLocaleString()} tokens · ${executedPlan.steps.length} steps · status: ${executedPlan.status}`));
1998
2144
  }
1999
- function printUsageFooter(display, usage, costUsd, opts) {
2145
+ function printUsageFooter(display, usage, costUsd) {
2000
2146
  const total = usage.inputTokens + usage.outputTokens;
2001
- console.log(chalk_1.default.hex('#4e3d30')(` ↳ ${total.toLocaleString()} tokens (${usage.inputTokens.toLocaleString()} in / ${usage.outputTokens.toLocaleString()} out) · est. ${costUsd.toFixed(4)}`));
2002
- // Context window bar
2003
- if (opts?.model) {
2004
- const ctxWin = (0, factory_js_1.getContextWindow)(opts.model);
2005
- if (ctxWin) {
2006
- const real = opts.cumulativeTokens ?? 0;
2007
- let used;
2008
- let estimated;
2009
- if (real > 0) {
2010
- used = real;
2011
- estimated = false;
2012
- }
2013
- else {
2014
- // Provider didn't report usage — estimate from history character count
2015
- const histChars = (opts.history ?? []).reduce((n, m) => {
2016
- if ('content' in m && typeof m.content === 'string')
2017
- return n + m.content.length;
2018
- if ('results' in m)
2019
- return n + JSON.stringify(m.results).length;
2020
- return n;
2021
- }, 0);
2022
- used = Math.ceil(histChars / 4);
2023
- estimated = true;
2024
- }
2025
- if (used > 0) {
2026
- console.log((0, display_js_1.formatContextBar)(used, ctxWin, estimated));
2027
- }
2028
- }
2147
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ↳ ${total.toLocaleString()} tokens (${usage.inputTokens.toLocaleString()} in / ${usage.outputTokens.toLocaleString()} out) · est. $${costUsd.toFixed(4)}`));
2148
+ }
2149
+ /**
2150
+ * Read a task summary aloud (the "Aura talks back" half of the voice loop).
2151
+ * Best-effort: strips code/markdown, caps length so a long report doesn't
2152
+ * monologue, and never throws into the caller (a TTS/network failure must
2153
+ * not break a successful task).
2154
+ */
2155
+ async function speakSummary(text) {
2156
+ if (!text || !text.trim())
2157
+ return;
2158
+ // Strip fenced code blocks, inline code, markdown markers, and collapse
2159
+ // whitespace — TTS should read the prose, not backticks and hashes.
2160
+ const spoken = text
2161
+ .replace(/```[\s\S]*?```/g, ' (code omitted) ')
2162
+ .replace(/`[^`]*`/g, '')
2163
+ .replace(/[#*_>|]/g, ' ')
2164
+ .replace(/\s+/g, ' ')
2165
+ .trim()
2166
+ .slice(0, 600);
2167
+ if (!spoken)
2168
+ return;
2169
+ try {
2170
+ const { speakText } = await Promise.resolve().then(() => __importStar(require('../tools/dictate.js')));
2171
+ await speakText(spoken);
2029
2172
  }
2173
+ catch { /* speech is best-effort — never break the task on TTS failure */ }
2030
2174
  }
2031
2175
  function printHelp() {
2032
2176
  console.log(`
2033
- ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768')("— Aura Code: model-agnostic AI coding agent")}
2177
+ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)("— Aura Code: model-agnostic AI coding agent")}
2034
2178
 
2035
- ${chalk_1.default.hex('#4e3d30')('Usage:')}
2036
- aura ${chalk_1.default.hex('#8a7768')('"<task>"')} Run a task, then drop into REPL
2037
- aura ${chalk_1.default.hex('#8a7768')('"<task>" --once')} Run a single task and exit
2038
- aura Start interactive REPL
2039
- aura ${chalk_1.default.hex('#8a7768')('--models')} List available models
2179
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Usage:')}
2180
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('"<task>"')} Run a single task
2181
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('serve')} Start the HTTP API server
2182
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('--interactive')} Start interactive REPL
2183
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('--models')} List available models
2040
2184
 
2041
- ${chalk_1.default.hex('#4e3d30')('Options:')}
2185
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Options:')}
2042
2186
  --model, -m <id> Model to use (default: from ~/.config/aura-code/config.json)
2043
2187
  --api-key <key> API key (overrides env var)
2044
2188
  --base-url <url> Custom API endpoint (for Ollama, proxies, etc.)
@@ -2047,12 +2191,10 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2047
2191
  --cwd <path> Working directory (default: current)
2048
2192
  --models List all known model IDs
2049
2193
  --no-session Disable conversation history persistence
2050
- --once Run single task and exit (no REPL after)
2051
2194
  --new-session Force a fresh session (ignore any prior history)
2052
2195
  --resume [id] Resume latest session, or a specific session by ID
2053
2196
  --chat-id <id> Attach to a specific chat ID (creates if missing)
2054
2197
  --list-sessions List all saved sessions for this project
2055
- --setup Run the provider setup wizard before entering REPL
2056
2198
  --no-setup Skip the first-run setup wizard
2057
2199
  --reset-setup Wipe saved config and re-run the setup wizard
2058
2200
  --orchestrate Force multi-agent orchestration mode
@@ -2064,11 +2206,14 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2064
2206
  --verify Verify output after task; retry up to --max-verify-retries times
2065
2207
  --max-verify-retries <n> Max verification retries (default: 3)
2066
2208
  --test-command <cmd> Shell command run as part of verification (e.g. "npm test")
2067
- --max-turns <n> Max agent loop turns before stopping (default: 150)
2209
+ --max-turns <n> Max agent loop turns before stopping (default: sized by task shape)
2210
+ --moa Mixture of agents: parallel read-only domain perspectives + synthesis (exploratory tasks only)
2068
2211
  --analyze Mine session history for weakness patterns; save report
2069
- --stats Show episode stats — completion rate, avg duration, top model, tokens
2070
2212
  --propose-harness Generate system-prompt patches from weakness report
2071
2213
  --apply-harness <id> Apply a proposal patch; reverts if tests fail
2214
+ --doctor Scan Aura itself for issues (build, config, deps, env, git)
2215
+ --doctor --fix Scan and attempt auto-repairs (rebuild, restore, reinstall)
2216
+ --doctor --offline Skip the network version check
2072
2217
  --workflow <name> ... Create and run a sequential workflow with named steps
2073
2218
  --resume-workflow <id> Resume a paused/failed workflow from last completed step
2074
2219
  --workflows List all persisted workflows
@@ -2080,7 +2225,7 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2080
2225
  --fallback <model> Fallback model if primary exhausts retries (repeatable)
2081
2226
  --verify Enable post-task verification with automatic retries
2082
2227
 
2083
- ${chalk_1.default.hex('#4e3d30')('Resilience:')}
2228
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Resilience:')}
2084
2229
  All API calls automatically:
2085
2230
  1. Honour Retry-After / Google's retryDelay on 429s
2086
2231
  2. Back off with exponential + jitter (capped at 60s)
@@ -2088,7 +2233,7 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2088
2233
  4. Fail over to the next --fallback model if retries exhaust
2089
2234
  5. Pace requests when --rate-limit-rpm / --rate-limit-tpm is set
2090
2235
 
2091
- ${chalk_1.default.hex('#4e3d30')('Project config (.aura.json):')}
2236
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Project config (.aura.json):')}
2092
2237
  {
2093
2238
  "model": "claude-sonnet-4-5-20251001",
2094
2239
  "mode": "auto",
@@ -2099,8 +2244,8 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2099
2244
  "apiKeyEnv": "DEEPSEEK_API_KEY",
2100
2245
  "prefixes": ["deepseek/"],
2101
2246
  "models": [
2102
- { "id": "deepseek/deepseek-v4-flash", "name": "DeepSeek V4 Flash", "speed": "Fast · 1M context" },
2103
- { "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro", "speed": "Powerful · 1M context" }
2247
+ { "id": "deepseek/deepseek-chat", "name": "DeepSeek Chat", "speed": "Fast" },
2248
+ { "id": "deepseek/deepseek-reasoner", "name": "DeepSeek R1", "speed": "Reasoning" }
2104
2249
  ]
2105
2250
  }
2106
2251
  ],
@@ -2114,19 +2259,21 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2114
2259
  CLI flags always override .aura.json.
2115
2260
  Custom providers are OpenAI-compatible endpoints.
2116
2261
 
2117
- ${chalk_1.default.hex('#4e3d30')('Model examples:')}
2262
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Model examples:')}
2118
2263
  aura -m claude-opus-4-5-20251001 "refactor auth"
2119
2264
  aura -m gpt-4o "add unit tests"
2120
2265
  aura -m gemini-2.5-pro --rate-limit-rpm 20 "explain this codebase"
2121
2266
  aura -m ollama/llama3.2 "local model, no API key needed"
2122
2267
 
2123
- ${chalk_1.default.hex('#4e3d30')('API keys (set as env vars):')}
2268
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('API keys (set as env vars):')}
2124
2269
  ANTHROPIC_API_KEY Claude models
2125
2270
  OPENAI_API_KEY GPT models
2126
2271
  GOOGLE_API_KEY Gemini models
2127
2272
  XAI_API_KEY Grok models
2128
2273
  OPENROUTER_API_KEY OpenRouter (access to all models)
2129
2274
  XIAOMI_API_KEY Xiaomi MiMo
2275
+ ZHIPU_API_KEY Zhipu GLM (Z.ai) — glm-* general endpoint, zhipu-coding/<model> Coding Plan
2276
+ ZHIPU_BASE_URL Override Zhipu endpoint (default https://api.z.ai/api/paas/v4)
2130
2277
  AURA_MODEL Default model (overridden by --model)
2131
2278
  AURA_API_RPM Default request rate limit
2132
2279
  AURA_API_TPM Default token rate limit (Gemini)
@@ -2134,7 +2281,10 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2134
2281
  AURA_FALLBACK_MODEL Comma-separated fallback models
2135
2282
  `);
2136
2283
  }
2137
- if (argv._[0] === 'serve') {
2284
+ if (argv.doctor === true) {
2285
+ // --doctor runs async above and exits on completion; don't start main().
2286
+ }
2287
+ else if (argv._[0] === 'serve') {
2138
2288
  const port = Number(argv.port ?? argv.p ?? 7337);
2139
2289
  (0, index_js_2.startServer)({ port, cwd, model: argv.model, apiKey: argv['api-key'] ?? undefined, baseUrl: argv['base-url'] ?? undefined, open: argv.open !== false }).catch(e => { console.error('Fatal:', String(e)); process.exit(1); });
2140
2290
  }