aura-code 0.3.7 → 0.6.3

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 (160) hide show
  1. package/README.md +56 -4
  2. package/dist/agent/compactor.d.ts +26 -0
  3. package/dist/agent/compactor.js +156 -0
  4. package/dist/agent/compactor.js.map +1 -0
  5. package/dist/agent/context.d.ts +4 -0
  6. package/dist/agent/context.js +38 -0
  7. package/dist/agent/context.js.map +1 -1
  8. package/dist/agent/design.d.ts +17 -0
  9. package/dist/agent/design.js +175 -0
  10. package/dist/agent/design.js.map +1 -0
  11. package/dist/agent/loop.js +24 -1
  12. package/dist/agent/loop.js.map +1 -1
  13. package/dist/agent/system-prompt.d.ts +1 -1
  14. package/dist/agent/system-prompt.js +14 -3
  15. package/dist/agent/system-prompt.js.map +1 -1
  16. package/dist/cli/display.js +46 -5
  17. package/dist/cli/display.js.map +1 -1
  18. package/dist/cli/index.js +323 -46
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/config/defaults.js +8 -1
  21. package/dist/config/defaults.js.map +1 -1
  22. package/dist/config/project-config.d.ts +6 -0
  23. package/dist/config/project-config.js +2 -0
  24. package/dist/config/project-config.js.map +1 -1
  25. package/dist/dream/dream.d.ts +71 -0
  26. package/dist/dream/dream.js +217 -0
  27. package/dist/dream/dream.js.map +1 -0
  28. package/dist/dream/okf.d.ts +10 -0
  29. package/dist/dream/okf.js +280 -0
  30. package/dist/dream/okf.js.map +1 -0
  31. package/dist/dream/parser.d.ts +60 -0
  32. package/dist/dream/parser.js +176 -0
  33. package/dist/dream/parser.js.map +1 -0
  34. package/dist/dream/reconcile.d.ts +69 -0
  35. package/dist/dream/reconcile.js +303 -0
  36. package/dist/dream/reconcile.js.map +1 -0
  37. package/dist/kanban/index.d.ts +5 -0
  38. package/dist/kanban/index.js +11 -0
  39. package/dist/kanban/index.js.map +1 -0
  40. package/dist/kanban/pipeline.d.ts +16 -0
  41. package/dist/kanban/pipeline.js +910 -0
  42. package/dist/kanban/pipeline.js.map +1 -0
  43. package/dist/kanban/server.d.ts +5 -0
  44. package/dist/kanban/server.js +820 -0
  45. package/dist/kanban/server.js.map +1 -0
  46. package/dist/kanban/types.d.ts +75 -0
  47. package/dist/kanban/types.js +6 -0
  48. package/dist/kanban/types.js.map +1 -0
  49. package/dist/learnlight/driven.d.ts +2 -0
  50. package/dist/learnlight/driven.js +109 -0
  51. package/dist/learnlight/driven.js.map +1 -0
  52. package/dist/learnlight/index.d.ts +4 -0
  53. package/dist/learnlight/index.js +32 -0
  54. package/dist/learnlight/index.js.map +1 -0
  55. package/dist/learnlight/lesson-prep.d.ts +36 -0
  56. package/dist/learnlight/lesson-prep.js +224 -0
  57. package/dist/learnlight/lesson-prep.js.map +1 -0
  58. package/dist/learnlight/report.d.ts +2 -0
  59. package/dist/learnlight/report.js +37 -0
  60. package/dist/learnlight/report.js.map +1 -0
  61. package/dist/learnlight/types.d.ts +72 -0
  62. package/dist/learnlight/types.js +6 -0
  63. package/dist/learnlight/types.js.map +1 -0
  64. package/dist/machina/index.d.ts +16 -0
  65. package/dist/machina/index.js +61 -0
  66. package/dist/machina/index.js.map +1 -0
  67. package/dist/machina/render-html.d.ts +2 -0
  68. package/dist/machina/render-html.js +189 -0
  69. package/dist/machina/render-html.js.map +1 -0
  70. package/dist/machina/render-terminal.d.ts +2 -0
  71. package/dist/machina/render-terminal.js +92 -0
  72. package/dist/machina/render-terminal.js.map +1 -0
  73. package/dist/machina/spec.d.ts +61 -0
  74. package/dist/machina/spec.js +124 -0
  75. package/dist/machina/spec.js.map +1 -0
  76. package/dist/machina/verify.d.ts +27 -0
  77. package/dist/machina/verify.js +85 -0
  78. package/dist/machina/verify.js.map +1 -0
  79. package/dist/perception/graph-store.d.ts +11 -0
  80. package/dist/perception/graph-store.js +33 -0
  81. package/dist/perception/graph-store.js.map +1 -1
  82. package/dist/perception/index.d.ts +1 -1
  83. package/dist/perception/index.js +2 -1
  84. package/dist/perception/index.js.map +1 -1
  85. package/dist/providers/anthropic.js +1 -1
  86. package/dist/providers/anthropic.js.map +1 -1
  87. package/dist/providers/factory.d.ts +49 -0
  88. package/dist/providers/factory.js +99 -0
  89. package/dist/providers/factory.js.map +1 -1
  90. package/dist/providers/google.js +1 -1
  91. package/dist/providers/google.js.map +1 -1
  92. package/dist/providers/openai-compatible.js +81 -50
  93. package/dist/providers/openai-compatible.js.map +1 -1
  94. package/dist/rem/graph.d.ts +72 -0
  95. package/dist/rem/graph.js +139 -0
  96. package/dist/rem/graph.js.map +1 -0
  97. package/dist/rem/index.d.ts +16 -0
  98. package/dist/rem/index.js +60 -0
  99. package/dist/rem/index.js.map +1 -0
  100. package/dist/rem/render-html.d.ts +2 -0
  101. package/dist/rem/render-html.js +189 -0
  102. package/dist/rem/render-html.js.map +1 -0
  103. package/dist/rem/render-terminal.d.ts +4 -0
  104. package/dist/rem/render-terminal.js +94 -0
  105. package/dist/rem/render-terminal.js.map +1 -0
  106. package/dist/research/council.d.ts +27 -0
  107. package/dist/research/council.js +263 -0
  108. package/dist/research/council.js.map +1 -0
  109. package/dist/research/research.d.ts +24 -0
  110. package/dist/research/research.js +116 -0
  111. package/dist/research/research.js.map +1 -0
  112. package/dist/ruby/alternator.d.ts +36 -1
  113. package/dist/ruby/alternator.js +40 -31
  114. package/dist/ruby/alternator.js.map +1 -1
  115. package/dist/ruby/stats.js +10 -11
  116. package/dist/ruby/stats.js.map +1 -1
  117. package/dist/ruby/types.js +1 -1
  118. package/dist/ruby/types.js.map +1 -1
  119. package/dist/safety/permissions.d.ts +8 -0
  120. package/dist/safety/permissions.js +12 -0
  121. package/dist/safety/permissions.js.map +1 -1
  122. package/dist/setup/provider-registry.js +14 -0
  123. package/dist/setup/provider-registry.js.map +1 -1
  124. package/dist/setup/provider-test.js +24 -2
  125. package/dist/setup/provider-test.js.map +1 -1
  126. package/dist/setup/telegram-wizard.d.ts +57 -0
  127. package/dist/setup/telegram-wizard.js +343 -0
  128. package/dist/setup/telegram-wizard.js.map +1 -0
  129. package/dist/tools/audio-transcribe.d.ts +20 -0
  130. package/dist/tools/audio-transcribe.js +39 -28
  131. package/dist/tools/audio-transcribe.js.map +1 -1
  132. package/dist/tools/browser.js +8 -10
  133. package/dist/tools/browser.js.map +1 -1
  134. package/dist/tools/gmail-tool.d.ts +15 -0
  135. package/dist/tools/gmail-tool.js +361 -0
  136. package/dist/tools/gmail-tool.js.map +1 -0
  137. package/dist/tools/read-file.js +13 -0
  138. package/dist/tools/read-file.js.map +1 -1
  139. package/dist/tools/telegram-bot.js +103 -20
  140. package/dist/tools/telegram-bot.js.map +1 -1
  141. package/dist/tools/telegram-voice.d.ts +22 -0
  142. package/dist/tools/telegram-voice.js +203 -0
  143. package/dist/tools/telegram-voice.js.map +1 -0
  144. package/dist/tools/telegram.d.ts +6 -1
  145. package/dist/tools/telegram.js +115 -5
  146. package/dist/tools/telegram.js.map +1 -1
  147. package/dist/tools/video-render.d.ts +11 -0
  148. package/dist/tools/video-render.js +189 -0
  149. package/dist/tools/video-render.js.map +1 -0
  150. package/dist/tools/web-search.d.ts +20 -0
  151. package/dist/tools/web-search.js +134 -38
  152. package/dist/tools/web-search.js.map +1 -1
  153. package/dist/util/errors.js +6 -0
  154. package/dist/util/errors.js.map +1 -1
  155. package/dist/viz/index.js +816 -30
  156. package/dist/viz/index.js.map +1 -1
  157. package/dist/viz/reader.d.ts +33 -0
  158. package/dist/viz/reader.js +343 -0
  159. package/dist/viz/reader.js.map +1 -0
  160. package/package.json +3 -1
package/dist/cli/index.js CHANGED
@@ -66,6 +66,8 @@ const diamond_js_1 = require("./diamond.js");
66
66
  const episode_capture_js_1 = require("../ruby/episode-capture.js");
67
67
  const model_selector_js_1 = require("../ruby/model-selector.js");
68
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");
69
71
  const load_env_js_1 = require("../util/load-env.js");
70
72
  const factory_js_2 = require("../providers/factory.js");
71
73
  // Load API keys from ~/.hermes/.env, ~/.config/aura-code/.env, and project .env
@@ -783,6 +785,9 @@ async function main() {
783
785
  if (!perception || (0, index_js_4.isStale)(perception)) {
784
786
  display.agentThinking();
785
787
  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(() => { });
786
791
  }
787
792
  const decision = await (0, index_js_3.routeTask)({ provider, context: ctx, task, perception: perception ?? undefined });
788
793
  if (decision.shouldDecompose && decision.confidence > 0.8) {
@@ -795,7 +800,11 @@ async function main() {
795
800
  catch {
796
801
  // Router failed — fall through to single agent
797
802
  }
798
- // ── RubyAlternator: suggest model from competence history ─────────────────
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.
799
808
  // Only when the user has NOT explicitly specified --model or :model
800
809
  if (!userSetModel && !globalCfg?.defaultModel) {
801
810
  try {
@@ -818,9 +827,13 @@ async function main() {
818
827
  }
819
828
  }
820
829
  const doVerify = cliVerify || !!fileConfig.verify;
830
+ const rubyConfig = { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby };
821
831
  const episodeStart = Date.now();
822
832
  let result;
833
+ let alternatorHandledEpisode = false;
823
834
  if (doVerify) {
835
+ // Ruby-alternation doesn't support the verification wrapper yet —
836
+ // a verified task always runs directly against the selected model.
824
837
  const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
825
838
  const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
826
839
  const testCommand = cliTestCommand ?? fileConfig.testCommand;
@@ -841,6 +854,20 @@ async function main() {
841
854
  });
842
855
  result = wrapperResult.loopResult;
843
856
  }
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
+ }
844
871
  else {
845
872
  result = await (0, loop_js_1.runAgentLoop)({
846
873
  provider, task, context: ctx, permissions, display,
@@ -853,26 +880,30 @@ async function main() {
853
880
  sessionPath,
854
881
  });
855
882
  }
856
- // Persist episode for Ruby Principle training data
857
- try {
858
- const { randomBytes } = await Promise.resolve().then(() => __importStar(require('crypto')));
859
- await (0, episode_capture_js_1.saveEpisode)(ctx.root, {
860
- id: randomBytes(4).toString('hex') + '-' + Date.now().toString(36),
861
- timestamp: Date.now(),
862
- task,
863
- projectRoot: ctx.root,
864
- rubyAttempted: false,
865
- rubySucceeded: false,
866
- largeModelUsed: runtimeConfig.model,
867
- largeModelOutput: result.summary,
868
- reviewerApproved: result.success,
869
- tokensUsed: { largeModel: result.usage.totalTokens },
870
- durationMs: Date.now() - episodeStart,
871
- taskCategory: 'other',
872
- });
873
- }
874
- catch {
875
- // Episode recording is best-effort; never block the user
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
+ }
876
907
  }
877
908
  // Update history so REPL continues the conversation
878
909
  activeChatHistory = result.history;
@@ -920,6 +951,7 @@ async function main() {
920
951
  permissions, cumulative,
921
952
  chatState: { projectRoot, activeChatId, activeChatHistory, activeChatTitle, noSession },
922
953
  rl,
954
+ rubyEnabled: { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby }.enabled,
923
955
  };
924
956
  const cmdResult = await handleReplCommand(input, replCtx);
925
957
  if (cmdResult.handled) {
@@ -930,11 +962,19 @@ async function main() {
930
962
  activeChatHistory = cmdResult.newHistory;
931
963
  if (cmdResult.newTitle !== undefined)
932
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
+ }
933
969
  ask();
934
970
  return;
935
971
  }
936
972
  // Run task — pass current conversation history for stay-active mode
937
- // ── RubyAlternator: suggest model from competence history ──────────────
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.
938
978
  // Only when the user has NOT set a model explicitly via --model or :model
939
979
  if (!userSetModel && !globalCfg?.defaultModel) {
940
980
  try {
@@ -957,8 +997,16 @@ async function main() {
957
997
  let result;
958
998
  try {
959
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
+ });
960
1005
  const doVerify = argv.verify === true || !!fileConfig.verify;
1006
+ const rubyConfig = { ...types_js_1.DEFAULT_RUBY_CONFIG, ...fileConfig.ruby };
961
1007
  if (doVerify) {
1008
+ // Ruby-alternation doesn't support the verification wrapper yet —
1009
+ // a verified task always runs directly against the selected model.
962
1010
  const { runWithVerification } = await Promise.resolve().then(() => __importStar(require('../verify/index.js')));
963
1011
  const maxRetries = cliMaxVerifyRetries ?? fileConfig.maxVerifyRetries ?? defaults_js_1.DEFAULTS.maxVerifyRetries;
964
1012
  const testCommand = cliTestCommand ?? fileConfig.testCommand;
@@ -968,11 +1016,7 @@ async function main() {
968
1016
  context: ctx, permissions, display,
969
1017
  initialHistory: activeChatHistory,
970
1018
  maxTurns: resolved.maxTurns,
971
- confirmFn: async (msg) => new Promise(res => {
972
- rl.question(`\\n⚠️ ${msg} [y/N] `, a => {
973
- res(a.trim().toLowerCase() === 'y' || a.trim().toLowerCase() === 'yes');
974
- });
975
- }),
1019
+ confirmFn: replConfirmFn,
976
1020
  spawnConfig: {
977
1021
  apiKey: runtimeConfig.apiKey,
978
1022
  baseUrl: runtimeConfig.baseUrl ?? undefined,
@@ -985,17 +1029,27 @@ async function main() {
985
1029
  });
986
1030
  result = wrapperResult.loopResult;
987
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
+ }
988
1046
  else {
989
1047
  result = await (0, loop_js_1.runAgentLoop)({
990
1048
  provider: currentProvider, task: input,
991
1049
  context: ctx, permissions, display,
992
1050
  initialHistory: activeChatHistory,
993
1051
  maxTurns: resolved.maxTurns,
994
- confirmFn: async (msg) => new Promise(res => {
995
- rl.question(`\\n⚠️ ${msg} [y/N] `, a => {
996
- res(a.trim().toLowerCase() === 'y' || a.trim().toLowerCase() === 'yes');
997
- });
998
- }),
1052
+ confirmFn: replConfirmFn,
999
1053
  spawnConfig: {
1000
1054
  apiKey: runtimeConfig.apiKey,
1001
1055
  baseUrl: runtimeConfig.baseUrl ?? undefined,
@@ -1165,15 +1219,30 @@ async function handleReplCommand(input, c) {
1165
1219
  ' ── Context / Stats ──────────────────────────────',
1166
1220
  ' :context Show loaded project context',
1167
1221
  ' :graph Show codebase knowledge graph summary',
1168
- ' :graph refresh Reload graph from graphify-out/graph.json',
1222
+ ' :graph refresh Extract + persist codebase graph',
1223
+ ' :stats Show episode-level stats (completion rate, models, tokens)',
1169
1224
  ' :plans List saved execution plans',
1170
1225
  ' :viz, :dashboard Generate and open the memory dashboard',
1171
- ' /stats, /usage Show token + cost usage this session',
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',
1172
1236
  ' /clear, /reset Reset cumulative usage stats',
1173
1237
  '',
1174
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',
1175
1242
  ' :quit, :q, /exit Exit',
1176
1243
  '',
1244
+ ' ⚠ = extremely high token usage — reads multiple source files and writes a careful long-form result',
1245
+ '',
1177
1246
  ].join('\n')));
1178
1247
  return { handled: true };
1179
1248
  }
@@ -1301,6 +1370,180 @@ async function handleReplCommand(input, c) {
1301
1370
  }
1302
1371
  return { handled: true };
1303
1372
  }
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
+ }
1304
1547
  if (input === ':plans') {
1305
1548
  const { planStore } = await Promise.resolve().then(() => __importStar(require('../orchestration/plan-store.js')));
1306
1549
  const plans = await planStore.list();
@@ -1323,24 +1566,25 @@ async function handleReplCommand(input, c) {
1323
1566
  return { handled: true };
1324
1567
  }
1325
1568
  if (input === ':graph refresh') {
1326
- console.log(chalk_1.default.hex('#8a7768')('\n Refreshing codebase graph...\n'));
1327
- const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1569
+ console.log(chalk_1.default.hex('#8a7768')('\n Extracting codebase graph...\n'));
1328
1570
  try {
1329
- // Pass root as sys.argv[1] to avoid shell interpolation
1330
- execFileSync('python3', [
1331
- '-c',
1332
- 'import sys,json,os,re,glob; root=sys.argv[1]+"/src"; print("Scanning", root)',
1333
- c.ctx.root,
1334
- ], { stdio: 'inherit' });
1335
- }
1336
- catch { /* ignore */ }
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`));
1580
+ }
1337
1581
  // Reload context graph
1338
1582
  c.ctx.graphSummary = (0, context_js_1.loadGraphSummary)(c.ctx.root);
1339
1583
  if (c.ctx.graphSummary) {
1340
1584
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Graph loaded and injected into context.\n'));
1341
1585
  }
1342
1586
  else {
1343
- console.log(chalk_1.default.hex('#8a7768')(' No graph.json found after refresh. Run graphify extract first.\n'));
1587
+ console.log(chalk_1.default.hex('#8a7768')(' No graph.json found after refresh.\n'));
1344
1588
  }
1345
1589
  return { handled: true };
1346
1590
  }
@@ -1406,7 +1650,39 @@ async function handleReplCommand(input, c) {
1406
1650
  console.log(chalk_1.default.hex('#5a9e6e')(' ✓ Session stats reset'));
1407
1651
  return { handled: true };
1408
1652
  }
1409
- if (input === '/stats' || input === '/usage') {
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') {
1410
1686
  const u = c.cumulative;
1411
1687
  const total = u.inputTokens + u.outputTokens;
1412
1688
  console.log(chalk_1.default.hex('#8a7768')([
@@ -1790,6 +2066,7 @@ ${chalk_1.default.hex('#cc785c').bold(' aura')} ${chalk_1.default.hex('#8a7768'
1790
2066
  --test-command <cmd> Shell command run as part of verification (e.g. "npm test")
1791
2067
  --max-turns <n> Max agent loop turns before stopping (default: 150)
1792
2068
  --analyze Mine session history for weakness patterns; save report
2069
+ --stats Show episode stats — completion rate, avg duration, top model, tokens
1793
2070
  --propose-harness Generate system-prompt patches from weakness report
1794
2071
  --apply-harness <id> Apply a proposal patch; reverts if tests fail
1795
2072
  --workflow <name> ... Create and run a sequential workflow with named steps