aura-code 0.6.3 → 0.10.2

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 (266) 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 +15 -5
  27. package/dist/agent/loop.js +182 -45
  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 +140 -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 +20 -4
  62. package/dist/cli/display.js +77 -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 +1018 -851
  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 +1190 -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 +92 -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/providers/types.d.ts +1 -0
  172. package/dist/repl/queue.d.ts +22 -0
  173. package/dist/repl/queue.js +165 -0
  174. package/dist/repl/queue.js.map +1 -0
  175. package/dist/repl/side-channel.d.ts +23 -0
  176. package/dist/repl/side-channel.js +63 -0
  177. package/dist/repl/side-channel.js.map +1 -0
  178. package/dist/research/council.d.ts +2 -0
  179. package/dist/research/council.js +29 -4
  180. package/dist/research/council.js.map +1 -1
  181. package/dist/ruby/alternator.d.ts +11 -30
  182. package/dist/ruby/alternator.js +59 -38
  183. package/dist/ruby/alternator.js.map +1 -1
  184. package/dist/ruby/episode-capture.d.ts +0 -10
  185. package/dist/ruby/episode-capture.js +0 -33
  186. package/dist/ruby/episode-capture.js.map +1 -1
  187. package/dist/ruby/types.js +1 -1
  188. package/dist/ruby/types.js.map +1 -1
  189. package/dist/safety/path-jail.d.ts +17 -0
  190. package/dist/safety/path-jail.js +100 -0
  191. package/dist/safety/path-jail.js.map +1 -0
  192. package/dist/safety/permissions.d.ts +12 -21
  193. package/dist/safety/permissions.js +81 -216
  194. package/dist/safety/permissions.js.map +1 -1
  195. package/dist/safety/ssrf.d.ts +29 -0
  196. package/dist/safety/ssrf.js +185 -0
  197. package/dist/safety/ssrf.js.map +1 -0
  198. package/dist/server/index.js +64 -10
  199. package/dist/server/index.js.map +1 -1
  200. package/dist/setup/first-run.d.ts +0 -24
  201. package/dist/setup/first-run.js +10 -344
  202. package/dist/setup/first-run.js.map +1 -1
  203. package/dist/setup/key-store.d.ts +12 -0
  204. package/dist/setup/key-store.js +108 -0
  205. package/dist/setup/key-store.js.map +1 -0
  206. package/dist/setup/provider-registry.js +31 -18
  207. package/dist/setup/provider-registry.js.map +1 -1
  208. package/dist/setup/provider-test.d.ts +7 -0
  209. package/dist/setup/provider-test.js +73 -10
  210. package/dist/setup/provider-test.js.map +1 -1
  211. package/dist/setup/provider-wizard.d.ts +4 -1
  212. package/dist/setup/provider-wizard.js +92 -26
  213. package/dist/setup/provider-wizard.js.map +1 -1
  214. package/dist/setup/xiaomi.d.ts +1 -1
  215. package/dist/setup/xiaomi.js +3 -3
  216. package/dist/setup/xiaomi.js.map +1 -1
  217. package/dist/tools/browser.js +14 -10
  218. package/dist/tools/browser.js.map +1 -1
  219. package/dist/tools/clipboard.js +7 -1
  220. package/dist/tools/clipboard.js.map +1 -1
  221. package/dist/tools/dictate.d.ts +60 -0
  222. package/dist/tools/dictate.js +983 -0
  223. package/dist/tools/dictate.js.map +1 -0
  224. package/dist/tools/dictate_patched.js +1057 -0
  225. package/dist/tools/edit-file.js +10 -2
  226. package/dist/tools/edit-file.js.map +1 -1
  227. package/dist/tools/http-request.js +14 -4
  228. package/dist/tools/http-request.js.map +1 -1
  229. package/dist/tools/image-read.js +34 -2
  230. package/dist/tools/image-read.js.map +1 -1
  231. package/dist/tools/index.d.ts +16 -1
  232. package/dist/tools/index.js +59 -8
  233. package/dist/tools/index.js.map +1 -1
  234. package/dist/tools/mcp.js +12 -0
  235. package/dist/tools/mcp.js.map +1 -1
  236. package/dist/tools/read-file.js +10 -14
  237. package/dist/tools/read-file.js.map +1 -1
  238. package/dist/tools/telegram-audio-policy.d.ts +30 -0
  239. package/dist/tools/telegram-audio-policy.js +49 -0
  240. package/dist/tools/telegram-audio-policy.js.map +1 -0
  241. package/dist/tools/telegram-bot.js +1136 -537
  242. package/dist/tools/telegram-bot.js.map +1 -1
  243. package/dist/tools/telegram-voice.d.ts +18 -5
  244. package/dist/tools/telegram-voice.js +54 -12
  245. package/dist/tools/telegram-voice.js.map +1 -1
  246. package/dist/tools/telegram.d.ts +3 -3
  247. package/dist/tools/telegram.js +5 -84
  248. package/dist/tools/telegram.js.map +1 -1
  249. package/dist/tools/tools.d.ts +2 -14
  250. package/dist/tools/tools.js +79 -144
  251. package/dist/tools/tools.js.map +1 -1
  252. package/dist/tools/web-fetch.js +39 -7
  253. package/dist/tools/web-fetch.js.map +1 -1
  254. package/dist/tools/web-search.d.ts +0 -20
  255. package/dist/tools/web-search.js +40 -137
  256. package/dist/tools/web-search.js.map +1 -1
  257. package/dist/util/errors.js +1 -15
  258. package/dist/util/errors.js.map +1 -1
  259. package/dist/util/json-repair.d.ts +11 -0
  260. package/dist/util/json-repair.js +31 -0
  261. package/dist/util/json-repair.js.map +1 -0
  262. package/dist/util/rate-limiter.js +2 -8
  263. package/dist/util/rate-limiter.js.map +1 -1
  264. package/dist/viz/index.js +793 -792
  265. package/dist/viz/index.js.map +1 -1
  266. 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,304 @@ 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
+ // Update the TUI status line so the model change is immediately visible.
1112
+ (0, tui_js_1.setStatusLine)([test.name, newModel, permissionLevel].filter(Boolean).join(' · '));
1113
+ // Remember the choice for the next session. The saved baseUrl belongs to
1114
+ // the wizard-configured model — keep it only when switching back to that
1115
+ // model, otherwise the factory's per-provider default applies.
1116
+ try {
1117
+ (0, global_config_js_1.saveGlobalConfig)({
1118
+ provider: globalCfg?.provider ?? test.name,
1119
+ // loadGlobalConfig treats an empty apiKeyEnv as "not configured", so
1120
+ // resolve from the NEW model's provider — never inherit the old
1121
+ // provider's env name across a provider change (a silently wrong
1122
+ // apiKeyEnv on disk pairs the wrong key with the model on the next
1123
+ // startup).
1124
+ apiKeyEnv: (0, model_select_js_1.apiKeyEnvForModelSwitch)(newModel, prevModel ?? prevResolved, globalCfg?.apiKeyEnv),
1125
+ defaultModel: newModel,
1126
+ baseUrl: savedProvider && newModel === savedProvider.model ? savedProvider.baseUrl : undefined,
1127
+ });
1128
+ }
1129
+ catch { /* persistence is best-effort; the switch itself succeeded */ }
1126
1130
  return { ok: true };
1127
1131
  }
1128
1132
  catch (e) {
1129
1133
  runtimeConfig.model = prevModel; // rollback on error
1134
+ resolved.model = prevResolved;
1135
+ runtimeConfig.apiKey = prevApiKey;
1130
1136
  runtimeConfig.baseUrl = prevBaseUrl;
1137
+ resolved.baseUrl = prevResolvedBaseUrl;
1131
1138
  return { ok: false, err: String(e) };
1132
1139
  }
1133
1140
  }
@@ -1136,134 +1143,450 @@ function trySetModel(c, newModel) {
1136
1143
  * lets the user pick by number or type a custom model ID.
1137
1144
  */
1138
1145
  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
- });
1146
+ // Same stdin-collision fix as :provider (see handleReplCommand): the TUI's
1147
+ // raw-mode stdin handler competes with any prompt for keystrokes, so stop
1148
+ // TUI input entirely (this also hides the main input box), let a plain
1149
+ // readline own stdin, then restart TUI input once the selector exits.
1150
+ // enterFullscreenPrompt resets the scroll region so the selector's output
1151
+ // isn't crammed into the small scroll area (numbers were invisible because
1152
+ // the bottom block was overwriting them).
1153
+ const wasInputActive = tui_js_1.inputActive;
1154
+ if (wasInputActive) {
1155
+ (0, tui_js_1.stopInput)();
1156
+ (0, tui_js_1.enterFullscreenPrompt)();
1153
1157
  }
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;
1158
+ try {
1159
+ // Grouped list where section headers are display-only: they carry no
1160
+ // number, so numbering is gap-free and a header can never be selected.
1161
+ const rows = (0, model_select_js_1.buildModelRows)((0, factory_js_1.getAllModels)());
1162
+ console.log(chalk_1.default.hex('#cc785c').bold('\n Model Selector\n'));
1163
+ for (const r of rows) {
1164
+ if (r.kind === 'header') {
1165
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX).bold(` ── ${r.provider} ──`));
1167
1166
  }
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();
1167
+ else {
1168
+ 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)}`);
1169
+ }
1170
+ }
1171
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`\n Current: ${runtimeConfig.model}`));
1172
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Type a number, model ID, or press Enter to cancel:\n'));
1173
+ const answer = await new Promise(resolve => {
1174
+ const promptRl = c.rl;
1175
+ if (!promptRl) {
1176
+ // TUI mode: TUI input is stopped above, so a temporary readline can
1177
+ // own stdin without the two-readers-one-stream conflict.
1178
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1179
+ rl.question(chalk_1.default.hex('#cc785c')(' ▸ '), ans => { rl.close(); resolve(ans); });
1179
1180
  return;
1180
1181
  }
1181
- // Treat as a raw model ID
1182
- trySetModel(c, choice);
1183
- resolve();
1182
+ promptRl.question(chalk_1.default.hex('#cc785c')(' ▸ '), resolve);
1184
1183
  });
1185
- });
1184
+ const choice = answer.trim();
1185
+ if (!choice) {
1186
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Cancelled.\n'));
1187
+ return;
1188
+ }
1189
+ // Try as a number — every listed number is a real, selectable model
1190
+ const num = parseInt(choice, 10);
1191
+ if (!isNaN(num) && num >= 1 && num <= (0, model_select_js_1.modelCount)(rows)) {
1192
+ trySetModel(c, (0, model_select_js_1.modelIdForNumber)(rows, num));
1193
+ return;
1194
+ }
1195
+ // Treat as a raw model ID
1196
+ trySetModel(c, choice);
1197
+ }
1198
+ finally {
1199
+ if (wasInputActive) {
1200
+ (0, tui_js_1.exitFullscreenPrompt)();
1201
+ (0, tui_js_1.startInput)();
1202
+ }
1203
+ }
1186
1204
  }
1187
1205
  async function handleReplCommand(input, c) {
1188
1206
  const unhandled = { handled: false };
1207
+ // ── :q — Task queue (with subcommands, keep bare :q as quit) ─────────────
1208
+ if (input.startsWith(':q ')) {
1209
+ const sub = input.slice(3).trimStart();
1210
+ const { addToQueue, loadQueue, removeFromQueue, clearQueue, runQueueItem, formatQueue } = await Promise.resolve().then(() => __importStar(require('../repl/queue.js')));
1211
+ if (sub.startsWith('add ')) {
1212
+ const prompt = sub.slice(4).trim();
1213
+ if (!prompt) {
1214
+ c.display.warning('Usage: :q add <prompt> -- add a task to the queue.');
1215
+ return { handled: true };
1216
+ }
1217
+ const item = addToQueue(prompt);
1218
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Queued #${loadQueue().length}: "${prompt.slice(0, 60)}"\n`));
1219
+ return { handled: true };
1220
+ }
1221
+ if (sub === 'list') {
1222
+ const items = loadQueue();
1223
+ console.log(formatQueue(items));
1224
+ return { handled: true };
1225
+ }
1226
+ if (sub.startsWith('run ')) {
1227
+ const n = parseInt(sub.slice(4).trim(), 10);
1228
+ if (isNaN(n) || n < 1) {
1229
+ c.display.warning('Usage: :q run <number> — run the task at that position (see :q list).');
1230
+ return { handled: true };
1231
+ }
1232
+ const items = loadQueue();
1233
+ if (n > items.length) {
1234
+ c.display.warning(`Queue only has ${items.length} item(s).`);
1235
+ return { handled: true };
1236
+ }
1237
+ c.display.agentThinking();
1238
+ const result = await runQueueItem(n - 1, buildProvider(c.display), c.ctx, c.permissions, c.display);
1239
+ if (!result) {
1240
+ c.display.warning('Could not run that item.');
1241
+ return { handled: true };
1242
+ }
1243
+ c.display.success(`Queue item #${n}: ${result.success ? 'done' : 'failed'}`);
1244
+ if (result.output) {
1245
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` ${result.output.slice(0, 240)}`));
1246
+ }
1247
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${result.turns} turn(s) · ${result.toolCalls} tool call(s).\n`));
1248
+ return { handled: true };
1249
+ }
1250
+ if (sub.startsWith('drop ')) {
1251
+ const n = parseInt(sub.slice(5).trim(), 10);
1252
+ if (isNaN(n) || n < 1) {
1253
+ c.display.warning('Usage: :q drop <number> — remove the task at that position.');
1254
+ return { handled: true };
1255
+ }
1256
+ const removed = removeFromQueue(n - 1);
1257
+ if (!removed) {
1258
+ c.display.warning(`No item at position ${n}.`);
1259
+ return { handled: true };
1260
+ }
1261
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Dropped #${n}: "${removed.prompt.slice(0, 60)}"\n`));
1262
+ return { handled: true };
1263
+ }
1264
+ if (sub === 'clear') {
1265
+ const count = loadQueue().length;
1266
+ if (count === 0) {
1267
+ c.display.warning('Queue is already empty.');
1268
+ return { handled: true };
1269
+ }
1270
+ clearQueue();
1271
+ console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Queue cleared (${count} item(s) removed).\n`));
1272
+ return { handled: true };
1273
+ }
1274
+ c.display.warning('Usage: :q add <prompt> | :q list | :q run <n> | :q drop <n> | :q clear');
1275
+ return { handled: true };
1276
+ }
1189
1277
  if (input === ':quit' || input === ':q' || input === '/exit') {
1190
1278
  process.exit(0);
1191
1279
  }
1280
+ if (input === ':speak') {
1281
+ speakEnabled = !speakEnabled;
1282
+ console.log(chalk_1.default.hex(speakEnabled ? '#5a9e6e' : '#a68a2a')(` 🔊 Voice replies ${speakEnabled ? 'ON — Aura will read its answers aloud' : 'OFF'}.\n`));
1283
+ return { handled: true };
1284
+ }
1285
+ // :approve — flip the session into auto-approve (no per-command y/N prompt).
1286
+ // :approve → toggle auto ⇄ normal
1287
+ // :approve all → auto (approve everything for this session)
1288
+ // :approve off → back to normal (confirm destructive commands again)
1289
+ // Dangerous commands are still blocked either way.
1290
+ if (input === ':approve' || input === ':approve all' || input === ':approve off') {
1291
+ const cur = c.permissions.getLevel();
1292
+ let next;
1293
+ if (input === ':approve off')
1294
+ next = 'normal';
1295
+ else if (input === ':approve all')
1296
+ next = 'auto';
1297
+ else
1298
+ next = cur === 'auto' ? 'normal' : 'auto';
1299
+ c.permissions.setLevel(next);
1300
+ if (next === 'auto') {
1301
+ 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'));
1302
+ }
1303
+ else {
1304
+ console.log(chalk_1.default.hex('#5a9e6e')(' 🔒 Auto-approve OFF — destructive commands will ask for confirmation again.\n'));
1305
+ }
1306
+ return { handled: true };
1307
+ }
1308
+ if (input === ':dream' || input === ':dream full') {
1309
+ const full = input === ':dream full';
1310
+ const { runDream } = await Promise.resolve().then(() => __importStar(require('../dream/dream.js')));
1311
+ c.display.agentThinking();
1312
+ const res = await runDream({ projectRoot: c.ctx.root, provider: buildProvider(c.display), full });
1313
+ if (res.skipped) {
1314
+ c.display.warning(res.providerError
1315
+ ? `Dream skipped (episodes preserved): ${res.providerError}`
1316
+ : (res.reason ?? 'Nothing to consolidate.'));
1317
+ }
1318
+ else {
1319
+ c.display.success(`Dream written: ${res.path} (${res.episodeCount} episodes${full ? ', full run' : ''})`);
1320
+ if (res.reconciled)
1321
+ c.display.success('Reconciliation also ran (>=3 dreams exist) -> dreams/.reconciled.md');
1322
+ }
1323
+ return { handled: true };
1324
+ }
1325
+ if (input.startsWith(':research ') || input === ':research') {
1326
+ const topic = input.slice(':research '.length).trim();
1327
+ if (!topic) {
1328
+ c.display.warning('Usage: :research <topic> -- runs a multi-step research pass and saves to research/*.md.');
1329
+ return { handled: true };
1330
+ }
1331
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Researching "${topic}"…\n`));
1332
+ try {
1333
+ const { runResearch } = await Promise.resolve().then(() => __importStar(require('../research/research.js')));
1334
+ const res = await runResearch({
1335
+ projectRoot: c.ctx.root,
1336
+ topic,
1337
+ provider: buildProvider(c.display),
1338
+ context: c.ctx,
1339
+ permissions: c.permissions,
1340
+ display: c.display,
1341
+ });
1342
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Research written: ${res.path}`));
1343
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${res.turns} turn(s) · ${res.toolCalls} tool call(s).\n`));
1344
+ }
1345
+ catch (e) {
1346
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1347
+ }
1348
+ return { handled: true };
1349
+ }
1350
+ if (input === ':confessions') {
1351
+ const { listConfessions } = await Promise.resolve().then(() => __importStar(require('../agent/confess.js')));
1352
+ const confs = listConfessions();
1353
+ if (confs.length === 0) {
1354
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No confessions yet. Run :confess after a high-token episode.\n'));
1355
+ }
1356
+ else {
1357
+ console.log(chalk_1.default.hex('#cc785c').bold(`\n ${confs.length} confession(s):\n`));
1358
+ for (const c of confs) {
1359
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${c.file}`));
1360
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${c.tokens.toLocaleString()} tokens burned → ${c.lesson.slice(0, 100)}`));
1361
+ }
1362
+ console.log('');
1363
+ }
1364
+ return { handled: true };
1365
+ }
1366
+ if (input === ':confess') {
1367
+ const { runConfession, findEpisodeToConfess } = await Promise.resolve().then(() => __importStar(require('../agent/confess.js')));
1368
+ const targetEp = findEpisodeToConfess(c.ctx.root);
1369
+ if (!targetEp) {
1370
+ console.log(chalk_1.default.hex('#cc9e5c')('\n No anomalous episode found. Confession is fully automatic — the system alone decides what to confess.\n'));
1371
+ return { handled: true };
1372
+ }
1373
+ 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`));
1374
+ try {
1375
+ // Use a different model than the one that made the mistake
1376
+ const confessorModel = targetEp.model.startsWith('deepseek') ? 'glm-5.2' : 'deepseek/deepseek-chat';
1377
+ const { createProvider } = await Promise.resolve().then(() => __importStar(require('../providers/factory.js')));
1378
+ const provider = createProvider({ model: confessorModel });
1379
+ const result = await runConfession({
1380
+ projectRoot: c.ctx.root,
1381
+ episodeId: targetEp.id,
1382
+ provider,
1383
+ });
1384
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Confession written: ${result.path}`));
1385
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Tokens burned: ${result.tokensBurned.toLocaleString()} | Confession cost: ${result.tokensSpent.toLocaleString()} (${confessorModel})`));
1386
+ console.log(chalk_1.default.hex('#cc9e6c')(' Permanent lesson:'));
1387
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` "${result.lesson}"\n`));
1388
+ }
1389
+ catch (e) {
1390
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1391
+ }
1392
+ return { handled: true };
1393
+ }
1394
+ if (input === ':rem') {
1395
+ const { getReconciledOrLatest } = await Promise.resolve().then(() => __importStar(require('../dream/dream.js')));
1396
+ const res = getReconciledOrLatest(c.ctx.root);
1397
+ if (!res) {
1398
+ c.display.warning('No dreams yet. Run :dream first.');
1399
+ }
1400
+ else {
1401
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n ${res.isReconciled ? 'Reconciled projection' : 'Latest dream (not yet reconciled)'}:\n`));
1402
+ console.log(res.content);
1403
+ }
1404
+ return { handled: true };
1405
+ }
1406
+ // ── :mine — Baby Ruby experience mining (src/mining/). The base pass is
1407
+ // zero-LLM (pure clustering over episodes/*.json); --refine additionally
1408
+ // runs Papa Ruby, one local-model call per qualifying concept, appending
1409
+ // accepted lessons to training-data/<date>.jsonl.
1410
+ if (input === ':mine' || input === ':mine --refine') {
1411
+ const refine = input.endsWith('--refine');
1412
+ const { mineExperience } = await Promise.resolve().then(() => __importStar(require('../mining/extract.js')));
1413
+ const mined = await mineExperience(c.ctx.root);
1414
+ if (mined.episodeCount === 0) {
1415
+ c.display.warning('No episodes to mine yet — run some tasks first.');
1416
+ return { handled: true };
1417
+ }
1418
+ console.log(chalk_1.default.hex('#cc785c').bold(`\n Mined ${mined.concepts.length} concept(s) from ${mined.episodeCount} episode(s) (${mined.unclustered} unclustered):\n`));
1419
+ for (const con of mined.concepts.slice(0, 15)) {
1420
+ 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})`));
1421
+ if (con.keywords.length > 0)
1422
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` keywords: ${con.keywords.join(', ')}`));
1423
+ }
1424
+ if (mined.concepts.length > 15) {
1425
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` … and ${mined.concepts.length - 15} more.`));
1426
+ }
1427
+ if (refine) {
1428
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Refining with the local Ruby model (Papa Ruby)…'));
1429
+ const { refineConcepts } = await Promise.resolve().then(() => __importStar(require('../mining/refine.js')));
1430
+ const res = await refineConcepts({ projectRoot: c.ctx.root, concepts: mined.concepts });
1431
+ if (res.accepted.length > 0) {
1432
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ ${res.accepted.length} training example(s) appended: ${res.outputPath}`));
1433
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ${res.rejected} rejected, ${res.skipped} below the confidence/frequency gate.\n`));
1434
+ }
1435
+ else {
1436
+ c.display.warning(`No concepts survived refinement — ${res.rejected} rejected, ${res.skipped} below the confidence/frequency gate.`);
1437
+ }
1438
+ }
1439
+ else if (mined.concepts.length > 0) {
1440
+ 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'));
1441
+ }
1442
+ return { handled: true };
1443
+ }
1444
+ if (input === ':doctor' || input.startsWith(':doctor')) {
1445
+ const fix = input.includes('--fix');
1446
+ const offline = input.includes('--offline');
1447
+ c.display.agentThinking();
1448
+ const report = await (0, index_js_5.runDoctor)({ projectRoot: c.ctx.root, fix, offline });
1449
+ if (typeof tui_js_1.writeOutput === 'function') {
1450
+ (0, tui_js_1.writeOutput)((0, index_js_5.formatDoctorReport)(report));
1451
+ }
1452
+ else {
1453
+ console.log((0, index_js_5.formatDoctorReport)(report));
1454
+ }
1455
+ return { handled: true };
1456
+ }
1457
+ if (input.startsWith(':machina ') || input === ':machina') {
1458
+ const machinaTask = input.slice(':machina '.length).trim();
1459
+ if (!machinaTask) {
1460
+ c.display.warning('Usage: :machina <task> -- runs the task with self-verification (file/test checks + auto-retry).');
1461
+ return { handled: true };
1462
+ }
1463
+ const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
1464
+ const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
1465
+ const testCommand = cliTestCommand ?? fileConfig.testCommand;
1466
+ const wrapperResult = await runWithVerification({
1467
+ loopOpts: {
1468
+ provider: buildProvider(c.display), task: machinaTask,
1469
+ context: c.ctx, permissions: c.permissions, display: c.display,
1470
+ initialHistory: c.chatState.activeChatHistory,
1471
+ maxTurns: resolved.maxTurns,
1472
+ spawnConfig: {
1473
+ apiKey: c.providerConfig.apiKey,
1474
+ baseUrl: c.providerConfig.baseUrl,
1475
+ },
1476
+ sessionPath: c.sessionPath,
1477
+ },
1478
+ config: { enabled: true, maxRetries, testCommand },
1479
+ projectRoot: c.ctx.root,
1480
+ display: c.display,
1481
+ });
1482
+ const mResult = wrapperResult.loopResult;
1483
+ if (mResult.success) {
1484
+ c.display.summary(mResult.summary, mResult.turns, mResult.toolCallCount);
1485
+ printUsageFooter(c.display, mResult.usage, mResult.costUsd);
1486
+ }
1487
+ else {
1488
+ c.display.error(mResult.summary);
1489
+ }
1490
+ return { handled: true, newHistory: mResult.history };
1491
+ }
1492
+ if (input.startsWith(':council ') || input === ':council') {
1493
+ const councilTask = input.slice(':council '.length).trim();
1494
+ if (!councilTask) {
1495
+ c.display.warning('Usage: :council <task> -- runs 2-3 read-only domain specialists in parallel, then synthesizes their reports.');
1496
+ return { handled: true };
1497
+ }
1498
+ const { runMixtureOfAgents } = await Promise.resolve().then(() => __importStar(require('../agent/mixture.js')));
1499
+ const councilResult = await runMixtureOfAgents({
1500
+ provider: buildProvider(c.display), task: councilTask, context: c.ctx, display: c.display,
1501
+ });
1502
+ if (councilResult.success) {
1503
+ c.display.summary(councilResult.summary, councilResult.turns, councilResult.toolCallCount);
1504
+ printUsageFooter(c.display, councilResult.usage, councilResult.costUsd);
1505
+ }
1506
+ else {
1507
+ c.display.error(councilResult.summary);
1508
+ }
1509
+ return { handled: true };
1510
+ }
1511
+ // ── :ecclesia — 5-agent independent research council (research/council.ts).
1512
+ // Distinct from :council above (mixture-of-agents over the current task):
1513
+ // the Ecclesia runs N agents that research a topic WITHOUT seeing each
1514
+ // other's findings, then one synthesis call reconciles them into a verdict.
1515
+ if (input.startsWith(':ecclesia ') || input === ':ecclesia') {
1516
+ let topic = input.slice(':ecclesia '.length).trim();
1517
+ if (!topic) {
1518
+ c.display.warning('Usage: :ecclesia <topic> [--panel <model>] [--seats <n>] -- N independent research agents (default 5) + a synthesis verdict, saved to council/*.md|.html.');
1519
+ return { handled: true };
1520
+ }
1521
+ let panelModel;
1522
+ let panelSize;
1523
+ const panelMatch = topic.match(/\s--panel\s+(\S+)/);
1524
+ if (panelMatch) {
1525
+ panelModel = panelMatch[1];
1526
+ topic = topic.replace(panelMatch[0], '').trim();
1527
+ }
1528
+ const seatsMatch = topic.match(/\s--seats\s+(\d+)/);
1529
+ if (seatsMatch) {
1530
+ panelSize = Number(seatsMatch[1]);
1531
+ topic = topic.replace(seatsMatch[0], '').trim();
1532
+ }
1533
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Convening the Ecclesia on "${topic}"…\n`));
1534
+ try {
1535
+ const { runCouncil } = await Promise.resolve().then(() => __importStar(require('../research/council.js')));
1536
+ const res = await runCouncil({
1537
+ projectRoot: c.ctx.root, topic,
1538
+ synthesisProvider: buildProvider(c.display),
1539
+ context: c.ctx, permissions: c.permissions, display: c.display,
1540
+ panelSize, panelModel,
1541
+ configuredModel: c.providerConfig.model,
1542
+ });
1543
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Ecclesia verdict written: ${res.path}`));
1544
+ console.log(chalk_1.default.hex('#5a9e6e')(` HTML: ${res.htmlPath}`));
1545
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` ${res.panelSize} seats on ${res.panelModel}.`));
1546
+ if (res.agentFailures > 0) {
1547
+ c.display.warning(`${res.agentFailures} of ${res.panelSize} panel agent(s) failed — verdict is based on the rest.`);
1548
+ }
1549
+ }
1550
+ catch (e) {
1551
+ console.log(chalk_1.default.hex('#b15439')(` ✗ ${String(e)}\n`));
1552
+ }
1553
+ return { handled: true };
1554
+ }
1555
+ // ── :btw — Side channel question (read-only, no history) ────────────────
1556
+ if (input.startsWith(':btw ')) {
1557
+ const question = input.slice(5).trim();
1558
+ if (!question) {
1559
+ c.display.warning('Usage: :btw <question> — ask a quick side question without interrupting the current task.');
1560
+ return { handled: true };
1561
+ }
1562
+ const { runBtwQuery, renderBtwAnswer } = await Promise.resolve().then(() => __importStar(require('../repl/side-channel.js')));
1563
+ c.display.agentThinking();
1564
+ const result = await runBtwQuery(question, buildProvider(c.display), c.ctx);
1565
+ console.log(renderBtwAnswer(result.answer, result.tokens));
1566
+ return { handled: true };
1567
+ }
1192
1568
  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')));
1569
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(help_data_js_1.HELP_TEXT.join('\n')));
1247
1570
  return { handled: true };
1248
1571
  }
1249
1572
  // ── Session commands ─────────────────────────────────────────────────────
1250
1573
  if (input === ':id') {
1251
1574
  const cs = c.chatState;
1252
1575
  if (cs.activeChatId) {
1253
- console.log(chalk_1.default.hex('#8a7768')(`\n Chat ID: ${chalk_1.default.hex('#cc785c')(cs.activeChatId)}`));
1576
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Chat ID: ${chalk_1.default.hex('#cc785c')(cs.activeChatId)}`));
1254
1577
  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`));
1578
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Title: ${cs.activeChatTitle}`));
1579
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Turns: ${Math.floor(cs.activeChatHistory.length / 2)}\n`));
1257
1580
  }
1258
1581
  else {
1259
- console.log(chalk_1.default.hex('#8a7768')('\n No active session (--no-session mode).\n'));
1582
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No active session (--no-session mode).\n'));
1260
1583
  }
1261
1584
  return { handled: true };
1262
1585
  }
1263
1586
  if (input === ':sessions') {
1264
1587
  const sessions = session_store_js_1.sessionStore.listSessions(c.chatState.projectRoot);
1265
1588
  if (sessions.length === 0) {
1266
- console.log(chalk_1.default.hex('#8a7768')('\n No saved sessions.\n'));
1589
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved sessions.\n'));
1267
1590
  }
1268
1591
  else {
1269
1592
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved sessions:\n'));
@@ -1272,8 +1595,8 @@ async function handleReplCommand(input, c) {
1272
1595
  const turns = Math.floor(s.history.length / 2);
1273
1596
  const marker = s.id === c.chatState.activeChatId ? chalk_1.default.hex('#5a9e6e')(' ← current') : '';
1274
1597
  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}`);
1598
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(s.title.slice(0, 40).padEnd(41))} ` +
1599
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${turns}t · ${updated}`)}${marker}`);
1277
1600
  }
1278
1601
  console.log();
1279
1602
  }
@@ -1282,7 +1605,7 @@ async function handleReplCommand(input, c) {
1282
1605
  if (input === ':resume' || input === ':resume ') {
1283
1606
  const latest = session_store_js_1.sessionStore.findLatestSession(c.chatState.projectRoot);
1284
1607
  if (!latest) {
1285
- console.log(chalk_1.default.hex('#8a7768')('\n No saved sessions to resume.\n'));
1608
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved sessions to resume.\n'));
1286
1609
  return { handled: true };
1287
1610
  }
1288
1611
  console.log(chalk_1.default.hex('#5a9e6e')(`\n ↩ Resuming ${latest.id} — "${latest.title}" (${Math.floor(latest.history.length / 2)} turns)\n`));
@@ -1305,7 +1628,7 @@ async function handleReplCommand(input, c) {
1305
1628
  }
1306
1629
  if (input === ':history') {
1307
1630
  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`));
1631
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Current session: ${turns} turn${turns !== 1 ? 's' : ''} in history.\n`));
1309
1632
  return { handled: true };
1310
1633
  }
1311
1634
  if (input === ':clear-history') {
@@ -1316,7 +1639,7 @@ async function handleReplCommand(input, c) {
1316
1639
  const title = input.startsWith(':save ') ? input.slice(':save '.length).trim() : undefined;
1317
1640
  const cs = c.chatState;
1318
1641
  if (!cs.activeChatId) {
1319
- console.log(chalk_1.default.hex('#8a7768')('\n No active session to save (--no-session mode).\n'));
1642
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No active session to save (--no-session mode).\n'));
1320
1643
  return { handled: true };
1321
1644
  }
1322
1645
  const session = await session_store_js_1.sessionStore.upsertSession(cs.projectRoot, cs.activeChatId, cs.activeChatHistory, title ?? cs.activeChatTitle);
@@ -1330,7 +1653,7 @@ async function handleReplCommand(input, c) {
1330
1653
  console.log(chalk_1.default.hex('#5a9e6e')(`\n ✓ Deleted session ${id}\n`));
1331
1654
  if (id === c.chatState.activeChatId) {
1332
1655
  const newId = session_store_js_1.sessionStore.generateId();
1333
- console.log(chalk_1.default.hex('#8a7768')(` Starting new session: ${newId}\n`));
1656
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Starting new session: ${newId}\n`));
1334
1657
  return { handled: true, newChatId: newId, newHistory: [], newTitle: undefined };
1335
1658
  }
1336
1659
  }
@@ -1341,28 +1664,28 @@ async function handleReplCommand(input, c) {
1341
1664
  }
1342
1665
  // ── Model / API commands ─────────────────────────────────────────────────
1343
1666
  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`));
1667
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n Project: ${c.ctx.name} · ${c.ctx.language} · ${c.ctx.framework}`));
1668
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` Root: ${c.ctx.root}\n`));
1346
1669
  return { handled: true };
1347
1670
  }
1348
1671
  if (input === ':graph') {
1349
1672
  const summary = (0, context_js_1.loadGraphSummary)(c.ctx.root);
1350
1673
  if (!summary) {
1351
- console.log(chalk_1.default.hex('#8a7768')('\n No graph.json found. Run :graph refresh to extract.\n'));
1674
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No graph.json found. Run :graph refresh to extract.\n'));
1352
1675
  }
1353
1676
  else {
1354
1677
  console.log(chalk_1.default.hex('#cc785c').bold('\n Codebase Knowledge Graph\n'));
1355
- console.log(chalk_1.default.hex('#8a7768')(summary));
1678
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(summary));
1356
1679
  console.log();
1357
1680
  }
1358
1681
  return { handled: true };
1359
1682
  }
1360
1683
  if (input === ':viz' || input === ':dashboard') {
1361
- console.log(chalk_1.default.hex('#8a7768')('\n Generating dashboard…\n'));
1684
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Generating dashboard…\n'));
1362
1685
  try {
1363
1686
  const outPath = (0, index_js_1.generateDashboard)(c.ctx.root);
1364
1687
  console.log(chalk_1.default.hex('#5a9e6e')(` ✓ Dashboard written to ${outPath}`));
1365
- console.log(chalk_1.default.hex('#8a7768')(' Opening in browser…\n'));
1688
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' Opening in browser…\n'));
1366
1689
  (0, index_js_1.openDashboard)(outPath);
1367
1690
  }
1368
1691
  catch (e) {
@@ -1370,185 +1693,11 @@ async function handleReplCommand(input, c) {
1370
1693
  }
1371
1694
  return { handled: true };
1372
1695
  }
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
1696
  if (input === ':plans') {
1548
1697
  const { planStore } = await Promise.resolve().then(() => __importStar(require('../orchestration/plan-store.js')));
1549
1698
  const plans = await planStore.list();
1550
1699
  if (!plans.length) {
1551
- console.log(chalk_1.default.hex('#8a7768')('\n No execution plans found.\n'));
1700
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No execution plans found.\n'));
1552
1701
  }
1553
1702
  else {
1554
1703
  console.log(chalk_1.default.hex('#cc785c').bold('\n Execution plans:\n'));
@@ -1558,51 +1707,70 @@ async function handleReplCommand(input, c) {
1558
1707
  const statusColor = p.status === 'done' ? '#5a9e6e' : p.status === 'failed' ? '#b15439' : '#cc9e5c';
1559
1708
  console.log(` ${chalk_1.default.hex(statusColor)(p.status.padEnd(8))} ` +
1560
1709
  `${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}`)}`);
1710
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(p.goal.slice(0, 50).padEnd(51))} ` +
1711
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${p.steps.length}s · ${dur} · ${created}`)}`);
1563
1712
  }
1564
1713
  console.log();
1565
1714
  }
1566
1715
  return { handled: true };
1567
1716
  }
1568
1717
  if (input === ':graph refresh') {
1569
- console.log(chalk_1.default.hex('#8a7768')('\n Extracting codebase graph...\n'));
1718
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n Refreshing codebase graph...\n'));
1719
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1570
1720
  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`));
1721
+ execSync('python3 -c "' +
1722
+ 'import json,os,re,glob; ' +
1723
+ 'root=\\\"' + c.ctx.root + '/src\\\"; ' +
1724
+ 'print(\\\"Scanning\\\", root)" ', { stdio: 'inherit' });
1580
1725
  }
1726
+ catch { /* ignore */ }
1581
1727
  // Reload context graph
1582
1728
  c.ctx.graphSummary = (0, context_js_1.loadGraphSummary)(c.ctx.root);
1583
1729
  if (c.ctx.graphSummary) {
1584
1730
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Graph loaded and injected into context.\n'));
1585
1731
  }
1586
1732
  else {
1587
- console.log(chalk_1.default.hex('#8a7768')(' No graph.json found after refresh.\n'));
1733
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(' No graph.json found after refresh. Run graphify extract first.\n'));
1588
1734
  }
1589
1735
  return { handled: true };
1590
1736
  }
1591
1737
  // ── Provider wizard command ────────────────────────────────────────────────
1592
1738
  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}`));
1739
+ // Fullscreen prompt approach was unreliable — wizard's readline and TUI's
1740
+ // stdin handler collide. Instead, stop TUI input, let readline own stdin
1741
+ // completely, then restart TUI input. enterFullscreenPrompt resets the
1742
+ // scroll region so the wizard's output isn't crammed into the small scroll
1743
+ // area (numbers were invisible because the bottom block was overwriting them).
1744
+ const wasInputActive = tui_js_1.inputActive;
1745
+ if (wasInputActive) {
1746
+ (0, tui_js_1.stopInput)();
1747
+ (0, tui_js_1.enterFullscreenPrompt)();
1748
+ }
1749
+ try {
1750
+ const cfg = await (0, provider_wizard_js_1.runProviderWizard)(undefined, undefined);
1751
+ if (cfg) {
1752
+ runtimeConfig.model = cfg.model;
1753
+ runtimeConfig.baseUrl = cfg.baseUrl;
1754
+ runtimeConfig.apiKey = cfg.apiKey;
1755
+ c.providerConfig.model = cfg.model;
1756
+ c.providerConfig.baseUrl = cfg.baseUrl;
1757
+ c.providerConfig.apiKey = cfg.apiKey;
1758
+ resolved.model = cfg.model;
1759
+ resolved.baseUrl = cfg.baseUrl;
1760
+ (0, tui_js_1.writeOutput)(chalk_1.default.hex('#5a9e6e')(` ✓ Now using ${cfg.provider} · ${cfg.model}`));
1761
+ // Update the TUI status line so the provider/model change is immediately visible.
1762
+ (0, tui_js_1.setStatusLine)([cfg.provider, cfg.model, permissionLevel].filter(Boolean).join(' · '));
1763
+ if (fileConfig.model && fileConfig.model !== cfg.model) {
1764
+ (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` +
1765
+ ` Remove the "model" field from .aura.json (or set it to ${cfg.model}) to keep this choice.`));
1766
+ }
1767
+ }
1768
+ }
1769
+ finally {
1770
+ if (wasInputActive) {
1771
+ (0, tui_js_1.exitFullscreenPrompt)();
1772
+ (0, tui_js_1.startInput)();
1773
+ }
1606
1774
  }
1607
1775
  return { handled: true };
1608
1776
  }
@@ -1613,9 +1781,9 @@ async function handleReplCommand(input, c) {
1613
1781
  return acc;
1614
1782
  }, {});
1615
1783
  for (const [provider, models] of Object.entries(byProvider)) {
1616
- console.log(chalk_1.default.hex('#8a7768')(`\n ${provider}`));
1784
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(`\n ${provider}`));
1617
1785
  for (const m of models) {
1618
- console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex('#4e3d30')(m.speed)}`);
1786
+ console.log(` ${chalk_1.default.hex('#cc785c')(m.id.padEnd(45))} ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(m.speed)}`);
1619
1787
  }
1620
1788
  }
1621
1789
  console.log();
@@ -1638,7 +1806,22 @@ async function handleReplCommand(input, c) {
1638
1806
  const newKey = input.slice(sep.length).trim();
1639
1807
  runtimeConfig.apiKey = newKey;
1640
1808
  c.providerConfig.apiKey = newKey;
1641
- console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session.'));
1809
+ // Persist it so it survives across sessions (fixes "type the key every
1810
+ // time"). Save under the env-var name for the current provider, and also
1811
+ // set that env var live so getApiKey resolves it this session.
1812
+ const envName = globalCfg?.apiKeyEnv || envNameForModel(resolved.model ?? '');
1813
+ if (envName) {
1814
+ try {
1815
+ const p = (0, key_store_js_1.saveKey)(envName, newKey);
1816
+ console.log(chalk_1.default.hex('#5a9e6e')(` ✓ API key saved as ${envName} → ${p} (persists across sessions).`));
1817
+ }
1818
+ catch (e) {
1819
+ console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session (could not persist: ' + String(e) + ').'));
1820
+ }
1821
+ }
1822
+ else {
1823
+ console.log(chalk_1.default.hex('#5a9e6e')(' ✓ API key set for current session.'));
1824
+ }
1642
1825
  return { handled: true };
1643
1826
  }
1644
1827
  if (input === '/clear' || input === '/reset') {
@@ -1650,42 +1833,10 @@ async function handleReplCommand(input, c) {
1650
1833
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Session stats reset'));
1651
1834
  return { handled: true };
1652
1835
  }
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') {
1836
+ if (input === '/stats' || input === '/usage') {
1686
1837
  const u = c.cumulative;
1687
1838
  const total = u.inputTokens + u.outputTokens;
1688
- console.log(chalk_1.default.hex('#8a7768')([
1839
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)([
1689
1840
  '',
1690
1841
  ` Session usage:`,
1691
1842
  ` Turns: ${u.turns}`,
@@ -1698,11 +1849,19 @@ async function handleReplCommand(input, c) {
1698
1849
  ].join('\n')));
1699
1850
  return { handled: true };
1700
1851
  }
1852
+ if (input === '/context') {
1853
+ const u = c.cumulative;
1854
+ const h = c.healthTracker.snapshot(u.inputTokens, u.outputTokens);
1855
+ h.turnCount = u.turns;
1856
+ h.toolCallCount = u.toolCalls;
1857
+ c.display.contextDashboard?.(h);
1858
+ return { handled: true };
1859
+ }
1701
1860
  // ── Workflow commands ──────────────────────────────────────────────────────
1702
1861
  if (input === ':workflows') {
1703
1862
  const workflows = await (0, engine_js_1.listWorkflows)();
1704
1863
  if (workflows.length === 0) {
1705
- console.log(chalk_1.default.hex('#8a7768')('\n No saved workflows.\n'));
1864
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('\n No saved workflows.\n'));
1706
1865
  }
1707
1866
  else {
1708
1867
  console.log(chalk_1.default.hex('#cc785c').bold('\n Saved workflows:\n'));
@@ -1712,9 +1871,9 @@ async function handleReplCommand(input, c) {
1712
1871
  const totalSteps = ws.definition.steps.length;
1713
1872
  const statusColor = ws.status === 'done' ? '#5a9e6e' : ws.status === 'failed' ? '#b15439' : '#cc785c';
1714
1873
  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))} ` +
1874
+ `${chalk_1.default.hex(diamond_js_1.TEXT_HEX)(ws.definition.name.slice(0, 36).padEnd(37))} ` +
1716
1875
  `${chalk_1.default.hex(statusColor)(ws.status.padEnd(8))} ` +
1717
- `${chalk_1.default.hex('#4e3d30')(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
1876
+ `${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`${doneSteps}/${totalSteps} steps · ${created}`)}`);
1718
1877
  }
1719
1878
  console.log();
1720
1879
  }
@@ -1772,7 +1931,7 @@ async function handleReplCommand(input, c) {
1772
1931
  }
1773
1932
  else {
1774
1933
  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`));
1934
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1776
1935
  }
1777
1936
  return { handled: true };
1778
1937
  }
@@ -1810,7 +1969,7 @@ async function handleReplCommand(input, c) {
1810
1969
  }
1811
1970
  else {
1812
1971
  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`));
1972
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(` Resume with: :resume-workflow ${finalState.definition.id}\n`));
1814
1973
  }
1815
1974
  return { handled: true };
1816
1975
  }
@@ -1919,29 +2078,29 @@ async function runArchitectPlan(task, provider, ctx, display) {
1919
2078
  });
1920
2079
  // Display result
1921
2080
  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`));
2081
+ console.log(chalk_1.default.hex(diamond_js_1.TEXT_HEX)(` Task: ${blueprint.task}`));
2082
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ID: ${blueprint.id}\n`));
1924
2083
  if (blueprint.files.length > 0) {
1925
2084
  console.log(chalk_1.default.hex('#cc785c').bold(' Files:\n'));
1926
2085
  for (const f of blueprint.files) {
1927
2086
  console.log(` ${chalk_1.default.hex('#cc785c')(f.path)}`);
1928
- console.log(` ${chalk_1.default.hex('#8a7768')(f.purpose)}`);
2087
+ console.log(` ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(f.purpose)}`);
1929
2088
  if (f.exports.length > 0)
1930
- console.log(` ${chalk_1.default.hex('#4e3d30')(`exports: ${f.exports.join(', ')}`)}`);
2089
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`exports: ${f.exports.join(', ')}`)}`);
1931
2090
  if (f.interfaces.length > 0)
1932
- console.log(` ${chalk_1.default.hex('#4e3d30')(`interfaces: ${f.interfaces.join(', ')}`)}`);
2091
+ console.log(` ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)(`interfaces: ${f.interfaces.join(', ')}`)}`);
1933
2092
  }
1934
2093
  }
1935
2094
  if (blueprint.dataModels.length > 0) {
1936
2095
  console.log(chalk_1.default.hex('#cc785c').bold('\n Data Models:\n'));
1937
2096
  for (const dm of blueprint.dataModels) {
1938
- console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex('#8a7768')(dm.description)}`);
2097
+ console.log(` ${chalk_1.default.hex('#cc785c')(dm.name)} — ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(dm.description)}`);
1939
2098
  }
1940
2099
  }
1941
2100
  if (blueprint.risks.length > 0) {
1942
2101
  console.log(chalk_1.default.hex('#b15439').bold('\n Risks:\n'));
1943
2102
  for (const risk of blueprint.risks) {
1944
- console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex('#8a7768')(risk)}`);
2103
+ console.log(` ${chalk_1.default.hex('#b15439')('⚠')} ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)(risk)}`);
1945
2104
  }
1946
2105
  }
1947
2106
  console.log(chalk_1.default.hex('#5a9e6e')('\n Blueprint saved. No files were modified.'));
@@ -1961,16 +2120,20 @@ async function runOrchestratedTask(task, provider, ctx, display, forceOrchestrat
1961
2120
  // If --plan flag, show plan and ask for confirmation
1962
2121
  if (argv.plan === true) {
1963
2122
  display.showPlan?.(plan);
1964
- // Use a simple readline prompt for confirmation
1965
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2123
+ // Use a simple readline prompt for confirmation — reuse the REPL's
2124
+ // readline when one is active (second interface double-echoes and
2125
+ // pauses stdin on close).
2126
+ const sharedRl = (0, permissions_js_1.getSharedReadline)();
2127
+ const rl = sharedRl ?? readline.createInterface({ input: process.stdin, output: process.stdout });
1966
2128
  const approved = await new Promise(resolve => {
1967
2129
  rl.question(chalk_1.default.hex('#cc785c')('\n Run this plan? [y/N] '), answer => {
1968
- rl.close();
2130
+ if (!sharedRl)
2131
+ rl.close();
1969
2132
  resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
1970
2133
  });
1971
2134
  });
1972
2135
  if (!approved) {
1973
- console.log(chalk_1.default.hex('#4e3d30')(' Plan cancelled.\n'));
2136
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(' Plan cancelled.\n'));
1974
2137
  process.exit(0);
1975
2138
  }
1976
2139
  }
@@ -1994,51 +2157,49 @@ async function runOrchestratedTask(task, provider, ctx, display, forceOrchestrat
1994
2157
  display.summary(executedPlan.outcome, executedPlan.steps.length, 0);
1995
2158
  }
1996
2159
  const totalTokens = executedPlan.totalTokens ?? 0;
1997
- console.log(chalk_1.default.hex('#4e3d30')(` ↳ ${totalTokens.toLocaleString()} tokens · ${executedPlan.steps.length} steps · status: ${executedPlan.status}`));
2160
+ console.log(chalk_1.default.hex(diamond_js_1.FAINT_HEX)(` ↳ ${totalTokens.toLocaleString()} tokens · ${executedPlan.steps.length} steps · status: ${executedPlan.status}`));
1998
2161
  }
1999
- function printUsageFooter(display, usage, costUsd, opts) {
2162
+ function printUsageFooter(display, usage, costUsd) {
2000
2163
  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
- }
2164
+ 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)}`));
2165
+ }
2166
+ /**
2167
+ * Read a task summary aloud (the "Aura talks back" half of the voice loop).
2168
+ * Best-effort: strips code/markdown, caps length so a long report doesn't
2169
+ * monologue, and never throws into the caller (a TTS/network failure must
2170
+ * not break a successful task).
2171
+ */
2172
+ async function speakSummary(text) {
2173
+ if (!text || !text.trim())
2174
+ return;
2175
+ // Strip fenced code blocks, inline code, markdown markers, and collapse
2176
+ // whitespace — TTS should read the prose, not backticks and hashes.
2177
+ const spoken = text
2178
+ .replace(/```[\s\S]*?```/g, ' (code omitted) ')
2179
+ .replace(/`[^`]*`/g, '')
2180
+ .replace(/[#*_>|]/g, ' ')
2181
+ .replace(/\s+/g, ' ')
2182
+ .trim()
2183
+ .slice(0, 600);
2184
+ if (!spoken)
2185
+ return;
2186
+ try {
2187
+ const { speakText } = await Promise.resolve().then(() => __importStar(require('../tools/dictate.js')));
2188
+ await speakText(spoken);
2029
2189
  }
2190
+ catch { /* speech is best-effort — never break the task on TTS failure */ }
2030
2191
  }
2031
2192
  function printHelp() {
2032
2193
  console.log(`
2033
- ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768')("— Aura Code: model-agnostic AI coding agent")}
2194
+ ${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
2195
 
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
2196
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Usage:')}
2197
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('"<task>"')} Run a single task
2198
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('serve')} Start the HTTP API server
2199
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('--interactive')} Start interactive REPL
2200
+ aura ${chalk_1.default.hex(diamond_js_1.TEXT_DIM_HEX)('--models')} List available models
2040
2201
 
2041
- ${chalk_1.default.hex('#4e3d30')('Options:')}
2202
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Options:')}
2042
2203
  --model, -m <id> Model to use (default: from ~/.config/aura-code/config.json)
2043
2204
  --api-key <key> API key (overrides env var)
2044
2205
  --base-url <url> Custom API endpoint (for Ollama, proxies, etc.)
@@ -2047,12 +2208,10 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2047
2208
  --cwd <path> Working directory (default: current)
2048
2209
  --models List all known model IDs
2049
2210
  --no-session Disable conversation history persistence
2050
- --once Run single task and exit (no REPL after)
2051
2211
  --new-session Force a fresh session (ignore any prior history)
2052
2212
  --resume [id] Resume latest session, or a specific session by ID
2053
2213
  --chat-id <id> Attach to a specific chat ID (creates if missing)
2054
2214
  --list-sessions List all saved sessions for this project
2055
- --setup Run the provider setup wizard before entering REPL
2056
2215
  --no-setup Skip the first-run setup wizard
2057
2216
  --reset-setup Wipe saved config and re-run the setup wizard
2058
2217
  --orchestrate Force multi-agent orchestration mode
@@ -2064,11 +2223,14 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2064
2223
  --verify Verify output after task; retry up to --max-verify-retries times
2065
2224
  --max-verify-retries <n> Max verification retries (default: 3)
2066
2225
  --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)
2226
+ --max-turns <n> Max agent loop turns before stopping (default: sized by task shape)
2227
+ --moa Mixture of agents: parallel read-only domain perspectives + synthesis (exploratory tasks only)
2068
2228
  --analyze Mine session history for weakness patterns; save report
2069
- --stats Show episode stats — completion rate, avg duration, top model, tokens
2070
2229
  --propose-harness Generate system-prompt patches from weakness report
2071
2230
  --apply-harness <id> Apply a proposal patch; reverts if tests fail
2231
+ --doctor Scan Aura itself for issues (build, config, deps, env, git)
2232
+ --doctor --fix Scan and attempt auto-repairs (rebuild, restore, reinstall)
2233
+ --doctor --offline Skip the network version check
2072
2234
  --workflow <name> ... Create and run a sequential workflow with named steps
2073
2235
  --resume-workflow <id> Resume a paused/failed workflow from last completed step
2074
2236
  --workflows List all persisted workflows
@@ -2080,7 +2242,7 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2080
2242
  --fallback <model> Fallback model if primary exhausts retries (repeatable)
2081
2243
  --verify Enable post-task verification with automatic retries
2082
2244
 
2083
- ${chalk_1.default.hex('#4e3d30')('Resilience:')}
2245
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Resilience:')}
2084
2246
  All API calls automatically:
2085
2247
  1. Honour Retry-After / Google's retryDelay on 429s
2086
2248
  2. Back off with exponential + jitter (capped at 60s)
@@ -2088,7 +2250,7 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2088
2250
  4. Fail over to the next --fallback model if retries exhaust
2089
2251
  5. Pace requests when --rate-limit-rpm / --rate-limit-tpm is set
2090
2252
 
2091
- ${chalk_1.default.hex('#4e3d30')('Project config (.aura.json):')}
2253
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Project config (.aura.json):')}
2092
2254
  {
2093
2255
  "model": "claude-sonnet-4-5-20251001",
2094
2256
  "mode": "auto",
@@ -2099,8 +2261,8 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2099
2261
  "apiKeyEnv": "DEEPSEEK_API_KEY",
2100
2262
  "prefixes": ["deepseek/"],
2101
2263
  "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" }
2264
+ { "id": "deepseek/deepseek-chat", "name": "DeepSeek Chat", "speed": "Fast" },
2265
+ { "id": "deepseek/deepseek-reasoner", "name": "DeepSeek R1", "speed": "Reasoning" }
2104
2266
  ]
2105
2267
  }
2106
2268
  ],
@@ -2114,19 +2276,21 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2114
2276
  CLI flags always override .aura.json.
2115
2277
  Custom providers are OpenAI-compatible endpoints.
2116
2278
 
2117
- ${chalk_1.default.hex('#4e3d30')('Model examples:')}
2279
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('Model examples:')}
2118
2280
  aura -m claude-opus-4-5-20251001 "refactor auth"
2119
2281
  aura -m gpt-4o "add unit tests"
2120
2282
  aura -m gemini-2.5-pro --rate-limit-rpm 20 "explain this codebase"
2121
2283
  aura -m ollama/llama3.2 "local model, no API key needed"
2122
2284
 
2123
- ${chalk_1.default.hex('#4e3d30')('API keys (set as env vars):')}
2285
+ ${chalk_1.default.hex(diamond_js_1.FAINT_HEX)('API keys (set as env vars):')}
2124
2286
  ANTHROPIC_API_KEY Claude models
2125
2287
  OPENAI_API_KEY GPT models
2126
2288
  GOOGLE_API_KEY Gemini models
2127
2289
  XAI_API_KEY Grok models
2128
2290
  OPENROUTER_API_KEY OpenRouter (access to all models)
2129
2291
  XIAOMI_API_KEY Xiaomi MiMo
2292
+ ZHIPU_API_KEY Zhipu GLM (Z.ai) — glm-* general endpoint, zhipu-coding/<model> Coding Plan
2293
+ ZHIPU_BASE_URL Override Zhipu endpoint (default https://api.z.ai/api/paas/v4)
2130
2294
  AURA_MODEL Default model (overridden by --model)
2131
2295
  AURA_API_RPM Default request rate limit
2132
2296
  AURA_API_TPM Default token rate limit (Gemini)
@@ -2134,7 +2298,10 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
2134
2298
  AURA_FALLBACK_MODEL Comma-separated fallback models
2135
2299
  `);
2136
2300
  }
2137
- if (argv._[0] === 'serve') {
2301
+ if (argv.doctor === true) {
2302
+ // --doctor runs async above and exits on completion; don't start main().
2303
+ }
2304
+ else if (argv._[0] === 'serve') {
2138
2305
  const port = Number(argv.port ?? argv.p ?? 7337);
2139
2306
  (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
2307
  }