micro-models-agent 0.63.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/dist/cli/cache-line.js +30 -0
  3. package/dist/cli/command-suggest.js +38 -0
  4. package/dist/cli/commands.js +285 -60
  5. package/dist/cli/completer.js +16 -16
  6. package/dist/cli/json-payload.js +32 -0
  7. package/dist/cli/main.js +165 -77
  8. package/dist/cli/plugin-commands.js +5 -4
  9. package/dist/cli/relaunch.js +37 -0
  10. package/dist/cli/repl-commands.js +441 -307
  11. package/dist/cli/repl.js +360 -83
  12. package/dist/cli/run-result.js +12 -6
  13. package/dist/cli/security-commands.js +64 -60
  14. package/dist/cli/setup-order.js +57 -0
  15. package/dist/cli/setup-prompt.js +49 -0
  16. package/dist/cli/setup.js +52 -48
  17. package/dist/config/budget.js +48 -0
  18. package/dist/config/config.js +132 -70
  19. package/dist/config/defaults.js +37 -11
  20. package/dist/config/domains.js +9 -50
  21. package/dist/config/utils.js +56 -0
  22. package/dist/core/agent/audit-gate.js +49 -0
  23. package/dist/core/agent/compaction.js +89 -0
  24. package/dist/core/agent/constants.js +61 -0
  25. package/dist/core/agent/context-renderer.js +40 -0
  26. package/dist/core/agent/hallucination-gate.js +87 -0
  27. package/dist/core/agent/loop-state.js +53 -0
  28. package/dist/core/agent/prefix-monitor.js +101 -0
  29. package/dist/core/agent/reasoning-resolver.js +56 -0
  30. package/dist/core/agent/token-tracker.js +96 -0
  31. package/dist/core/agent/tool-batch.js +237 -0
  32. package/dist/core/agent/tool-output.js +62 -0
  33. package/dist/core/agent-moe.js +214 -69
  34. package/dist/core/agent.js +506 -546
  35. package/dist/core/bootstrap.js +297 -98
  36. package/dist/core/crash-handler.js +2 -1
  37. package/dist/core/prompt-builder.js +3 -0
  38. package/dist/core/prompt-overflow.js +307 -0
  39. package/dist/core/session-logger.js +34 -2
  40. package/dist/i18n/en.json +7 -4
  41. package/dist/i18n/ru.json +7 -4
  42. package/dist/index.js +5 -1
  43. package/dist/llm/cache-usage.js +76 -0
  44. package/dist/llm/image-utils.js +20 -16
  45. package/dist/llm/llm-errors.js +41 -0
  46. package/dist/llm/model-loader.js +30 -0
  47. package/dist/llm/openai-compat.js +287 -101
  48. package/dist/llm/orchestrator.js +140 -68
  49. package/dist/llm/provider-budget.js +68 -0
  50. package/dist/llm/provider.js +0 -1
  51. package/dist/llm/stream-state.js +26 -0
  52. package/dist/llm/token-counter.js +28 -0
  53. package/dist/logger/app-logger.js +12 -15
  54. package/dist/main.js +1606 -800
  55. package/dist/migration/detect.js +3 -1
  56. package/dist/modules/browser/actions.js +0 -3
  57. package/dist/modules/browser/bridge-client.js +2 -0
  58. package/dist/modules/browser/driver.js +46 -4
  59. package/dist/modules/certification/cli.js +85 -42
  60. package/dist/modules/certification/loader.js +15 -1
  61. package/dist/modules/certification/manifest.js +126 -15
  62. package/dist/modules/certification/runner.js +4 -26
  63. package/dist/modules/certification/scenarios.js +184 -5
  64. package/dist/modules/certification/syntax-scenarios.js +51 -0
  65. package/dist/modules/context/chunk-query.js +25 -5
  66. package/dist/modules/context/fact-extractor.js +6 -2
  67. package/dist/modules/context/manager.js +23 -7
  68. package/dist/modules/execution/audit-runners.js +7 -1
  69. package/dist/modules/execution/auditor.js +3 -3
  70. package/dist/modules/execution/execution-plugin.js +22 -15
  71. package/dist/modules/execution/input-from.js +46 -0
  72. package/dist/modules/execution/module.js +107 -18
  73. package/dist/modules/execution/moe-executor.js +166 -54
  74. package/dist/modules/execution/plan-actions.js +524 -0
  75. package/dist/modules/execution/plan-steps.js +23 -0
  76. package/dist/modules/execution/plan-store.js +15 -3
  77. package/dist/modules/execution/plan-tool.js +6 -488
  78. package/dist/modules/execution/plan-validator.js +24 -0
  79. package/dist/modules/execution/stuck-detector.js +3 -18
  80. package/dist/modules/execution/tracker.js +14 -5
  81. package/dist/modules/execution/transient-error.js +30 -0
  82. package/dist/modules/execution/verifier.js +94 -7
  83. package/dist/modules/execution/windows-commands.js +11 -0
  84. package/dist/modules/hallucination/confidence.js +36 -23
  85. package/dist/modules/hallucination/consistency.js +3 -0
  86. package/dist/modules/hallucination/detector.js +8 -3
  87. package/dist/modules/hallucination/factual.js +26 -7
  88. package/dist/modules/hallucination/llm-judge.js +12 -2
  89. package/dist/modules/indexer/map-command.js +35 -0
  90. package/dist/modules/indexer/map-select.js +87 -0
  91. package/dist/modules/indexer/module.js +34 -22
  92. package/dist/modules/indexer/symbols.js +189 -0
  93. package/dist/modules/indexer/walker.js +96 -42
  94. package/dist/modules/lsp/check-tool.js +2 -1
  95. package/dist/modules/lsp/client.js +49 -32
  96. package/dist/modules/lsp/config.js +55 -2
  97. package/dist/modules/lsp/module.js +38 -5
  98. package/dist/modules/lsp/probe.js +4 -3
  99. package/dist/modules/lsp/project-root.js +41 -1
  100. package/dist/modules/lsp/startup-check.js +12 -4
  101. package/dist/modules/mcp/client.js +153 -104
  102. package/dist/modules/mcp/module.js +165 -41
  103. package/dist/modules/memory/module.js +4 -3
  104. package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
  105. package/dist/modules/plugins/manager.js +47 -84
  106. package/dist/modules/pricing/index.js +17 -7
  107. package/dist/modules/pricing/prices.js +30 -12
  108. package/dist/modules/processes/index.js +1 -0
  109. package/dist/modules/processes/kill-tree.js +56 -0
  110. package/dist/modules/processes/registry.js +2 -54
  111. package/dist/modules/providers/cache.js +23 -0
  112. package/dist/modules/providers/factory.js +28 -0
  113. package/dist/modules/providers/fallback.js +7 -5
  114. package/dist/modules/providers/health.js +2 -1
  115. package/dist/modules/providers/index.js +1 -0
  116. package/dist/modules/providers/manager.js +17 -2
  117. package/dist/modules/providers/presets.js +79 -6
  118. package/dist/modules/reasoning/policy.js +40 -0
  119. package/dist/modules/reasoning/probe.js +111 -0
  120. package/dist/modules/security/audit-notifier.js +42 -27
  121. package/dist/modules/security/command-validator.js +25 -20
  122. package/dist/modules/security/encryption.js +6 -12
  123. package/dist/modules/security/network-validator.js +76 -5
  124. package/dist/modules/security/path-validator.js +77 -34
  125. package/dist/modules/security/rate-limiter.js +11 -0
  126. package/dist/modules/security/security-policies.js +1 -1
  127. package/dist/modules/security/session-encryption.js +13 -2
  128. package/dist/modules/security/session-isolation.js +2 -9
  129. package/dist/modules/session/manager.js +11 -0
  130. package/dist/modules/session/module.js +11 -3
  131. package/dist/modules/session/store.js +41 -5
  132. package/dist/modules/skills/loader.js +7 -1
  133. package/dist/modules/skills/module.js +2 -1
  134. package/dist/modules/updater/changelog-reader.js +94 -0
  135. package/dist/modules/updater/dev-detect.js +17 -0
  136. package/dist/modules/updater/index.js +1 -0
  137. package/dist/modules/updater/module.js +14 -3
  138. package/dist/output/bus.js +32 -0
  139. package/dist/output/channel.js +233 -0
  140. package/dist/output/format.js +14 -0
  141. package/dist/output/index.js +7 -0
  142. package/dist/output/json-sink.js +22 -0
  143. package/dist/output/machine.js +8 -0
  144. package/dist/output/session-sink.js +27 -0
  145. package/dist/output/types.js +1 -0
  146. package/dist/tools/approve.js +6 -2
  147. package/dist/tools/attach-image.js +11 -11
  148. package/dist/tools/auto-fixer.js +198 -0
  149. package/dist/tools/bash.js +142 -89
  150. package/dist/tools/chunk-query.js +10 -6
  151. package/dist/tools/download-file.js +1 -1
  152. package/dist/tools/edit-file.js +20 -2
  153. package/dist/tools/executor.js +54 -9
  154. package/dist/tools/glob-tool.js +7 -0
  155. package/dist/tools/grep-tool.js +15 -1
  156. package/dist/tools/index.js +3 -1
  157. package/dist/tools/list-dir.js +3 -1
  158. package/dist/tools/load-skill.js +2 -1
  159. package/dist/tools/mcp-call.js +1 -1
  160. package/dist/tools/move-file.js +5 -4
  161. package/dist/tools/path-utils.js +7 -0
  162. package/dist/tools/pipeline-run.js +1 -1
  163. package/dist/tools/prompt-io.js +28 -0
  164. package/dist/tools/question.js +12 -12
  165. package/dist/tools/scope-request.js +91 -0
  166. package/dist/tools/session-info.js +44 -0
  167. package/dist/tools/set-thinking.js +71 -0
  168. package/dist/tools/subagent.js +50 -9
  169. package/dist/tools/syntax-validator.js +177 -0
  170. package/dist/tools/user-input.js +16 -9
  171. package/dist/tools/write-file.js +17 -1
  172. package/dist/ui/diff.js +10 -0
  173. package/dist/ui/line-editor.js +179 -26
  174. package/dist/ui/line-math.js +20 -3
  175. package/dist/ui/md-formatter.js +100 -10
  176. package/dist/ui/output.js +5 -4
  177. package/dist/ui/plan-view.js +2 -7
  178. package/dist/ui/renderer.js +89 -85
  179. package/dist/ui/spinner.js +14 -4
  180. package/dist/utils/error.js +4 -0
  181. package/dist/utils/index.js +4 -0
  182. package/dist/utils/retry.js +17 -0
  183. package/dist/utils/sleep.js +23 -0
  184. package/dist/utils/truncate.js +9 -0
  185. package/package.json +1 -1
@@ -4,10 +4,14 @@ import { renderTable } from "../ui/table";
4
4
  import { t } from "../i18n/index";
5
5
  import { runSetup } from "./setup";
6
6
  import { saveConfig } from "../config/config";
7
+ import { HOSTED_BASE_URLS } from "../modules/providers/presets";
8
+ import { budgetBreakdownLines, setBudgetShare } from "../config/budget";
7
9
  import { getMessageText } from "../llm/provider";
10
+ import { estimateTokens } from "../llm/token-counter";
8
11
  import { join, dirname } from "path";
9
- import { homedir } from "os";
10
12
  import { existsSync } from "fs";
13
+ import { runMapAction } from "../modules/indexer/map-command";
14
+ import { errMsg } from "../utils";
11
15
  const version = readMmaVersion();
12
16
  export const COMMAND_GROUPS = {
13
17
  help: "general",
@@ -24,6 +28,7 @@ export const COMMAND_GROUPS = {
24
28
  reload: "agent",
25
29
  wizard: "agent",
26
30
  sysprompt: "agent",
31
+ map: "agent",
27
32
  lsp: "agent",
28
33
  sessions: "session",
29
34
  new: "session",
@@ -58,18 +63,18 @@ function registerBuiltinCommands(ctx) {
58
63
  description: t("repl.clear"),
59
64
  usage: t("repl.clear_usage"),
60
65
  action: () => {
61
- console.clear();
66
+ ctx.output.clearScreen();
62
67
  },
63
68
  });
64
69
  }
65
- function registerMmaCommands(ctx) {
70
+ function registerRunCommands(ctx) {
66
71
  ctx.registerCommand({
67
72
  name: "run",
68
73
  description: t("repl.run"),
69
74
  usage: t("repl.run_usage"),
70
75
  action: async (args) => {
71
76
  if (args.length === 0) {
72
- console.log(t("repl.run_usage"));
77
+ ctx.output.writeLine(t("repl.run_usage"));
73
78
  return;
74
79
  }
75
80
  const prompt = args.join(" ");
@@ -84,7 +89,7 @@ function registerMmaCommands(ctx) {
84
89
  action: async (args) => {
85
90
  const source = args.join(" ");
86
91
  if (!source) {
87
- console.log(t("repl.image_usage"));
92
+ ctx.output.writeLine(t("repl.image_usage"));
88
93
  return;
89
94
  }
90
95
  try {
@@ -96,7 +101,7 @@ function registerMmaCommands(ctx) {
96
101
  if (source.toLowerCase() === "clipboard") {
97
102
  const clipBuf = await readClipboardImage();
98
103
  if (!clipBuf) {
99
- console.log(pc.yellow(t("image.clipboard_empty")));
104
+ ctx.output.writeLine(pc.yellow(t("image.clipboard_empty")));
100
105
  return;
101
106
  }
102
107
  const { bufferToDataUrl } = await import("../llm/image-utils");
@@ -112,7 +117,7 @@ function registerMmaCommands(ctx) {
112
117
  else {
113
118
  const absPath = resolve(process.cwd(), source);
114
119
  if (!existsSync(absPath)) {
115
- console.log(pc.red(t("image.not_found", { path: source })));
120
+ ctx.output.writeLine(pc.red(t("image.not_found", { path: source })), "stderr");
116
121
  return;
117
122
  }
118
123
  const result = await loadFileAsDataUrl(absPath);
@@ -121,7 +126,7 @@ function registerMmaCommands(ctx) {
121
126
  }
122
127
  const contextManager = ctx.agent.contextManager;
123
128
  if (!contextManager) {
124
- console.log(pc.red(t("image.no_context")));
129
+ ctx.output.writeLine(pc.red(t("image.no_context")), "stderr");
125
130
  return;
126
131
  }
127
132
  contextManager.addPendingImage({
@@ -129,76 +134,72 @@ function registerMmaCommands(ctx) {
129
134
  image_url: { url: dataUrl },
130
135
  });
131
136
  const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
132
- console.log(pc.green(t("image.attached", { source: label, size: `${sizeKb} KB` })));
137
+ ctx.output.writeLine(pc.green(t("image.attached", { source: label, size: `${sizeKb} KB` })));
133
138
  }
134
139
  catch (err) {
135
- console.log(pc.red(t("image.error", { message: err.message })));
140
+ ctx.output.writeLine(pc.red(t("image.error", { message: err.message })), "stderr");
136
141
  }
137
142
  },
138
143
  });
144
+ }
145
+ function registerConfigCommands(ctx) {
139
146
  ctx.registerCommand({
140
147
  name: "config",
141
148
  description: t("repl.config"),
142
149
  usage: t("repl.config_usage"),
143
- action: () => {
150
+ action: (args) => {
151
+ // Subcommand routing: `/config migrate`. A separate registration with
152
+ // a space in the name was unreachable — the dispatcher splits input on
153
+ // whitespace, so "/config migrate" always resolved to plain "config".
154
+ if ((args[0] ?? "").trim().toLowerCase() === "migrate") {
155
+ return runConfigMigrate(ctx);
156
+ }
144
157
  const cfg = ctx.config;
145
158
  const ctxW = cfg.contextWindow;
146
159
  const sys = Math.floor(ctxW * cfg.contextBudget.systemPrompt);
147
160
  const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
148
- console.log(`${t("repl.model")} ${cfg.model}`);
161
+ ctx.output.writeLine(`${t("repl.model")} ${cfg.model}`);
149
162
  const providers = ctx.agent.listProviders();
150
163
  const active = providers.find((p) => p.active);
151
164
  if (active) {
152
- console.log(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) → ${pc.dim(active.baseUrl)}`);
165
+ ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) → ${pc.dim(active.baseUrl)}`);
153
166
  }
154
167
  else {
155
- console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
156
- }
157
- console.log(`${t("repl.context")} ${ctxW} (sys:${sys} res:${res} hist:${ctxW - sys - res})`);
158
- console.log(`${t("repl.max_iters")} ${cfg.maxToolIterations}`);
159
- console.log(`${t("repl.stuck_thresh")} ${cfg.stuckThreshold}`);
160
- console.log(`${t("repl.reasoning_label")} ${cfg.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"))}`);
161
- console.log(`${t("repl.log_level")} ${cfg.logLevel}`);
162
- console.log(`${t("repl.locale")} ${cfg.locale}`);
168
+ ctx.output.writeLine(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
169
+ }
170
+ ctx.output.writeLine(`${t("repl.context")} ${ctxW} (sys:${sys} res:${res} hist:${ctxW - sys - res})`);
171
+ ctx.output.writeLine(`${t("repl.max_iters")} ${cfg.maxToolIterations}`);
172
+ ctx.output.writeLine(`${t("repl.stuck_thresh")} ${cfg.stuckThreshold}`);
173
+ ctx.output.writeLine(`${t("repl.reasoning_label")} ${cfg.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"))}`);
174
+ ctx.output.writeLine(`${t("repl.log_level")} ${cfg.logLevel}`);
175
+ ctx.output.writeLine(`${t("repl.locale")} ${cfg.locale}`);
163
176
  const meta = ctx.sessionManager?.getActiveMeta();
164
177
  if (meta) {
165
- console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
166
- }
167
- },
168
- });
169
- ctx.registerCommand({
170
- name: "config migrate",
171
- description: t("cli.migrate_config"),
172
- action: async () => {
173
- const { hasDomainFiles } = await import("../config/domains");
174
- const { loadConfig: loadCfg } = await import("../config/config");
175
- const configDir = ctx.configDir;
176
- const configPath = join(configDir, "config.json");
177
- if (hasDomainFiles(configDir)) {
178
- console.log(pc.yellow(t("config.migrate_no_legacy")));
179
- return;
180
- }
181
- if (!existsSync(configPath)) {
182
- console.log(pc.yellow(t("config.migrate_no_legacy")));
183
- return;
178
+ ctx.output.writeLine(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
184
179
  }
185
- console.log(t("config.migrate_start"));
186
- const { config } = loadCfg({ configDir, projectConfigPath: join(configDir, ".mmrc") });
187
- saveConfig(config, configPath, configDir);
188
- const { renameSync, readdirSync } = await import("fs");
189
- renameSync(configPath, configPath + ".bak");
190
- const domainFiles = readdirSync(join(configDir, "config")).filter((f) => f.endsWith(".json"));
191
- console.log(pc.green(t("config.migrate_done", { count: String(domainFiles.length) })));
192
180
  },
193
181
  });
194
182
  ctx.registerCommand({
195
183
  name: "reasoning",
196
184
  description: t("repl.reasoning"),
197
185
  usage: t("repl.reasoning_usage"),
198
- action: () => {
199
- ctx.config.showReasoning = !ctx.config.showReasoning;
200
- const status = ctx.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"));
201
- console.log(t("repl.reasoning_status", { status }));
186
+ action: (args) => {
187
+ const level = args[0]?.trim().toLowerCase();
188
+ const VALID_LEVELS = ["auto", "none", "low", "medium", "high", "max"];
189
+ if (!level) {
190
+ // Toggle show/hide reasoning content
191
+ ctx.config.showReasoning = !ctx.config.showReasoning;
192
+ const status = ctx.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"));
193
+ ctx.output.writeLine(t("repl.reasoning_status", { status }));
194
+ }
195
+ else if (VALID_LEVELS.includes(level)) {
196
+ // Set reasoning effort level
197
+ ctx.agent.setReasoningLevel(level);
198
+ ctx.output.writeLine(pc.green(t("repl.reasoning_level_set", { level })));
199
+ }
200
+ else {
201
+ ctx.output.writeLine(pc.yellow(t("repl.reasoning_invalid_level", { level, valid: VALID_LEVELS.join(", ") })));
202
+ }
202
203
  },
203
204
  });
204
205
  ctx.registerCommand({
@@ -208,16 +209,16 @@ function registerMmaCommands(ctx) {
208
209
  action: () => {
209
210
  const providers = ctx.agent.listProviders();
210
211
  const active = providers.find((p) => p.active);
211
- console.log(`${t("repl.model")} ${ctx.config.model}`);
212
+ ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
212
213
  if (active) {
213
- console.log(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) @ ${pc.dim(active.baseUrl)}`);
214
+ ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) @ ${pc.dim(active.baseUrl)}`);
214
215
  }
215
216
  else {
216
- console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
217
+ ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
217
218
  }
218
219
  const meta = ctx.sessionManager?.getActiveMeta();
219
220
  if (meta) {
220
- console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
221
+ ctx.output.writeLine(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
221
222
  }
222
223
  },
223
224
  });
@@ -227,10 +228,10 @@ function registerMmaCommands(ctx) {
227
228
  aliases: ["setup"],
228
229
  usage: t("repl.wizard_usage"),
229
230
  action: async () => {
230
- console.log(pc.yellow(t("repl.wizard_running")));
231
+ ctx.output.writeLine(pc.yellow(t("repl.wizard_running")));
231
232
  await ctx.withExclusiveInput(async () => {
232
233
  const answers = await runSetup(ctx.rl);
233
- const configPath = join(homedir(), ".mma", "config.json");
234
+ const configPath = join(ctx.configDir, "config.json");
234
235
  ctx.config.provider.type = answers.provider;
235
236
  ctx.config.provider.baseUrl = answers.apiBase;
236
237
  ctx.config.provider.apiKey = answers.apiKey;
@@ -240,7 +241,7 @@ function registerMmaCommands(ctx) {
240
241
  ctx.config.locale = answers.locale;
241
242
  saveConfig(ctx.config, configPath, dirname(configPath));
242
243
  await ctx.agent.reconfigure(ctx.config);
243
- console.log(pc.green(t("cli.config_saved")));
244
+ ctx.output.writeLine(pc.green(t("cli.config_saved")));
244
245
  });
245
246
  },
246
247
  });
@@ -250,173 +251,268 @@ function registerMmaCommands(ctx) {
250
251
  usage: t("repl.sysprompt_desc"),
251
252
  action: () => {
252
253
  const info = ctx.agent.getSystemPromptInfo();
253
- console.log(pc.bold(`System prompt (${info.tokenCount} tokens):`));
254
- console.log(pc.dim("─".repeat(60)));
254
+ ctx.output.writeLine(pc.bold(t("repl.sysprompt_tokens", { count: String(info.tokenCount) })));
255
+ ctx.output.writeLine(pc.dim("─".repeat(60)));
255
256
  for (const line of info.text.split("\n")) {
256
- console.log(line);
257
+ ctx.output.writeLine(line);
257
258
  }
258
- console.log(pc.dim("─".repeat(60)));
259
+ ctx.output.writeLine(pc.dim("─".repeat(60)));
259
260
  if (info.excluded.length > 0) {
260
- console.log(pc.yellow(`\nExcluded blocks: ${info.excluded.length}`));
261
+ ctx.output.writeLine(pc.yellow(t("repl.excluded_blocks", { count: String(info.excluded.length) })));
261
262
  for (const e of info.excluded) {
262
- console.log(pc.dim(` - ${e.slice(0, 80)}${e.length > 80 ? "..." : ""}`));
263
+ ctx.output.writeLine(pc.dim(` - ${e.slice(0, 80)}${e.length > 80 ? "..." : ""}`));
263
264
  }
264
265
  }
265
266
  },
266
267
  });
267
268
  ctx.registerCommand({
268
- name: "provider",
269
- description: t("repl.provider_list"),
270
- usage: t("repl.provider_usage"),
269
+ name: "map",
270
+ description: t("repl.map"),
271
+ usage: t("repl.map_usage"),
271
272
  action: async (args) => {
272
- const subcmd = args[0];
273
- if (!subcmd || subcmd === "list") {
274
- const providers = ctx.agent.listProviders();
275
- if (providers.length === 0) {
276
- console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
277
- console.log(` ${ctx.config.provider.baseUrl}`);
278
- }
279
- else {
280
- console.log(t("repl.provider_current"));
281
- for (const p of providers) {
282
- const marker = p.active ? pc.green("* ") : " ";
283
- console.log(` ${marker}${p.label} (${pc.dim(p.type)}) ${pc.dim(p.baseUrl)}`);
284
- }
285
- }
273
+ const indexer = ctx.agent.getModule("indexer");
274
+ if (!indexer) {
275
+ ctx.output.writeLine(pc.yellow(t("indexer.not_indexed")));
286
276
  return;
287
277
  }
288
- if (subcmd === "use") {
289
- const name = args[1];
290
- if (!name) {
291
- console.log(t("repl.provider_usage"));
278
+ const action = args[0] || "summary";
279
+ const query = args.slice(1).join(" ");
280
+ const result = await runMapAction(indexer, action, query);
281
+ ctx.output.writeLine(result.output);
282
+ },
283
+ });
284
+ }
285
+ function registerProviderCommands(ctx) {
286
+ const list = () => {
287
+ const providers = ctx.agent.listProviders();
288
+ if (providers.length === 0) {
289
+ ctx.output.writeLine(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
290
+ ctx.output.writeLine(` ${ctx.config.provider.baseUrl}`);
291
+ return;
292
+ }
293
+ ctx.output.writeLine(t("repl.provider_current"));
294
+ for (const p of providers) {
295
+ const marker = p.active ? pc.green("* ") : " ";
296
+ ctx.output.writeLine(` ${marker}${p.label} (${pc.dim(p.type)}) ${pc.dim(p.baseUrl)}`);
297
+ }
298
+ };
299
+ const use = async (args) => {
300
+ const name = args[0];
301
+ if (!name) {
302
+ ctx.output.writeLine(t("repl.provider_usage"));
303
+ return;
304
+ }
305
+ try {
306
+ await ctx.agent.setProvider(name);
307
+ ctx.refreshModelCache();
308
+ ctx.output.writeLine(pc.green(t("repl.provider_set", { name })));
309
+ }
310
+ catch (e) {
311
+ ctx.output.writeLine(pc.red(e.message), "stderr");
312
+ }
313
+ };
314
+ const add = async (args) => {
315
+ const flags = {};
316
+ let name;
317
+ for (let i = 0; i < args.length; i++) {
318
+ const arg = args[i];
319
+ if (arg.startsWith("--")) {
320
+ const value = args[i + 1];
321
+ if (value === undefined || value.startsWith("--")) {
322
+ ctx.output.writeLine(pc.red(t("repl.provider_add_missing_value", { flag: arg })), "stderr");
292
323
  return;
293
324
  }
294
- try {
295
- await ctx.agent.setProvider(name);
296
- ctx.refreshModelCache();
297
- console.log(pc.green(t("repl.provider_set", { name })));
298
- }
299
- catch (e) {
300
- console.log(pc.red(e.message));
301
- }
325
+ flags[arg.slice(2)] = value;
326
+ i++;
327
+ }
328
+ else if (name === undefined) {
329
+ name = arg;
330
+ }
331
+ }
332
+ if (!name) {
333
+ ctx.output.writeLine(t("repl.provider_usage"));
334
+ return;
335
+ }
336
+ const entries = Array.isArray(ctx.config.provider.entries) ? ctx.config.provider.entries : [];
337
+ if (entries.length === 0) {
338
+ entries.push({
339
+ type: ctx.config.provider.type,
340
+ label: ctx.config.provider.type,
341
+ baseUrl: ctx.config.provider.baseUrl,
342
+ apiKey: ctx.config.provider.apiKey,
343
+ });
344
+ }
345
+ const baseUrl = flags.url || HOSTED_BASE_URLS[name] || HOSTED_BASE_URLS[`opencode-${name}`] || "";
346
+ if (!baseUrl) {
347
+ ctx.output.writeLine(pc.red(t("cli.provider_no_url", { name })), "stderr");
348
+ return;
349
+ }
350
+ entries.push({
351
+ type: name,
352
+ label: name,
353
+ baseUrl,
354
+ apiKey: flags.key,
355
+ ...(flags.priority !== undefined ? { priority: Number(flags.priority) } : {}),
356
+ ...(flags["context-window"] !== undefined
357
+ ? { contextWindow: Number(flags["context-window"]) }
358
+ : {}),
359
+ ...(flags.rpm !== undefined || flags.parallel !== undefined
360
+ ? {
361
+ rateLimits: {
362
+ maxRequestsPerMinute: Number(flags.rpm ?? 0) || 60,
363
+ maxParallelTasks: Number(flags.parallel ?? 0) || 1,
364
+ },
365
+ }
366
+ : {}),
367
+ });
368
+ ctx.config.provider.entries = entries;
369
+ ctx.config.provider.active = ctx.config.provider.active || ctx.config.provider.type;
370
+ const configPath = join(ctx.configDir, "config.json");
371
+ saveConfig(ctx.config, configPath, dirname(configPath));
372
+ await ctx.agent.reconfigure(ctx.config);
373
+ ctx.output.writeLine(pc.green(t("cli.provider_added", { name })));
374
+ ctx.output.writeLine(t("repl.provider_switch_hint", { name }));
375
+ };
376
+ const handlers = {
377
+ list,
378
+ use,
379
+ add,
380
+ };
381
+ ctx.registerCommand({
382
+ name: "provider",
383
+ description: t("repl.provider_list"),
384
+ usage: t("repl.provider_usage"),
385
+ action: async (args) => {
386
+ const subcmd = args[0];
387
+ const handler = subcmd ? handlers[subcmd] : handlers.list;
388
+ if (!handler) {
389
+ ctx.output.writeLine(t("repl.provider_usage"));
302
390
  return;
303
391
  }
304
- console.log(t("repl.provider_usage"));
392
+ await handler(args.slice(1));
305
393
  },
306
394
  });
395
+ const listModels = async () => {
396
+ ctx.output.writeLine(`${t("repl.model_current")} ${ctx.config.model}`);
397
+ const { OpenAICompatProvider } = await import("../llm/openai-compat");
398
+ const provider = new OpenAICompatProvider({
399
+ model: ctx.config.model,
400
+ baseUrl: ctx.config.provider.baseUrl,
401
+ apiKey: ctx.config.provider.apiKey,
402
+ contextWindow: ctx.config.contextWindow,
403
+ });
404
+ const { Spinner } = await import("../ui/spinner");
405
+ const s = new Spinner({ channel: ctx.output });
406
+ s.start(t("cli.fetching_models"));
407
+ try {
408
+ const models = await provider.listModels();
409
+ s.stop();
410
+ if (models.length > 0) {
411
+ ctx.refreshModelCache();
412
+ ctx.output.writeLine(t("cli.available_models"));
413
+ const { getCertMark } = await import("../modules/certification/manifest");
414
+ for (const m of models) {
415
+ const marker = m === ctx.config.model ? pc.green("* ") : " ";
416
+ const mark = getCertMark(m, ctx.config.provider.baseUrl, version, process.cwd());
417
+ const cert = mark === "certified" ? pc.green("✔") : mark === "stale" ? pc.yellow("○") : pc.dim("·");
418
+ const label = mark === "certified"
419
+ ? ` ${pc.green(t("cli.cert_label"))}`
420
+ : mark === "stale"
421
+ ? ` ${pc.yellow(t("cli.cert_stale_label"))}`
422
+ : "";
423
+ ctx.output.writeLine(` ${marker}${cert} ${m}${label}`);
424
+ }
425
+ }
426
+ else {
427
+ ctx.output.writeLine(t("cli.no_models_found"));
428
+ }
429
+ }
430
+ catch (err) {
431
+ s.stop();
432
+ ctx.output.writeLine(t("cli.model_fetch_failed", { error: String(err) }), "stderr");
433
+ }
434
+ ctx.output.writeLine(t("cli.model_hint"));
435
+ };
436
+ const useModel = async (args) => {
437
+ const name = args[0];
438
+ if (!name) {
439
+ ctx.output.writeLine(t("repl.model_usage"));
440
+ return;
441
+ }
442
+ ctx.config.model = name;
443
+ const configPath = join(ctx.configDir, "config.json");
444
+ saveConfig(ctx.config, configPath, dirname(configPath));
445
+ await ctx.agent.reconfigure(ctx.config);
446
+ ctx.output.writeLine(pc.green(t("repl.model_set", { name })));
447
+ };
448
+ const modelHandlers = {
449
+ list: listModels,
450
+ use: useModel,
451
+ };
307
452
  ctx.registerCommand({
308
453
  name: "model",
309
454
  description: t("repl.model_list"),
310
455
  usage: t("repl.model_usage"),
311
456
  action: async (args) => {
312
457
  const subcmd = args[0];
313
- if (!subcmd || subcmd === "list") {
314
- console.log(`${t("repl.model_current")} ${ctx.config.model}`);
315
- const { OpenAICompatProvider } = await import("../llm/openai-compat");
316
- const provider = new OpenAICompatProvider({
317
- model: ctx.config.model,
318
- baseUrl: ctx.config.provider.baseUrl,
319
- apiKey: ctx.config.provider.apiKey,
320
- contextWindow: ctx.config.contextWindow,
321
- });
322
- const { Spinner } = await import("../ui/spinner");
323
- const s = new Spinner();
324
- s.start(t("cli.fetching_models"));
325
- try {
326
- const models = await provider.listModels();
327
- s.stop();
328
- if (models.length > 0) {
329
- ctx.refreshModelCache();
330
- console.log(t("cli.available_models"));
331
- const { getCertMark } = await import("../modules/certification/manifest");
332
- for (const m of models) {
333
- const marker = m === ctx.config.model ? pc.green("* ") : " ";
334
- const mark = getCertMark(m, ctx.config.provider.baseUrl, version, process.cwd());
335
- const cert = mark === "certified"
336
- ? pc.green("✔")
337
- : mark === "stale"
338
- ? pc.yellow("○")
339
- : pc.dim("·");
340
- console.log(` ${marker}${cert} ${m}`);
341
- }
342
- }
343
- else {
344
- console.log(t("cli.no_models_found"));
345
- }
346
- }
347
- catch (err) {
348
- s.stop();
349
- console.log(t("cli.model_fetch_failed", { error: String(err) }));
350
- }
351
- console.log(t("cli.model_hint"));
352
- return;
353
- }
354
- if (subcmd === "use") {
355
- const name = args[1];
356
- if (!name) {
357
- console.log(t("repl.model_usage"));
358
- return;
359
- }
360
- ctx.config.model = name;
361
- const configPath = join(homedir(), ".mma", "config.json");
362
- saveConfig(ctx.config, configPath, dirname(configPath));
363
- await ctx.agent.reconfigure(ctx.config);
364
- console.log(pc.green(t("repl.model_set", { name })));
458
+ const handler = subcmd ? modelHandlers[subcmd] : modelHandlers.list;
459
+ if (!handler) {
460
+ ctx.output.writeLine(t("repl.model_usage"));
365
461
  return;
366
462
  }
367
- console.log(t("repl.model_usage"));
463
+ await handler(args.slice(1));
368
464
  },
369
465
  });
370
466
  ctx.registerCommand({
371
467
  name: "context",
372
468
  description: t("cli.manage_context"),
373
- usage: "/context <size>",
469
+ usage: t("repl.context_usage"),
374
470
  action: async (args) => {
471
+ const save = async () => {
472
+ const configPath = join(ctx.configDir, "config.json");
473
+ saveConfig(ctx.config, configPath, dirname(configPath));
474
+ await ctx.agent.reconfigure(ctx.config);
475
+ };
375
476
  if (args.length === 0) {
376
- console.log(`/context ${t("repl.context")} ${ctx.config.contextWindow}`);
377
- console.log(`Usage: /context <size> (min 1024)`);
477
+ for (const line of budgetBreakdownLines(ctx.config))
478
+ ctx.output.writeLine(line);
479
+ ctx.output.writeLine(pc.dim(t("repl.context_usage")));
480
+ return;
481
+ }
482
+ if (args[0] === "system" || args[0] === "reserve") {
483
+ const key = args[0];
484
+ const value = Number(args[1]);
485
+ if (setBudgetShare(ctx.config, key, value) !== null) {
486
+ ctx.output.writeLine(pc.yellow(t("cli.context_invalid_fraction")));
487
+ return;
488
+ }
489
+ await save();
490
+ ctx.output.writeLine(pc.green(t("cli.context_fraction_set", { key, value })));
378
491
  return;
379
492
  }
380
493
  const size = parseInt(args[0], 10);
381
494
  if (isNaN(size) || size < 1024) {
382
- console.log(t("cli.invalid_context_size"));
495
+ ctx.output.writeLine(t("cli.invalid_context_size"));
383
496
  return;
384
497
  }
385
498
  ctx.config.contextWindow = size;
386
- const configPath = join(homedir(), ".mma", "config.json");
387
- saveConfig(ctx.config, configPath, dirname(configPath));
388
- await ctx.agent.reconfigure(ctx.config);
389
- console.log(pc.green(t("cli.context_set", { size })));
499
+ await save();
500
+ ctx.output.writeLine(pc.green(t("cli.context_set", { size })));
390
501
  },
391
502
  });
503
+ }
504
+ function registerAgentCommands(ctx) {
392
505
  ctx.registerCommand({
393
506
  name: "reload",
394
507
  description: t("repl.reload"),
395
508
  usage: t("repl.reload_usage"),
396
509
  action: async () => {
397
- console.log(pc.yellow(t("repl.reloading")));
398
- if (ctx.sessionManager && ctx.config.session.autoSave) {
399
- }
400
- ctx.agent.shutdown();
401
- const { loadConfig } = await import("../config/config");
402
- const { homedir } = await import("os");
403
- const { join } = await import("path");
404
- const configDir = ctx.configDir;
405
- const baseDir = ctx.baseDir;
406
- const projectConfigPath = join(baseDir, ".mmrc");
407
- const { config: freshConfig } = loadConfig({ configDir, projectConfigPath });
408
- Object.assign(ctx.config, freshConfig);
409
- const { bootstrap } = await import("../core/bootstrap");
410
- const result = await bootstrap(configDir, baseDir, false, false);
411
- ctx.agent = result.agent;
412
- ctx.sessionManager = result.sessionManager;
413
- ctx.skillsModule = result.skillsModule;
414
- ctx.pluginManager = result.pluginManager;
415
- ctx.setupCompleter();
416
- console.log(pc.green(t("repl.reloaded")));
417
- console.log(`${t("repl.model")} ${ctx.config.model}`);
418
- console.log(`${t("repl.context")} ${ctx.config.contextWindow}`);
419
- console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
510
+ ctx.output.writeLine(pc.yellow(t("repl.reloading")));
511
+ await ctx.reload();
512
+ ctx.output.writeLine(pc.green(t("repl.reloaded")));
513
+ ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
514
+ ctx.output.writeLine(`${t("repl.context")} ${ctx.config.contextWindow}`);
515
+ ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
420
516
  },
421
517
  });
422
518
  ctx.registerCommand({
@@ -425,18 +521,18 @@ function registerMmaCommands(ctx) {
425
521
  usage: t("repl.plugins_usage"),
426
522
  action: (args) => {
427
523
  if (!ctx.pluginManager) {
428
- console.log(t("cli.plugins.none"));
524
+ ctx.output.writeLine(t("cli.plugins.none"));
429
525
  return;
430
526
  }
431
527
  const showAll = args.includes("--all");
432
528
  const infos = ctx.pluginManager.getPluginInfos();
433
529
  const filtered = showAll ? infos : infos.filter(({ plugin }) => !plugin.isBuiltin);
434
530
  if (filtered.length === 0) {
435
- console.log(t("cli.plugins.none"));
531
+ ctx.output.writeLine(t("cli.plugins.none"));
436
532
  return;
437
533
  }
438
534
  const mmaVersion = version;
439
- console.log(t("cli.plugins.header", {
535
+ ctx.output.writeLine(t("cli.plugins.header", {
440
536
  count: String(filtered.length),
441
537
  mma: mmaVersion,
442
538
  }));
@@ -444,16 +540,18 @@ function registerMmaCommands(ctx) {
444
540
  const v = plugin.version || "-";
445
541
  const origin = source || "builtin";
446
542
  const mark = plugin.isBuiltin ? t("cli.plugins.builtin_mark") : " ";
447
- console.log(` ${mark} ${plugin.name} v${v} [${origin}]`);
543
+ ctx.output.writeLine(` ${mark} ${plugin.name} v${v} [${origin}]`);
448
544
  }
449
545
  if (!showAll) {
450
546
  const builtinCount = infos.filter(({ plugin }) => plugin.isBuiltin).length;
451
547
  if (builtinCount > 0) {
452
- console.log(pc.dim(` (${builtinCount} builtin hidden — /plugins --all to show)`));
548
+ ctx.output.writeLine(pc.dim(` (${builtinCount} builtin hidden — /plugins --all to show)`));
453
549
  }
454
550
  }
455
551
  },
456
552
  });
553
+ }
554
+ function registerLspCommands(ctx) {
457
555
  ctx.registerCommand({
458
556
  name: "lsp",
459
557
  description: t("repl.lsp"),
@@ -463,7 +561,7 @@ function registerMmaCommands(ctx) {
463
561
  // Access LSP module via the agent's module registry
464
562
  const lspModule = ctx.agent.getModule("lsp");
465
563
  if (!lspModule) {
466
- console.log(pc.yellow(t("repl.lsp_not_available")));
564
+ ctx.output.writeLine(pc.yellow(t("repl.lsp_not_available")));
467
565
  return;
468
566
  }
469
567
  switch (subcommand) {
@@ -471,57 +569,62 @@ function registerMmaCommands(ctx) {
471
569
  const disabled = lspModule.getDisabledServers();
472
570
  const failures = lspModule.getFailureCounts();
473
571
  const config = ctx.config.lsp;
474
- console.log(pc.bold(t("repl.lsp_status_header")));
475
- console.log(`${t("repl.lsp_enabled")} ${config?.enabled ? pc.green("yes") : pc.red("no")}`);
572
+ ctx.output.writeLine(pc.bold(t("repl.lsp_status_header")));
573
+ ctx.output.writeLine(`${t("repl.lsp_enabled")} ${config?.enabled ? pc.green("yes") : pc.red("no")}`);
476
574
  if (disabled.length > 0) {
477
- console.log(pc.yellow(`${t("repl.lsp_disabled_servers")} ${disabled.join(", ")}`));
575
+ ctx.output.writeLine(pc.yellow(`${t("repl.lsp_disabled_servers")} ${disabled.join(", ")}`));
478
576
  }
479
577
  if (failures.size > 0) {
480
- console.log(pc.dim(t("repl.lsp_failure_counts")));
578
+ ctx.output.writeLine(pc.dim(t("repl.lsp_failure_counts")));
481
579
  for (const [server, count] of failures) {
482
- console.log(pc.dim(` ${server}: ${count}`));
580
+ ctx.output.writeLine(pc.dim(` ${server}: ${count}`));
483
581
  }
484
582
  }
485
583
  if (disabled.length === 0 && failures.size === 0) {
486
- console.log(pc.green(t("repl.lsp_all_ok")));
584
+ ctx.output.writeLine(pc.green(t("repl.lsp_all_ok")));
487
585
  }
488
586
  break;
489
587
  }
490
588
  case "restart": {
491
- console.log(pc.yellow(t("repl.lsp_restarting")));
589
+ ctx.output.writeLine(pc.yellow(t("repl.lsp_restarting")));
492
590
  lspModule.resetDisabledServers();
493
- console.log(pc.green(t("repl.lsp_restarted")));
591
+ ctx.output.writeLine(pc.green(t("repl.lsp_restarted")));
494
592
  break;
495
593
  }
496
594
  case "check": {
497
595
  const path = args[1];
498
596
  if (!path) {
499
- console.log(pc.yellow(t("repl.lsp_check_usage")));
597
+ ctx.output.writeLine(pc.yellow(t("repl.lsp_check_usage")));
500
598
  return;
501
599
  }
502
- console.log(pc.dim(t("repl.lsp_checking", { path })));
503
- // Run lsp_check tool via the agent's tool executor
600
+ ctx.output.writeLine(pc.dim(t("repl.lsp_checking", { path })));
601
+ // Запуск тула lsp_check через публичный API агента (не через приватные deps)
504
602
  try {
505
- const result = await ctx.agent.deps?.toolExecutor?.execute("lsp_check", { path }, ctx.agent.deps?.toolCtx);
603
+ const result = await ctx.agent.runTool("lsp_check", { path });
506
604
  if (result?.output) {
507
- console.log(result.output);
605
+ ctx.output.writeLine(result.output);
508
606
  }
509
607
  }
510
608
  catch (e) {
511
- console.log(pc.red(t("repl.lsp_check_error", {
512
- error: e instanceof Error ? e.message : String(e),
513
- })));
609
+ ctx.output.writeLine(pc.red(t("repl.lsp_check_error", { error: errMsg(e) })), "stderr");
514
610
  }
515
611
  break;
516
612
  }
517
613
  default: {
518
- console.log(pc.dim(t("repl.lsp_usage")));
614
+ ctx.output.writeLine(pc.dim(t("repl.lsp_usage")));
519
615
  break;
520
616
  }
521
617
  }
522
618
  },
523
619
  });
524
620
  }
621
+ function registerMmaCommands(ctx) {
622
+ registerRunCommands(ctx);
623
+ registerConfigCommands(ctx);
624
+ registerProviderCommands(ctx);
625
+ registerAgentCommands(ctx);
626
+ registerLspCommands(ctx);
627
+ }
525
628
  function registerSessionCommands(ctx) {
526
629
  if (!ctx.sessionManager)
527
630
  return;
@@ -534,7 +637,7 @@ function registerSessionCommands(ctx) {
534
637
  const sessions = ctx.sessionManager.list();
535
638
  const active = ctx.sessionManager.getActive();
536
639
  if (sessions.length === 0) {
537
- console.log(t("session.no_sessions_hint"));
640
+ ctx.output.writeLine(t("session.no_sessions_hint"));
538
641
  return;
539
642
  }
540
643
  const rows = sessions.map((s) => [
@@ -551,9 +654,9 @@ function registerSessionCommands(ctx) {
551
654
  t("session.col_updated"),
552
655
  t("session.col_msgs"),
553
656
  ], rows)) {
554
- console.log(line);
657
+ ctx.output.writeLine(line);
555
658
  }
556
- console.log(pc.dim(`\n ${t("repl.resume_hint")}`));
659
+ ctx.output.writeLine(pc.dim(`\n ${t("repl.resume_hint")}`));
557
660
  },
558
661
  });
559
662
  ctx.registerCommand({
@@ -565,9 +668,9 @@ function registerSessionCommands(ctx) {
565
668
  const name = args.join(" ") || undefined;
566
669
  const meta = ctx.sessionManager.create(name);
567
670
  ctx.agent.clearContext();
568
- console.clear();
569
- console.log(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
570
- console.log(pc.dim(` ${t("session.chat_cleared")}\n`));
671
+ ctx.output.clearScreen();
672
+ ctx.output.writeLine(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
673
+ ctx.output.writeLine(pc.dim(` ${t("session.chat_cleared")}\n`));
571
674
  },
572
675
  });
573
676
  ctx.registerCommand({
@@ -579,54 +682,55 @@ function registerSessionCommands(ctx) {
579
682
  const query = args.join(" ");
580
683
  const sessions = ctx.sessionManager.list();
581
684
  if (!query) {
582
- console.log(pc.dim(t("session.available")));
685
+ ctx.output.writeLine(pc.dim(t("session.available")));
583
686
  const active = ctx.sessionManager.getActive();
584
687
  for (const s of sessions) {
585
688
  const marker = s.id === active ? pc.green(" *") : " ";
586
- console.log(pc.dim(` ${marker} ${s.id.slice(0, 12)} ${s.name}`));
689
+ ctx.output.writeLine(pc.dim(` ${marker} ${s.id.slice(0, 12)} ${s.name}`));
587
690
  }
588
- console.log(pc.dim(`\n ${t("repl.resume_usage")}`));
691
+ ctx.output.writeLine(pc.dim(`\n ${t("repl.resume_usage")}`));
589
692
  return;
590
693
  }
591
694
  const match = sessions.find((s) => s.id === query ||
592
695
  s.id.startsWith(query) ||
593
696
  s.name.toLowerCase().includes(query.toLowerCase()));
594
697
  if (!match) {
595
- console.log(t("session.no_match", { query }));
698
+ ctx.output.writeLine(t("session.no_match", { query }));
596
699
  return;
597
700
  }
598
701
  ctx.sessionManager.setActive(match.id);
599
702
  const history = ctx.sessionManager.loadHistory();
600
703
  ctx.agent.setContext(history);
601
- console.clear();
602
- console.log(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
704
+ ctx.output.clearScreen();
705
+ ctx.output.writeLine(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
603
706
  " " +
604
707
  pc.dim(`(${match.id.slice(0, 12)})`) +
605
708
  " — " +
606
709
  match.messageCount +
607
- " msgs");
608
- console.log(pc.dim("─".repeat(50)));
710
+ " " +
711
+ t("repl.msgs"));
712
+ ctx.output.writeLine(pc.dim("─".repeat(50)));
609
713
  if (history.length === 0) {
610
- console.log(pc.dim(t("session.no_history")));
714
+ ctx.output.writeLine(pc.dim(t("session.no_history")));
611
715
  }
612
716
  else {
613
- console.log(pc.dim(t("session.chat_history")));
614
- console.log();
717
+ ctx.output.writeLine(pc.dim(t("session.chat_history")));
718
+ ctx.output.writeLine("");
615
719
  for (const msg of history) {
616
720
  if (msg.role === "user") {
617
- console.log(pc.cyan(t("session.user_label") + ":"));
618
- console.log(getMessageText(msg.content));
619
- console.log();
721
+ ctx.output.writeLine(pc.cyan(t("session.user_label") + ":"));
722
+ ctx.output.writeLine(getMessageText(msg.content));
723
+ ctx.output.writeLine("");
620
724
  }
621
725
  else if (msg.role === "assistant") {
622
- console.log(pc.green(t("session.assistant_label") + ":"));
623
- console.log(getMessageText(msg.content));
624
- console.log();
726
+ ctx.output.writeLine(pc.green(t("session.assistant_label") + ":"));
727
+ ctx.output.writeLine(getMessageText(msg.content));
728
+ ctx.output.writeLine("");
625
729
  }
626
730
  }
627
731
  }
628
- console.log(pc.dim("─".repeat(50)));
629
- console.log(pc.dim(` ${t("session.chat_loaded")}`));
732
+ ctx.output.writeLine(pc.dim("─".repeat(50)));
733
+ ctx.output.writeLine(pc.dim(` ${t("session.chat_loaded")}`));
630
734
  },
631
735
  });
632
736
  ctx.registerCommand({
@@ -636,16 +740,16 @@ function registerSessionCommands(ctx) {
636
740
  action: (args) => {
637
741
  const name = args.join(" ");
638
742
  if (!name) {
639
- console.log(t("repl.rename_usage"));
743
+ ctx.output.writeLine(t("repl.rename_usage"));
640
744
  return;
641
745
  }
642
746
  const active = ctx.sessionManager.getActive();
643
747
  if (!active) {
644
- console.log(t("session.no_active"));
748
+ ctx.output.writeLine(t("session.no_active"));
645
749
  return;
646
750
  }
647
751
  ctx.sessionManager.rename(active, name);
648
- console.log(t("session.renamed", { name }));
752
+ ctx.output.writeLine(t("session.renamed", { name }));
649
753
  },
650
754
  });
651
755
  ctx.registerCommand({
@@ -656,106 +760,136 @@ function registerSessionCommands(ctx) {
656
760
  action: (args) => {
657
761
  const query = args[0];
658
762
  if (!query) {
659
- console.log(t("repl.delete_usage"));
763
+ ctx.output.writeLine(t("repl.delete_usage"));
660
764
  return;
661
765
  }
662
766
  const sessions = ctx.sessionManager.list();
663
767
  const match = sessions.find((s) => s.id === query || s.id.startsWith(query));
664
768
  if (!match) {
665
- console.log(t("session.no_match", { query }));
769
+ ctx.output.writeLine(t("session.no_match", { query }));
666
770
  return;
667
771
  }
668
772
  ctx.sessionManager.delete(match.id);
669
- console.log(`${t("session.deleted", { id: match.id })}: ${match.name}`);
773
+ ctx.output.writeLine(`${t("session.deleted", { id: match.id })}: ${match.name}`);
670
774
  },
671
775
  });
672
776
  }
673
777
  function registerSkillCommands(ctx) {
674
778
  if (!ctx.skillsModule)
675
779
  return;
780
+ const listSkills = () => {
781
+ const available = ctx.skillsModule.getAvailable();
782
+ if (available.length === 0) {
783
+ ctx.output.writeLine(t("repl.no_skills"));
784
+ return;
785
+ }
786
+ ctx.output.writeLine(pc.bold(t("repl.available_skills")));
787
+ for (const skill of available) {
788
+ const tokens = estimateTokens(skill.content);
789
+ const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
790
+ const marker = loaded ? pc.green(" [loaded]") : "";
791
+ ctx.output.writeLine(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
792
+ }
793
+ };
794
+ const listLoaded = () => {
795
+ const loaded = ctx.skillsModule.getLoaded();
796
+ const budget = ctx.skillsModule.getBudget();
797
+ if (loaded.length === 0) {
798
+ ctx.output.writeLine(t("repl.no_loaded"));
799
+ return;
800
+ }
801
+ ctx.output.writeLine(pc.bold(t("repl.loaded_skills")));
802
+ for (const skill of loaded) {
803
+ const tokens = estimateTokens(skill.content);
804
+ ctx.output.writeLine(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
805
+ }
806
+ ctx.output.writeLine(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
807
+ };
808
+ const loadSkill = (args) => {
809
+ const arg = args.join(" ");
810
+ if (!arg) {
811
+ ctx.output.writeLine(t("repl.skill_load_usage"));
812
+ return;
813
+ }
814
+ const result = ctx.skillsModule.loadByName(arg);
815
+ if (result.success) {
816
+ ctx.output.writeLine(pc.green(result.message));
817
+ }
818
+ else {
819
+ ctx.output.writeLine(pc.red(result.message), "stderr");
820
+ }
821
+ };
822
+ const unloadSkill = (args) => {
823
+ const arg = args.join(" ");
824
+ if (!arg) {
825
+ ctx.output.writeLine(t("repl.skill_unload_usage"));
826
+ return;
827
+ }
828
+ if (ctx.skillsModule.unload(arg)) {
829
+ ctx.output.writeLine(pc.green(t("repl.skill_unloaded", { name: arg })));
830
+ }
831
+ else {
832
+ ctx.output.writeLine(pc.red(t("repl.skill_not_loaded", { name: arg })), "stderr");
833
+ }
834
+ };
835
+ const searchSkills = (args) => {
836
+ const arg = args.join(" ");
837
+ if (!arg) {
838
+ ctx.output.writeLine(t("repl.skill_search_usage"));
839
+ return;
840
+ }
841
+ const results = ctx.skillsModule.search(arg);
842
+ if (results.length === 0) {
843
+ ctx.output.writeLine(t("repl.no_skill_match", { query: arg }));
844
+ return;
845
+ }
846
+ ctx.output.writeLine(pc.bold(t("repl.skills_matching", { query: arg })));
847
+ for (const skill of results) {
848
+ const tokens = estimateTokens(skill.content);
849
+ ctx.output.writeLine(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
850
+ }
851
+ };
852
+ const skillHandlers = {
853
+ list: listSkills,
854
+ loaded: listLoaded,
855
+ load: loadSkill,
856
+ unload: unloadSkill,
857
+ search: searchSkills,
858
+ };
676
859
  ctx.registerCommand({
677
860
  name: "skill",
678
861
  description: t("repl.skill"),
679
862
  usage: t("repl.skill_usage"),
680
863
  action: (args) => {
681
864
  const subcmd = args[0];
682
- const arg = args.slice(1).join(" ");
683
- if (!subcmd || subcmd === "list") {
684
- const available = ctx.skillsModule.getAvailable();
685
- if (available.length === 0) {
686
- console.log(t("repl.no_skills"));
687
- return;
688
- }
689
- console.log(pc.bold(t("repl.available_skills")));
690
- for (const skill of available) {
691
- const tokens = Math.ceil(skill.content.length / 4);
692
- const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
693
- const marker = loaded ? pc.green(" [loaded]") : "";
694
- console.log(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
695
- }
696
- return;
697
- }
698
- if (subcmd === "loaded") {
699
- const loaded = ctx.skillsModule.getLoaded();
700
- const budget = ctx.skillsModule.getBudget();
701
- if (loaded.length === 0) {
702
- console.log(t("repl.no_loaded"));
703
- return;
704
- }
705
- console.log(pc.bold(t("repl.loaded_skills")));
706
- for (const skill of loaded) {
707
- const tokens = Math.ceil(skill.content.length / 4);
708
- console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
709
- }
710
- console.log(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
865
+ const handler = subcmd ? skillHandlers[subcmd] : skillHandlers.list;
866
+ if (!handler) {
867
+ ctx.output.writeLine(pc.red(t("repl.skill_unknown_sub", { subcmd })), "stderr");
868
+ ctx.output.writeLine(t("repl.skill_usage"));
711
869
  return;
712
870
  }
713
- if (subcmd === "load") {
714
- if (!arg) {
715
- console.log(t("repl.skill_load_usage"));
716
- return;
717
- }
718
- const result = ctx.skillsModule.loadByName(arg);
719
- if (result.success) {
720
- console.log(pc.green(result.message));
721
- }
722
- else {
723
- console.log(pc.red(result.message));
724
- }
725
- return;
726
- }
727
- if (subcmd === "unload") {
728
- if (!arg) {
729
- console.log(t("repl.skill_unload_usage"));
730
- return;
731
- }
732
- if (ctx.skillsModule.unload(arg)) {
733
- console.log(pc.green(t("repl.skill_unloaded", { name: arg })));
734
- }
735
- else {
736
- console.log(pc.red(t("repl.skill_not_loaded", { name: arg })));
737
- }
738
- return;
739
- }
740
- if (subcmd === "search") {
741
- if (!arg) {
742
- console.log(t("repl.skill_search_usage"));
743
- return;
744
- }
745
- const results = ctx.skillsModule.search(arg);
746
- if (results.length === 0) {
747
- console.log(t("repl.no_skill_match", { query: arg }));
748
- return;
749
- }
750
- console.log(pc.bold(t("repl.skills_matching", { query: arg })));
751
- for (const skill of results) {
752
- const tokens = Math.ceil(skill.content.length / 4);
753
- console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
754
- }
755
- return;
756
- }
757
- console.log(pc.red(t("repl.skill_unknown_sub", { subcmd })));
758
- console.log(t("repl.skill_usage"));
871
+ handler(args.slice(1));
759
872
  },
760
873
  });
761
874
  }
875
+ async function runConfigMigrate(ctx) {
876
+ const { hasDomainFiles } = await import("../config/domains");
877
+ const { loadConfig: loadCfg } = await import("../config/config");
878
+ const configDir = ctx.configDir;
879
+ const configPath = join(configDir, "config.json");
880
+ if (hasDomainFiles(configDir)) {
881
+ ctx.output.writeLine(pc.yellow(t("config.migrate_no_legacy")));
882
+ return;
883
+ }
884
+ if (!existsSync(configPath)) {
885
+ ctx.output.writeLine(pc.yellow(t("config.migrate_no_legacy")));
886
+ return;
887
+ }
888
+ ctx.output.writeLine(t("config.migrate_start"));
889
+ const { config } = loadCfg({ configDir, projectConfigPath: join(configDir, ".mmrc") });
890
+ saveConfig(config, configPath, configDir);
891
+ const { renameSync, readdirSync } = await import("fs");
892
+ renameSync(configPath, configPath + ".bak");
893
+ const domainFiles = readdirSync(join(configDir, "config")).filter((f) => f.endsWith(".json"));
894
+ ctx.output.writeLine(pc.green(t("config.migrate_done", { count: String(domainFiles.length) })));
895
+ }