micro-models-agent 2.10.0 → 2.12.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 (359) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/app.js +22 -4
  3. package/dist/assets/index.html +29 -0
  4. package/dist/assets/js/dom.js +2 -1
  5. package/dist/assets/js/feed.js +38 -26
  6. package/dist/assets/js/format.js +1 -1
  7. package/dist/assets/js/rail.js +114 -0
  8. package/dist/assets/js/settings-controls.js +2 -2
  9. package/dist/assets/js/settings.js +246 -51
  10. package/dist/assets/js/store.js +3 -0
  11. package/dist/assets/js/tabs.js +104 -2
  12. package/dist/assets/styles.css +215 -66
  13. package/dist/cli/cache-line.js +1 -0
  14. package/dist/cli/command-suggest.js +5 -4
  15. package/dist/cli/commands.js +231 -122
  16. package/dist/cli/completer.js +132 -31
  17. package/dist/cli/index.js +1 -0
  18. package/dist/cli/json-payload.js +4 -4
  19. package/dist/cli/main.js +166 -61
  20. package/dist/cli/onboarding-commands.js +126 -0
  21. package/dist/cli/plugin-commands.js +2 -1
  22. package/dist/cli/relaunch.js +16 -13
  23. package/dist/cli/repl-commands-lsp.js +75 -0
  24. package/dist/cli/repl-commands-skills.js +105 -0
  25. package/dist/cli/repl-commands.js +589 -266
  26. package/dist/cli/repl.js +436 -177
  27. package/dist/cli/run-result.js +5 -6
  28. package/dist/cli/security-commands.js +37 -28
  29. package/dist/cli/setup-order.js +4 -55
  30. package/dist/cli/setup-prompt.js +3 -2
  31. package/dist/cli/setup.js +27 -107
  32. package/dist/cli/web-command.js +98 -0
  33. package/dist/config/budget.js +5 -5
  34. package/dist/config/config.js +103 -96
  35. package/dist/config/defaults.js +38 -8
  36. package/dist/config/domains.js +24 -27
  37. package/dist/config/env.js +46 -0
  38. package/dist/config/experts.js +2 -0
  39. package/dist/config/index.js +1 -0
  40. package/dist/config/paths.js +56 -0
  41. package/dist/config/redact.js +44 -0
  42. package/dist/config/registry-extra.js +334 -0
  43. package/dist/config/registry.js +584 -0
  44. package/dist/config/security.js +35 -36
  45. package/dist/config/timeouts.js +74 -0
  46. package/dist/config/utils.js +16 -15
  47. package/dist/core/agent/artifact-progress.js +59 -0
  48. package/dist/core/agent/artifact-resolve.js +79 -0
  49. package/dist/core/agent/audit-gate.js +7 -3
  50. package/dist/core/agent/compaction.js +2 -2
  51. package/dist/core/agent/constants.js +33 -4
  52. package/dist/core/agent/context-log.js +454 -0
  53. package/dist/core/agent/context-renderer.js +5 -0
  54. package/dist/core/agent/critic-gate.js +132 -0
  55. package/dist/core/agent/hallucination-gate.js +148 -38
  56. package/dist/core/agent/loop-state.js +15 -0
  57. package/dist/core/agent/prefix-monitor.js +1 -1
  58. package/dist/core/agent/reasoning-resolver.js +10 -1
  59. package/dist/core/agent/token-tracker.js +7 -4
  60. package/dist/core/agent/tool-batch.js +93 -18
  61. package/dist/core/agent/tool-images.js +18 -0
  62. package/dist/core/agent/tool-output.js +65 -11
  63. package/dist/core/agent/tool-pairing.js +60 -0
  64. package/dist/core/agent-moe.js +25 -24
  65. package/dist/core/agent.js +429 -108
  66. package/dist/core/bootstrap/agent-phase.js +66 -0
  67. package/dist/core/bootstrap/config-phase.js +86 -0
  68. package/dist/core/bootstrap/modules-phase.js +269 -0
  69. package/dist/core/bootstrap/provider-phase.js +183 -0
  70. package/dist/core/bootstrap/session-phase.js +204 -0
  71. package/dist/core/bootstrap/state.js +94 -0
  72. package/dist/core/bootstrap/workspace-phase.js +115 -0
  73. package/dist/core/bootstrap.js +92 -660
  74. package/dist/core/crash-handler.js +17 -7
  75. package/dist/core/environment.js +37 -12
  76. package/dist/core/host-plan.js +43 -0
  77. package/dist/core/host.js +23 -0
  78. package/dist/core/index.js +1 -0
  79. package/dist/core/prompt-builder.js +17 -1
  80. package/dist/core/prompt-overflow.js +42 -43
  81. package/dist/core/session-logger.js +122 -30
  82. package/dist/core/system-prompt.js +117 -0
  83. package/dist/core/version.js +6 -4
  84. package/dist/core/web-host.js +34 -0
  85. package/dist/core/workspace.js +19 -15
  86. package/dist/i18n/en.json +106 -21
  87. package/dist/i18n/index.js +37 -3
  88. package/dist/i18n/ru.json +109 -21
  89. package/dist/index.js +7 -3
  90. package/dist/llm/image-utils.js +55 -64
  91. package/dist/llm/llm-errors.js +9 -0
  92. package/dist/llm/model-loader.js +10 -32
  93. package/dist/llm/models-dev.js +121 -0
  94. package/dist/llm/openai-compat.js +146 -73
  95. package/dist/llm/orchestrator.js +33 -11
  96. package/dist/llm/provider-budget.js +2 -2
  97. package/dist/llm/provider-context.js +233 -0
  98. package/dist/llm/provider.js +1 -1
  99. package/dist/llm/response.js +5 -0
  100. package/dist/llm/token-counter.js +27 -11
  101. package/dist/logger/app-logger.js +48 -22
  102. package/dist/logger/file-log.js +28 -19
  103. package/dist/logger/index.js +1 -0
  104. package/dist/main.js +24285 -22447
  105. package/dist/migration/backup.js +14 -3
  106. package/dist/migration/detect.js +12 -3
  107. package/dist/migration/index.js +1 -0
  108. package/dist/modules/artifacts/store.js +9 -8
  109. package/dist/modules/browser/actions.js +6 -0
  110. package/dist/modules/browser/bridge-client.js +14 -12
  111. package/dist/modules/browser/bridge-path.js +4 -4
  112. package/dist/modules/browser/console-buffer.js +37 -0
  113. package/dist/modules/browser/cookie-store.js +5 -0
  114. package/dist/modules/browser/driver-types.js +1 -0
  115. package/dist/modules/browser/driver.js +19 -11
  116. package/dist/modules/browser/index.js +3 -1
  117. package/dist/modules/browser/module.js +2 -1
  118. package/dist/modules/browser/session.js +10 -30
  119. package/dist/modules/browser/snapshot.js +19 -13
  120. package/dist/modules/browser/types.js +1 -0
  121. package/dist/modules/certification/cli.js +28 -20
  122. package/dist/modules/certification/fact-checker.js +9 -1
  123. package/dist/modules/certification/loader.js +9 -2
  124. package/dist/modules/certification/manifest.js +83 -21
  125. package/dist/modules/certification/runner.js +23 -13
  126. package/dist/modules/certification/scenarios.js +20 -20
  127. package/dist/modules/certification/syntax-scenarios.js +1 -2
  128. package/dist/modules/context/chunk-query.js +11 -9
  129. package/dist/modules/context/fact-extractor.js +37 -39
  130. package/dist/modules/context/index.js +1 -0
  131. package/dist/modules/context/manager.js +106 -80
  132. package/dist/modules/critic/client.js +176 -0
  133. package/dist/modules/critic/evidence.js +81 -0
  134. package/dist/modules/critic/provider.js +45 -0
  135. package/dist/modules/critic/tools.js +78 -0
  136. package/dist/modules/critic/types.js +1 -0
  137. package/dist/modules/execution/audit-runners.js +28 -34
  138. package/dist/modules/execution/auditor.js +53 -49
  139. package/dist/modules/execution/execution-plugin.js +142 -102
  140. package/dist/modules/execution/index.js +1 -1
  141. package/dist/modules/execution/input-from.js +37 -13
  142. package/dist/modules/execution/module.js +368 -191
  143. package/dist/modules/execution/moe-executor.js +53 -26
  144. package/dist/modules/execution/plan-actions.js +320 -202
  145. package/dist/modules/execution/plan-checks.js +35 -0
  146. package/dist/modules/execution/plan-coverage.js +36 -33
  147. package/dist/modules/execution/plan-steps.js +11 -3
  148. package/dist/modules/execution/plan-store.js +83 -17
  149. package/dist/modules/execution/plan-tool.js +89 -35
  150. package/dist/modules/execution/plan-validator.js +30 -10
  151. package/dist/modules/execution/planner.js +22 -13
  152. package/dist/modules/execution/stuck-detector.js +196 -93
  153. package/dist/modules/execution/tracker.js +22 -8
  154. package/dist/modules/execution/verifier.js +69 -48
  155. package/dist/modules/execution/windows-commands.js +12 -12
  156. package/dist/modules/hallucination/artifact-claim.js +93 -0
  157. package/dist/modules/hallucination/confidence.js +13 -11
  158. package/dist/modules/hallucination/consistency.js +10 -2
  159. package/dist/modules/hallucination/detector.js +10 -5
  160. package/dist/modules/hallucination/factual.js +43 -26
  161. package/dist/modules/hallucination/index.js +1 -0
  162. package/dist/modules/hallucination/js-identifiers.js +44 -45
  163. package/dist/modules/hallucination/llm-judge.js +17 -12
  164. package/dist/modules/hallucination/success-claim.js +59 -0
  165. package/dist/modules/indexer/cache.js +52 -4
  166. package/dist/modules/indexer/index.js +1 -0
  167. package/dist/modules/indexer/map-command.js +2 -4
  168. package/dist/modules/indexer/map-select.js +16 -16
  169. package/dist/modules/indexer/module.js +47 -12
  170. package/dist/modules/indexer/project-profile.js +8 -7
  171. package/dist/modules/indexer/symbols.js +15 -15
  172. package/dist/modules/indexer/walker.js +159 -29
  173. package/dist/modules/lsp/check-tool.js +7 -5
  174. package/dist/modules/lsp/client.js +39 -39
  175. package/dist/modules/lsp/command.js +17 -24
  176. package/dist/modules/lsp/config.js +12 -11
  177. package/dist/modules/lsp/index.js +1 -0
  178. package/dist/modules/lsp/module.js +48 -46
  179. package/dist/modules/lsp/probe.js +13 -14
  180. package/dist/modules/lsp/project-root.js +14 -17
  181. package/dist/modules/lsp/startup-check.js +18 -16
  182. package/dist/modules/mcp/client.js +59 -62
  183. package/dist/modules/mcp/index.js +1 -0
  184. package/dist/modules/mcp/module.js +66 -37
  185. package/dist/modules/mcp/registry.js +5 -0
  186. package/dist/modules/memory/index.js +1 -0
  187. package/dist/modules/memory/module.js +12 -8
  188. package/dist/modules/memory/search.js +4 -1
  189. package/dist/modules/memory/store.js +40 -2
  190. package/dist/modules/onboarding/init.js +47 -0
  191. package/dist/modules/onboarding/meet.js +30 -0
  192. package/dist/modules/onboarding/mode.js +30 -0
  193. package/dist/modules/onboarding/prompts.js +43 -0
  194. package/dist/modules/pipelines/engine.js +10 -0
  195. package/dist/modules/pipelines/index.js +1 -0
  196. package/dist/modules/pipelines/parser.js +4 -1
  197. package/dist/modules/pipelines/template.js +4 -0
  198. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  199. package/dist/modules/plugins/builtin/notify.js +2 -0
  200. package/dist/modules/plugins/index.js +1 -0
  201. package/dist/modules/plugins/loader.js +18 -3
  202. package/dist/modules/plugins/manager.js +37 -17
  203. package/dist/modules/pricing/index.js +10 -11
  204. package/dist/modules/pricing/prices.js +12 -12
  205. package/dist/modules/processes/index.js +1 -0
  206. package/dist/modules/processes/kill-tree.js +27 -31
  207. package/dist/modules/processes/registry.js +44 -25
  208. package/dist/modules/processes/runner.js +4 -4
  209. package/dist/modules/providers/capabilities.js +28 -0
  210. package/dist/modules/providers/create.js +3 -4
  211. package/dist/modules/providers/factory.js +7 -7
  212. package/dist/modules/providers/fallback.js +12 -12
  213. package/dist/modules/providers/health.js +4 -4
  214. package/dist/modules/providers/manager.js +50 -19
  215. package/dist/modules/providers/naming.js +15 -0
  216. package/dist/modules/providers/preflight.js +71 -0
  217. package/dist/modules/providers/presets.js +39 -8
  218. package/dist/modules/providers/registry.js +7 -2
  219. package/dist/modules/reasoning/policy.js +8 -6
  220. package/dist/modules/reasoning/probe.js +30 -25
  221. package/dist/modules/security/audit-log.js +19 -36
  222. package/dist/modules/security/audit-notifier.js +35 -75
  223. package/dist/modules/security/command-validator.js +99 -51
  224. package/dist/modules/security/content-scanner.js +8 -8
  225. package/dist/modules/security/data-sanitizer.js +9 -16
  226. package/dist/modules/security/encryption.js +32 -52
  227. package/dist/modules/security/index.js +2 -1
  228. package/dist/modules/security/network-validator.js +120 -43
  229. package/dist/modules/security/path-validator.js +61 -46
  230. package/dist/modules/security/protected-paths.js +73 -0
  231. package/dist/modules/security/rate-limiter.js +17 -49
  232. package/dist/modules/security/security-policies.js +25 -57
  233. package/dist/modules/security/session-encryption.js +32 -71
  234. package/dist/modules/security/session-isolation.js +15 -28
  235. package/dist/modules/session/auto-name.js +63 -0
  236. package/dist/modules/session/export.js +98 -0
  237. package/dist/modules/session/index.js +1 -0
  238. package/dist/modules/session/manager.js +79 -28
  239. package/dist/modules/session/module.js +4 -3
  240. package/dist/modules/session/store.js +57 -20
  241. package/dist/modules/setup/answers.js +35 -0
  242. package/dist/modules/setup/apply.js +79 -0
  243. package/dist/modules/setup/discovery.js +72 -0
  244. package/dist/modules/skills/index.js +1 -0
  245. package/dist/modules/skills/loader.js +9 -1
  246. package/dist/modules/skills/module.js +11 -0
  247. package/dist/modules/updater/changelog-reader.js +15 -16
  248. package/dist/modules/updater/checker.js +9 -4
  249. package/dist/modules/updater/dev-detect.js +7 -6
  250. package/dist/modules/updater/index.js +1 -0
  251. package/dist/modules/updater/module.js +17 -15
  252. package/dist/modules/user-profile/compressor.js +2 -0
  253. package/dist/modules/user-profile/index.js +1 -0
  254. package/dist/modules/user-profile/profile.js +10 -1
  255. package/dist/modules/webui/answer-api.js +29 -0
  256. package/dist/modules/webui/assets.js +50 -0
  257. package/dist/modules/webui/body.js +21 -0
  258. package/dist/modules/webui/commands-settings.js +209 -0
  259. package/dist/modules/webui/commands-shared.js +60 -0
  260. package/dist/modules/webui/commands.js +174 -0
  261. package/dist/modules/webui/config-api.js +134 -0
  262. package/dist/modules/webui/file-api.js +202 -0
  263. package/dist/modules/webui/forward.js +159 -0
  264. package/dist/modules/webui/history-api.js +112 -0
  265. package/dist/modules/webui/host-state.js +60 -0
  266. package/dist/modules/webui/i18n-api.js +9 -0
  267. package/dist/modules/webui/limits.js +6 -0
  268. package/dist/modules/webui/log-forward.js +20 -0
  269. package/dist/modules/webui/memory-commands.js +64 -0
  270. package/dist/modules/webui/model-cache.js +18 -0
  271. package/dist/modules/webui/module.js +148 -0
  272. package/dist/modules/webui/persist.js +24 -0
  273. package/dist/modules/webui/prompt-io.js +97 -0
  274. package/dist/modules/webui/providers-api.js +337 -0
  275. package/dist/modules/webui/providers-view.js +56 -0
  276. package/dist/modules/webui/reasoning-view.js +15 -0
  277. package/dist/modules/webui/run-agent.js +81 -0
  278. package/dist/modules/webui/server.js +284 -0
  279. package/dist/modules/webui/sessions-api.js +178 -0
  280. package/dist/modules/webui/settings-api.js +561 -0
  281. package/dist/modules/webui/setup-api.js +201 -0
  282. package/dist/modules/webui/state.js +74 -0
  283. package/dist/modules/webui/util.js +261 -0
  284. package/dist/output/bus.js +7 -2
  285. package/dist/output/channel.js +80 -28
  286. package/dist/output/format.js +2 -1
  287. package/dist/output/index.js +1 -0
  288. package/dist/output/json-sink.js +5 -3
  289. package/dist/output/machine.js +4 -3
  290. package/dist/output/session-sink.js +4 -4
  291. package/dist/tools/approve.js +4 -0
  292. package/dist/tools/attach-image.js +5 -4
  293. package/dist/tools/auto-fixer.js +26 -44
  294. package/dist/tools/auto-tags.js +83 -0
  295. package/dist/tools/bash.js +193 -116
  296. package/dist/tools/browser.js +76 -31
  297. package/dist/tools/chunk-query.js +14 -6
  298. package/dist/tools/create-dir.js +9 -5
  299. package/dist/tools/delete-file.js +9 -5
  300. package/dist/tools/download-file.js +32 -7
  301. package/dist/tools/edit-file.js +52 -14
  302. package/dist/tools/enable-tools.js +6 -6
  303. package/dist/tools/executor.js +132 -27
  304. package/dist/tools/file-info.js +3 -1
  305. package/dist/tools/filter-tools.js +3 -3
  306. package/dist/tools/glob-tool.js +4 -1
  307. package/dist/tools/grep-tool.js +13 -10
  308. package/dist/tools/hidden-tools-block.js +6 -6
  309. package/dist/tools/index.js +16 -8
  310. package/dist/tools/list-dir.js +4 -2
  311. package/dist/tools/load-skill.js +7 -0
  312. package/dist/tools/mcp-call.js +15 -4
  313. package/dist/tools/meet-done.js +14 -0
  314. package/dist/tools/move-file.js +14 -7
  315. package/dist/tools/path-utils.js +29 -29
  316. package/dist/tools/pipeline-run.js +29 -7
  317. package/dist/tools/preview.js +1 -1
  318. package/dist/tools/process-ids.js +31 -0
  319. package/dist/tools/process-kill.js +9 -16
  320. package/dist/tools/process-list.js +1 -0
  321. package/dist/tools/process-log.js +11 -16
  322. package/dist/tools/process-wait.js +63 -0
  323. package/dist/tools/prompt-io.js +4 -3
  324. package/dist/tools/question.js +172 -25
  325. package/dist/tools/read-file.js +55 -34
  326. package/dist/tools/recall.js +14 -8
  327. package/dist/tools/registry.js +6 -5
  328. package/dist/tools/remember.js +8 -4
  329. package/dist/tools/scope-request.js +11 -6
  330. package/dist/tools/search-history.js +5 -2
  331. package/dist/tools/session-info.js +2 -0
  332. package/dist/tools/set-thinking.js +8 -4
  333. package/dist/tools/subagent.js +59 -39
  334. package/dist/tools/syntax-validator.js +292 -90
  335. package/dist/tools/user-input.js +22 -18
  336. package/dist/tools/web-browse.js +9 -3
  337. package/dist/tools/web-fetch.js +12 -23
  338. package/dist/tools/web-search.js +7 -5
  339. package/dist/tools/write-file.js +45 -15
  340. package/dist/ui/action-log.js +13 -0
  341. package/dist/ui/box.js +4 -3
  342. package/dist/ui/colors.js +2 -0
  343. package/dist/ui/diff.js +6 -5
  344. package/dist/ui/index.js +1 -0
  345. package/dist/ui/line-editor.js +230 -122
  346. package/dist/ui/line-math.js +10 -2
  347. package/dist/ui/md-formatter.js +57 -37
  348. package/dist/ui/output.js +8 -3
  349. package/dist/ui/plan-view.js +21 -10
  350. package/dist/ui/renderer.js +393 -67
  351. package/dist/ui/spinner.js +16 -5
  352. package/dist/ui/table.js +15 -17
  353. package/dist/ui/theme.js +143 -0
  354. package/dist/utils/child-run.js +20 -0
  355. package/dist/utils/error.js +1 -1
  356. package/dist/utils/index.js +2 -0
  357. package/dist/utils/retry.js +2 -2
  358. package/dist/utils/truncate.js +1 -1
  359. package/package.json +1 -1
@@ -1,18 +1,29 @@
1
+ // Slash-команды REPL: /config, /model, /provider, сессии, сахар над mutateConfig.
1
2
  import { pc } from "../ui/colors";
3
+ import { resolveVerbosity } from "../ui/theme";
2
4
  import { readMmaVersion } from "../core/version";
3
5
  import { renderTable } from "../ui/table";
4
- import { t } from "../i18n/index";
6
+ import { t, setLocale } from "../i18n/index";
5
7
  import { runSetup } from "./setup";
8
+ import { applyAnswersToConfig } from "./setup-order";
6
9
  import { saveConfig } from "../config/config";
7
10
  import { HOSTED_BASE_URLS } from "../modules/providers/presets";
8
- import { budgetBreakdownLines, setBudgetShare } from "../config/budget";
11
+ import { providerNameTaken } from "../modules/providers/naming";
12
+ import { activeProviderEndpoint } from "../modules/providers/manager";
13
+ import { budgetBreakdownLines } from "../config/budget";
14
+ import { setConfigValue, getConfigValue, resolveConfigKey, canonicalKeys, keysForGroup, configDefs, formatConfigValue, } from "../config/registry";
15
+ import { formatExport, redactExport, sanitizeFileName, extensionFor, } from "../modules/session/export";
9
16
  import { getMessageText } from "../llm/provider";
10
- import { estimateTokens } from "../llm/token-counter";
11
17
  import { join, dirname } from "path";
12
- import { existsSync } from "fs";
18
+ import { existsSync, writeFileSync, mkdirSync } from "fs";
13
19
  import { runMapAction } from "../modules/indexer/map-command";
20
+ import { registerOnboardingCommands } from "./onboarding-commands";
14
21
  import { errMsg } from "../utils";
22
+ import { globalExportsDir } from "../config/paths";
23
+ import { registerLspCommands } from "./repl-commands-lsp";
24
+ import { registerSkillCommands } from "./repl-commands-skills";
15
25
  const version = readMmaVersion();
26
+ /** Группы команд для `/help` (general/agent/session/skill). */
16
27
  export const COMMAND_GROUPS = {
17
28
  help: "general",
18
29
  exit: "general",
@@ -22,25 +33,33 @@ export const COMMAND_GROUPS = {
22
33
  config: "agent",
23
34
  status: "agent",
24
35
  reasoning: "agent",
36
+ verbose: "agent",
25
37
  provider: "agent",
26
38
  model: "agent",
27
39
  context: "agent",
28
40
  reload: "agent",
29
41
  wizard: "agent",
42
+ meet: "agent",
43
+ init: "agent",
44
+ memory: "agent",
30
45
  sysprompt: "agent",
31
46
  map: "agent",
32
47
  lsp: "agent",
48
+ security: "agent",
33
49
  sessions: "session",
34
50
  new: "session",
35
51
  resume: "session",
36
52
  rename: "session",
37
53
  delete: "session",
54
+ export: "session",
38
55
  skill: "skill",
39
56
  plugins: "agent",
40
57
  };
58
+ /** Регистрирует все команды REPL (builtin + mma + онбординг + сессии + скиллы). */
41
59
  export function registerAllCommands(ctx) {
42
60
  registerBuiltinCommands(ctx);
43
61
  registerMmaCommands(ctx);
62
+ registerOnboardingCommands(ctx);
44
63
  registerSessionCommands(ctx);
45
64
  registerSkillCommands(ctx);
46
65
  }
@@ -49,7 +68,8 @@ function registerBuiltinCommands(ctx) {
49
68
  name: "help",
50
69
  description: t("repl.help"),
51
70
  usage: t("repl.help_usage"),
52
- action: () => ctx.showHelp(),
71
+ examples: ["/help", "/help config"],
72
+ action: (args) => ctx.showHelp(args[0]),
53
73
  });
54
74
  ctx.registerCommand({
55
75
  name: "exit",
@@ -64,6 +84,7 @@ function registerBuiltinCommands(ctx) {
64
84
  usage: t("repl.clear_usage"),
65
85
  action: () => {
66
86
  ctx.output.clearScreen();
87
+ ctx.output.writeLine(pc.dim(t("repl.clear_hint")));
67
88
  },
68
89
  });
69
90
  }
@@ -142,40 +163,292 @@ function registerRunCommands(ctx) {
142
163
  },
143
164
  });
144
165
  }
166
+ const SUGAR_HINTS = {
167
+ model: "/model use <name>",
168
+ "provider.active": "/provider use <name>",
169
+ contextWindow: "/context <size>",
170
+ "contextBudget.systemPrompt": "/context system <share>",
171
+ "contextBudget.responseReserve": "/context reserve <share>",
172
+ showReasoning: "/reasoning show | hide",
173
+ "reasoning.mode": "/reasoning level <level>",
174
+ "ui.verbosity": "/verbose <level>",
175
+ };
176
+ function changeHint(def) {
177
+ if (def.readOnly)
178
+ return t("config.read_only_hint");
179
+ return SUGAR_HINTS[def.key] ?? `/config ${def.key} <value>`;
180
+ }
181
+ function configValueHint(def) {
182
+ if (!def)
183
+ return "";
184
+ switch (def.valueType) {
185
+ case "enum":
186
+ return t("config.hint_enum", { values: (def.enumValues ?? []).join(", ") });
187
+ case "bool":
188
+ return t("config.hint_bool");
189
+ case "share":
190
+ return t("config.hint_share");
191
+ case "int":
192
+ return t("config.hint_int", { min: String(def.min ?? 1) });
193
+ default:
194
+ return t("config.hint_string");
195
+ }
196
+ }
197
+ function editDistance(a, b) {
198
+ const dp = Array.from({ length: b.length + 1 }, (_, i) => i);
199
+ for (let i = 1; i <= a.length; i++) {
200
+ let prev = dp[0];
201
+ dp[0] = i;
202
+ for (let j = 1; j <= b.length; j++) {
203
+ const tmp = dp[j];
204
+ dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
205
+ prev = tmp;
206
+ }
207
+ }
208
+ return dp[b.length];
209
+ }
210
+ function nearestConfigKey(input) {
211
+ const names = canonicalKeys();
212
+ let best = null;
213
+ for (const name of names) {
214
+ const d = editDistance(input.toLowerCase(), name.toLowerCase());
215
+ if (best === null || d < best.dist)
216
+ best = { name, dist: d };
217
+ }
218
+ if (!best)
219
+ return null;
220
+ const threshold = Math.max(2, Math.floor(best.name.length / 3));
221
+ return best.dist <= threshold ? best.name : null;
222
+ }
223
+ function renderConfigError(ctx, result) {
224
+ const key = result.key ?? result.input;
225
+ switch (result.reason) {
226
+ case "unknown-key": {
227
+ const suggestion = nearestConfigKey(result.input);
228
+ const suffix = suggestion
229
+ ? t("config.suggest", { name: suggestion })
230
+ : t("config.valid_keys", { keys: canonicalKeys().join(", ") });
231
+ ctx.output.writeLine(pc.yellow(t("config.unknown_key", { key: result.input, suggestion: suffix })), "stderr");
232
+ return;
233
+ }
234
+ case "read-only":
235
+ ctx.output.writeLine(pc.yellow(t("config.read_only", { key })), "stderr");
236
+ return;
237
+ case "range":
238
+ ctx.output.writeLine(pc.yellow(t("config.range", { key })), "stderr");
239
+ return;
240
+ case "combined":
241
+ ctx.output.writeLine(pc.yellow(t("config.combined")), "stderr");
242
+ return;
243
+ default:
244
+ ctx.output.writeLine(pc.yellow(t("config.invalid_value", { key, hint: configValueHint(result.def) })), "stderr");
245
+ }
246
+ }
247
+ function applyConfigSideEffects(ctx, result) {
248
+ if (result.key === "ui.verbosity") {
249
+ // Зеркало стартового правила (`main.ts`): структурные info-логи и диагностика
250
+ // петли видимы в чате только в verbose.
251
+ const verbose = result.value === "verbose";
252
+ ctx.output.setSuppressInfoLogs(!verbose);
253
+ ctx.output.setShowDiagnostics(verbose);
254
+ }
255
+ else if (result.key === "reasoning.mode") {
256
+ ctx.agent.setReasoningLevel(result.value);
257
+ }
258
+ else if (result.key === "locale") {
259
+ setLocale(result.value);
260
+ }
261
+ }
262
+ function persistConfig(ctx) {
263
+ const configPath = join(ctx.configDir, "config.json");
264
+ saveConfig(ctx.config, configPath, dirname(configPath));
265
+ }
266
+ /**
267
+ * Единый путь записи каждой мутации настроек. Сахарные команды тоже идут сюда,
268
+ * чтобы валидация, сайд-эффекты, персист и reconfigure не расходились.
269
+ */
270
+ async function mutateConfig(ctx, key, raw) {
271
+ const result = setConfigValue(ctx.config, key, raw);
272
+ if (!result.ok)
273
+ return result;
274
+ applyConfigSideEffects(ctx, result);
275
+ persistConfig(ctx);
276
+ if (result.reconfigure)
277
+ await ctx.agent.reconfigure(ctx.config);
278
+ if (result.key === "locale")
279
+ ctx.reregisterCommands();
280
+ return result;
281
+ }
282
+ function reportSaved(ctx, result) {
283
+ ctx.output.writeLine(pc.green(t("config.saved", { key: result.key })));
284
+ }
285
+ function rawFromValue(value) {
286
+ if (typeof value === "boolean")
287
+ return value ? "on" : "off";
288
+ return String(value);
289
+ }
290
+ function printConfigTable(ctx, defs) {
291
+ const list = defs ?? configDefs();
292
+ const rows = list.map((def) => [
293
+ def.key,
294
+ formatConfigValue(def.get(ctx.config)),
295
+ def.domain,
296
+ changeHint(def),
297
+ ]);
298
+ ctx.output.writeLine(pc.bold(t("config.header")));
299
+ for (const line of renderTable([
300
+ t("config.col_setting"),
301
+ t("config.col_value"),
302
+ t("config.col_domain"),
303
+ t("config.col_change"),
304
+ ], rows)) {
305
+ ctx.output.writeLine(line);
306
+ }
307
+ const meta = ctx.sessionManager?.getActiveMeta();
308
+ if (meta) {
309
+ ctx.output.writeLine(pc.dim(t("config.session_line", {
310
+ name: meta.name,
311
+ id: meta.id,
312
+ count: String(meta.messageCount),
313
+ })));
314
+ }
315
+ ctx.output.writeLine(pc.dim(t("config.usage_hint")));
316
+ }
317
+ function confirm(ctx, prompt) {
318
+ return new Promise((resolve) => {
319
+ void ctx.withExclusiveInput(async () => {
320
+ await new Promise((done) => {
321
+ ctx.rl.question(prompt, (answer) => {
322
+ resolve(/^y(es)?$/i.test(answer.trim()));
323
+ done();
324
+ });
325
+ });
326
+ });
327
+ });
328
+ }
329
+ async function runConfigReset(ctx, rest) {
330
+ const snapshot = ctx.getConfigSnapshot();
331
+ const yes = rest.includes("--yes");
332
+ const all = rest.includes("--all");
333
+ const target = rest.find((a) => !a.startsWith("--"));
334
+ if (all) {
335
+ const changed = canonicalKeys().filter((k) => {
336
+ const def = resolveConfigKey(k);
337
+ if (!def || !snapshot.has(k))
338
+ return false;
339
+ return rawFromValue(def.get(ctx.config)) !== rawFromValue(snapshot.get(k));
340
+ });
341
+ if (changed.length === 0) {
342
+ ctx.output.writeLine(t("config.reset_none"));
343
+ return;
344
+ }
345
+ if (!yes) {
346
+ if (!process.stdin.isTTY) {
347
+ ctx.output.writeLine(pc.yellow(t("config.reset_all_needs_yes")), "stderr");
348
+ return;
349
+ }
350
+ const ok = await confirm(ctx, t("config.reset_all_confirm", { count: String(changed.length) }));
351
+ if (!ok) {
352
+ ctx.output.writeLine(t("config.reset_cancelled"));
353
+ return;
354
+ }
355
+ }
356
+ let anyReconfig = false;
357
+ let localeChanged = false;
358
+ for (const k of changed) {
359
+ const result = setConfigValue(ctx.config, k, rawFromValue(snapshot.get(k)));
360
+ if (result.ok) {
361
+ applyConfigSideEffects(ctx, result);
362
+ anyReconfig = anyReconfig || result.reconfigure;
363
+ localeChanged = localeChanged || result.key === "locale";
364
+ }
365
+ }
366
+ persistConfig(ctx);
367
+ if (anyReconfig)
368
+ await ctx.agent.reconfigure(ctx.config);
369
+ if (localeChanged)
370
+ ctx.reregisterCommands();
371
+ ctx.output.writeLine(pc.green(t("config.reset_all_done", { count: String(changed.length) })));
372
+ return;
373
+ }
374
+ if (!target) {
375
+ ctx.output.writeLine(t("config.reset_usage"));
376
+ return;
377
+ }
378
+ const def = resolveConfigKey(target);
379
+ if (!def) {
380
+ renderConfigError(ctx, { reason: "unknown-key", input: target });
381
+ return;
382
+ }
383
+ if (def.readOnly || !snapshot.has(def.key)) {
384
+ ctx.output.writeLine(t("config.reset_unchanged", { key: def.key }));
385
+ return;
386
+ }
387
+ if (rawFromValue(def.get(ctx.config)) === rawFromValue(snapshot.get(def.key))) {
388
+ ctx.output.writeLine(t("config.reset_unchanged", { key: def.key }));
389
+ return;
390
+ }
391
+ const result = await mutateConfig(ctx, def.key, rawFromValue(snapshot.get(def.key)));
392
+ if (!result.ok) {
393
+ renderConfigError(ctx, result);
394
+ return;
395
+ }
396
+ ctx.output.writeLine(pc.green(t("config.reset_ok", { key: result.key, value: result.display })));
397
+ }
398
+ function printReasoningStatus(ctx) {
399
+ const display = ctx.config.showReasoning ? t("reasoning.on") : t("reasoning.off");
400
+ const mode = ctx.config.reasoning?.mode ?? "auto";
401
+ const auto = mode === "auto" ? t("reasoning.auto_on") : t("reasoning.auto_off");
402
+ ctx.output.writeLine(t("reasoning.status", { display, level: mode, auto }));
403
+ }
145
404
  function registerConfigCommands(ctx) {
146
405
  ctx.registerCommand({
147
406
  name: "config",
148
407
  description: t("repl.config"),
149
408
  usage: t("repl.config_usage"),
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".
409
+ examples: ["/config", "/config model", "/config reasoning.mode high", "/config reset ui.verbosity"],
410
+ action: async (args) => {
411
+ // Роутинг субкоманды `/config migrate`. Отдельная регистрация с пробелом в
412
+ // имени недостижима: диспетчер режет ввод по пробелам, и "/config migrate"
413
+ // всегда резолвился в plain "config".
154
414
  if ((args[0] ?? "").trim().toLowerCase() === "migrate") {
155
415
  return runConfigMigrate(ctx);
156
416
  }
157
- const cfg = ctx.config;
158
- const ctxW = cfg.contextWindow;
159
- const sys = Math.floor(ctxW * cfg.contextBudget.systemPrompt);
160
- const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
161
- ctx.output.writeLine(`${t("repl.model")} ${cfg.model}`);
162
- const providers = ctx.agent.listProviders();
163
- const active = providers.find((p) => p.active);
164
- if (active) {
165
- ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) → ${pc.dim(active.baseUrl)}`);
417
+ if (args.length === 0) {
418
+ printConfigTable(ctx);
419
+ return;
166
420
  }
167
- else {
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}`);
176
- const meta = ctx.sessionManager?.getActiveMeta();
177
- if (meta) {
178
- ctx.output.writeLine(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
421
+ if (args[0] === "reset") {
422
+ await runConfigReset(ctx, args.slice(1));
423
+ return;
424
+ }
425
+ const name = args[0];
426
+ if (args.length === 1) {
427
+ const group = keysForGroup(name);
428
+ if (group.length > 0) {
429
+ printConfigTable(ctx, group);
430
+ return;
431
+ }
432
+ const got = getConfigValue(ctx.config, name);
433
+ if (!got.ok) {
434
+ renderConfigError(ctx, got);
435
+ return;
436
+ }
437
+ ctx.output.writeLine(`${got.key} = ${got.display}`);
438
+ return;
439
+ }
440
+ const raw = args.slice(1).join(" ");
441
+ const result = await mutateConfig(ctx, name, raw);
442
+ if (!result.ok) {
443
+ renderConfigError(ctx, result);
444
+ return;
445
+ }
446
+ reportSaved(ctx, result);
447
+ if (result.key === "model") {
448
+ const known = ctx.knownModels();
449
+ if (known.length > 0 && !known.includes(String(result.value))) {
450
+ ctx.output.writeLine(pc.yellow(t("model.not_in_list", { name: String(result.value) })));
451
+ }
179
452
  }
180
453
  },
181
454
  });
@@ -183,23 +456,60 @@ function registerConfigCommands(ctx) {
183
456
  name: "reasoning",
184
457
  description: t("repl.reasoning"),
185
458
  usage: t("repl.reasoning_usage"),
186
- action: (args) => {
459
+ examples: ["/reasoning", "/reasoning show", "/reasoning level high"],
460
+ action: async (args) => {
461
+ const sub = args[0]?.trim().toLowerCase();
462
+ if (!sub) {
463
+ printReasoningStatus(ctx);
464
+ return;
465
+ }
466
+ if (sub === "show" || sub === "on" || sub === "hide" || sub === "off") {
467
+ const on = sub === "show" || sub === "on";
468
+ const result = await mutateConfig(ctx, "showReasoning", on ? "on" : "off");
469
+ if (!result.ok) {
470
+ renderConfigError(ctx, result);
471
+ return;
472
+ }
473
+ ctx.output.writeLine(pc.green(t("reasoning.display_set", { state: on ? t("reasoning.on") : t("reasoning.off") })));
474
+ return;
475
+ }
476
+ if (sub === "level") {
477
+ const lvl = args[1]?.trim().toLowerCase();
478
+ if (!lvl) {
479
+ ctx.output.writeLine(t("repl.reasoning_usage"));
480
+ return;
481
+ }
482
+ const result = await mutateConfig(ctx, "reasoning.mode", lvl);
483
+ if (!result.ok) {
484
+ renderConfigError(ctx, result);
485
+ return;
486
+ }
487
+ ctx.output.writeLine(pc.green(t("repl.reasoning_level_set", { level: lvl })));
488
+ if (lvl !== "auto")
489
+ ctx.output.writeLine(pc.dim(t("reasoning.auto_off_hint")));
490
+ return;
491
+ }
492
+ ctx.output.writeLine(pc.yellow(t("reasoning.unknown_sub", { sub })));
493
+ ctx.output.writeLine(t("repl.reasoning_usage"));
494
+ },
495
+ });
496
+ ctx.registerCommand({
497
+ name: "verbose",
498
+ description: t("repl.verbose"),
499
+ usage: t("repl.verbose_usage"),
500
+ examples: ["/verbose", "/verbose normal"],
501
+ action: async (args) => {
187
502
  const level = args[0]?.trim().toLowerCase();
188
- const VALID_LEVELS = ["auto", "none", "low", "medium", "high", "max"];
189
503
  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 })));
504
+ ctx.output.writeLine(t("verbose.status", { level: resolveVerbosity(ctx.config.ui) }));
505
+ return;
199
506
  }
200
- else {
201
- ctx.output.writeLine(pc.yellow(t("repl.reasoning_invalid_level", { level, valid: VALID_LEVELS.join(", ") })));
507
+ const result = await mutateConfig(ctx, "ui.verbosity", level);
508
+ if (!result.ok) {
509
+ renderConfigError(ctx, result);
510
+ return;
202
511
  }
512
+ ctx.output.writeLine(pc.green(t("repl.verbose_set", { level: result.display })));
203
513
  },
204
514
  });
205
515
  ctx.registerCommand({
@@ -209,17 +519,13 @@ function registerConfigCommands(ctx) {
209
519
  action: () => {
210
520
  const providers = ctx.agent.listProviders();
211
521
  const active = providers.find((p) => p.active);
212
- ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
213
- if (active) {
214
- ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) @ ${pc.dim(active.baseUrl)}`);
215
- }
216
- else {
217
- ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
218
- }
522
+ const providerLabel = active ? active.label : ctx.config.provider.type;
523
+ const parts = [ctx.config.model, providerLabel];
219
524
  const meta = ctx.sessionManager?.getActiveMeta();
220
- if (meta) {
221
- ctx.output.writeLine(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
222
- }
525
+ if (meta)
526
+ parts.push(`${meta.name} (${meta.messageCount} ${t("repl.msgs")})`);
527
+ parts.push(`${ctx.config.contextWindow} tok`);
528
+ ctx.output.writeLine(parts.join(pc.dim(" · ")));
223
529
  },
224
530
  });
225
531
  ctx.registerCommand({
@@ -232,13 +538,7 @@ function registerConfigCommands(ctx) {
232
538
  await ctx.withExclusiveInput(async () => {
233
539
  const answers = await runSetup(ctx.rl);
234
540
  const configPath = join(ctx.configDir, "config.json");
235
- ctx.config.provider.type = answers.provider;
236
- ctx.config.provider.baseUrl = answers.apiBase;
237
- ctx.config.provider.apiKey = answers.apiKey;
238
- ctx.config.model = answers.model;
239
- ctx.config.contextWindow = answers.contextWindow;
240
- ctx.config.maxToolIterations = answers.maxToolIterations;
241
- ctx.config.locale = answers.locale;
541
+ applyAnswersToConfig(ctx.config, answers);
242
542
  saveConfig(ctx.config, configPath, dirname(configPath));
243
543
  await ctx.agent.reconfigure(ctx.config);
244
544
  ctx.output.writeLine(pc.green(t("cli.config_saved")));
@@ -248,21 +548,28 @@ function registerConfigCommands(ctx) {
248
548
  ctx.registerCommand({
249
549
  name: "sysprompt",
250
550
  description: t("repl.sysprompt_desc"),
251
- usage: t("repl.sysprompt_desc"),
252
- action: () => {
551
+ usage: t("repl.sysprompt_usage"),
552
+ examples: ["/sysprompt", "/sysprompt --full"],
553
+ action: (args) => {
253
554
  const info = ctx.agent.getSystemPromptInfo();
555
+ const full = args.includes("--full");
254
556
  ctx.output.writeLine(pc.bold(t("repl.sysprompt_tokens", { count: String(info.tokenCount) })));
255
- ctx.output.writeLine(pc.dim("─".repeat(60)));
256
- for (const line of info.text.split("\n")) {
257
- ctx.output.writeLine(line);
258
- }
259
- ctx.output.writeLine(pc.dim("─".repeat(60)));
260
557
  if (info.excluded.length > 0) {
261
558
  ctx.output.writeLine(pc.yellow(t("repl.excluded_blocks", { count: String(info.excluded.length) })));
262
559
  for (const e of info.excluded) {
263
560
  ctx.output.writeLine(pc.dim(` - ${e.slice(0, 80)}${e.length > 80 ? "..." : ""}`));
264
561
  }
265
562
  }
563
+ if (full) {
564
+ ctx.output.writeLine(pc.dim("─".repeat(60)));
565
+ for (const line of info.text.split("\n")) {
566
+ ctx.output.writeLine(line);
567
+ }
568
+ ctx.output.writeLine(pc.dim("─".repeat(60)));
569
+ }
570
+ else {
571
+ ctx.output.writeLine(pc.dim(t("repl.sysprompt_full_hint")));
572
+ }
266
573
  },
267
574
  });
268
575
  ctx.registerCommand({
@@ -286,8 +593,9 @@ function registerProviderCommands(ctx) {
286
593
  const list = () => {
287
594
  const providers = ctx.agent.listProviders();
288
595
  if (providers.length === 0) {
289
- ctx.output.writeLine(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
290
- ctx.output.writeLine(` ${ctx.config.provider.baseUrl}`);
596
+ const endpoint = activeProviderEndpoint(ctx.config.provider);
597
+ ctx.output.writeLine(`${t("repl.provider_current")} ${endpoint.type}`);
598
+ ctx.output.writeLine(` ${endpoint.baseUrl}`);
291
599
  return;
292
600
  }
293
601
  ctx.output.writeLine(t("repl.provider_current"));
@@ -302,14 +610,14 @@ function registerProviderCommands(ctx) {
302
610
  ctx.output.writeLine(t("repl.provider_usage"));
303
611
  return;
304
612
  }
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");
613
+ const result = await mutateConfig(ctx, "provider.active", name);
614
+ if (!result.ok) {
615
+ renderConfigError(ctx, result);
616
+ return;
312
617
  }
618
+ ctx.refreshModelCache();
619
+ ctx.output.writeLine(pc.green(t("repl.provider_set", { name })));
620
+ reportSaved(ctx, result);
313
621
  };
314
622
  const add = async (args) => {
315
623
  const flags = {};
@@ -333,6 +641,13 @@ function registerProviderCommands(ctx) {
333
641
  ctx.output.writeLine(t("repl.provider_usage"));
334
642
  return;
335
643
  }
644
+ for (const flag of ["priority", "context-window", "rpm", "parallel"]) {
645
+ const v = flags[flag];
646
+ if (v !== undefined && !Number.isFinite(Number(v))) {
647
+ ctx.output.writeLine(pc.red(t("repl.provider_add_invalid_number", { flag: `--${flag}`, value: v })), "stderr");
648
+ return;
649
+ }
650
+ }
336
651
  const entries = Array.isArray(ctx.config.provider.entries) ? ctx.config.provider.entries : [];
337
652
  if (entries.length === 0) {
338
653
  entries.push({
@@ -347,6 +662,10 @@ function registerProviderCommands(ctx) {
347
662
  ctx.output.writeLine(pc.red(t("cli.provider_no_url", { name })), "stderr");
348
663
  return;
349
664
  }
665
+ if (providerNameTaken(entries, name)) {
666
+ ctx.output.writeLine(pc.red(t("cli.provider_name_conflict", { name })), "stderr");
667
+ return;
668
+ }
350
669
  entries.push({
351
670
  type: name,
352
671
  label: name,
@@ -367,8 +686,7 @@ function registerProviderCommands(ctx) {
367
686
  });
368
687
  ctx.config.provider.entries = entries;
369
688
  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));
689
+ persistConfig(ctx);
372
690
  await ctx.agent.reconfigure(ctx.config);
373
691
  ctx.output.writeLine(pc.green(t("cli.provider_added", { name })));
374
692
  ctx.output.writeLine(t("repl.provider_switch_hint", { name }));
@@ -382,10 +700,12 @@ function registerProviderCommands(ctx) {
382
700
  name: "provider",
383
701
  description: t("repl.provider_list"),
384
702
  usage: t("repl.provider_usage"),
703
+ examples: ["/provider list", "/provider use openrouter", "/provider add openai --key sk-..."],
385
704
  action: async (args) => {
386
705
  const subcmd = args[0];
387
706
  const handler = subcmd ? handlers[subcmd] : handlers.list;
388
707
  if (!handler) {
708
+ ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "provider", sub: subcmd })));
389
709
  ctx.output.writeLine(t("repl.provider_usage"));
390
710
  return;
391
711
  }
@@ -439,11 +759,17 @@ function registerProviderCommands(ctx) {
439
759
  ctx.output.writeLine(t("repl.model_usage"));
440
760
  return;
441
761
  }
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);
762
+ const result = await mutateConfig(ctx, "model", name);
763
+ if (!result.ok) {
764
+ renderConfigError(ctx, result);
765
+ return;
766
+ }
446
767
  ctx.output.writeLine(pc.green(t("repl.model_set", { name })));
768
+ const known = ctx.knownModels();
769
+ if (known.length > 0 && !known.includes(name)) {
770
+ ctx.output.writeLine(pc.yellow(t("model.not_in_list", { name })));
771
+ }
772
+ reportSaved(ctx, result);
447
773
  };
448
774
  const modelHandlers = {
449
775
  list: listModels,
@@ -453,10 +779,12 @@ function registerProviderCommands(ctx) {
453
779
  name: "model",
454
780
  description: t("repl.model_list"),
455
781
  usage: t("repl.model_usage"),
782
+ examples: ["/model list", "/model use qwen/qwen3.5-9b"],
456
783
  action: async (args) => {
457
784
  const subcmd = args[0];
458
785
  const handler = subcmd ? modelHandlers[subcmd] : modelHandlers.list;
459
786
  if (!handler) {
787
+ ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "model", sub: subcmd })));
460
788
  ctx.output.writeLine(t("repl.model_usage"));
461
789
  return;
462
790
  }
@@ -467,12 +795,8 @@ function registerProviderCommands(ctx) {
467
795
  name: "context",
468
796
  description: t("cli.manage_context"),
469
797
  usage: t("repl.context_usage"),
798
+ examples: ["/context", "/context 64000", "/context system 10%", "/context reserve 0.2"],
470
799
  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
- };
476
800
  if (args.length === 0) {
477
801
  for (const line of budgetBreakdownLines(ctx.config))
478
802
  ctx.output.writeLine(line);
@@ -480,28 +804,62 @@ function registerProviderCommands(ctx) {
480
804
  return;
481
805
  }
482
806
  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")));
807
+ const raw = args[1];
808
+ if (!raw) {
809
+ ctx.output.writeLine(t("repl.context_usage"));
487
810
  return;
488
811
  }
489
- await save();
490
- ctx.output.writeLine(pc.green(t("cli.context_fraction_set", { key, value })));
812
+ const key = args[0] === "system"
813
+ ? "contextBudget.systemPrompt"
814
+ : "contextBudget.responseReserve";
815
+ const result = await mutateConfig(ctx, key, raw);
816
+ if (!result.ok) {
817
+ renderConfigError(ctx, result);
818
+ return;
819
+ }
820
+ ctx.output.writeLine(pc.green(t("cli.context_fraction_set", { key: args[0], value: result.display })));
491
821
  return;
492
822
  }
493
- const size = parseInt(args[0], 10);
494
- if (isNaN(size) || size < 1024) {
495
- ctx.output.writeLine(t("cli.invalid_context_size"));
823
+ if (!/^\d+$/.test(args[0])) {
824
+ ctx.output.writeLine(pc.yellow(t("context.unknown_arg", { arg: args[0] })));
825
+ ctx.output.writeLine(t("repl.context_usage"));
496
826
  return;
497
827
  }
498
- ctx.config.contextWindow = size;
499
- await save();
500
- ctx.output.writeLine(pc.green(t("cli.context_set", { size })));
828
+ const result = await mutateConfig(ctx, "contextWindow", args[0]);
829
+ if (!result.ok) {
830
+ renderConfigError(ctx, result);
831
+ return;
832
+ }
833
+ ctx.output.writeLine(pc.green(t("cli.context_set", { size: String(result.value) })));
834
+ reportSaved(ctx, result);
501
835
  },
502
836
  });
503
837
  }
504
838
  function registerAgentCommands(ctx) {
839
+ const securityStatus = () => {
840
+ const s = ctx.config.security;
841
+ const flag = (v) => (v ? t("reasoning.on") : t("reasoning.off"));
842
+ const sep = pc.dim(" · ");
843
+ ctx.output.writeLine(`security${sep}${flag(s?.enabled)}`);
844
+ ctx.output.writeLine(`↳ bash ${flag(s?.bash.enabled)}${sep}paths ${flag(s?.paths.enabled)}${sep}network ${flag(s?.network.enabled)}`);
845
+ ctx.output.writeLine(`↳ contentScan ${flag(s?.contentScan.enabled)}${sep}encryption ${flag(s?.sessionEncryption.enabled)}`);
846
+ };
847
+ const securityHandlers = {
848
+ status: securityStatus,
849
+ };
850
+ ctx.registerCommand({
851
+ name: "security",
852
+ description: t("repl.security"),
853
+ action: (args) => {
854
+ const sub = args[0]?.trim().toLowerCase();
855
+ const handler = sub ? securityHandlers[sub] : securityHandlers.status;
856
+ if (!handler) {
857
+ ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "security", sub })));
858
+ return;
859
+ }
860
+ handler();
861
+ },
862
+ });
505
863
  ctx.registerCommand({
506
864
  name: "reload",
507
865
  description: t("repl.reload"),
@@ -512,7 +870,8 @@ function registerAgentCommands(ctx) {
512
870
  ctx.output.writeLine(pc.green(t("repl.reloaded")));
513
871
  ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
514
872
  ctx.output.writeLine(`${t("repl.context")} ${ctx.config.contextWindow}`);
515
- ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
873
+ const endpoint = activeProviderEndpoint(ctx.config.provider);
874
+ ctx.output.writeLine(`${t("repl.provider")} ${endpoint.type} @ ${endpoint.baseUrl}`);
516
875
  },
517
876
  });
518
877
  ctx.registerCommand({
@@ -545,74 +904,7 @@ function registerAgentCommands(ctx) {
545
904
  if (!showAll) {
546
905
  const builtinCount = infos.filter(({ plugin }) => plugin.isBuiltin).length;
547
906
  if (builtinCount > 0) {
548
- ctx.output.writeLine(pc.dim(` (${builtinCount} builtin hidden — /plugins --all to show)`));
549
- }
550
- }
551
- },
552
- });
553
- }
554
- function registerLspCommands(ctx) {
555
- ctx.registerCommand({
556
- name: "lsp",
557
- description: t("repl.lsp"),
558
- usage: t("repl.lsp_usage"),
559
- action: async (args) => {
560
- const subcommand = args[0] || "status";
561
- // Access LSP module via the agent's module registry
562
- const lspModule = ctx.agent.getModule("lsp");
563
- if (!lspModule) {
564
- ctx.output.writeLine(pc.yellow(t("repl.lsp_not_available")));
565
- return;
566
- }
567
- switch (subcommand) {
568
- case "status": {
569
- const disabled = lspModule.getDisabledServers();
570
- const failures = lspModule.getFailureCounts();
571
- const config = ctx.config.lsp;
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")}`);
574
- if (disabled.length > 0) {
575
- ctx.output.writeLine(pc.yellow(`${t("repl.lsp_disabled_servers")} ${disabled.join(", ")}`));
576
- }
577
- if (failures.size > 0) {
578
- ctx.output.writeLine(pc.dim(t("repl.lsp_failure_counts")));
579
- for (const [server, count] of failures) {
580
- ctx.output.writeLine(pc.dim(` ${server}: ${count}`));
581
- }
582
- }
583
- if (disabled.length === 0 && failures.size === 0) {
584
- ctx.output.writeLine(pc.green(t("repl.lsp_all_ok")));
585
- }
586
- break;
587
- }
588
- case "restart": {
589
- ctx.output.writeLine(pc.yellow(t("repl.lsp_restarting")));
590
- lspModule.resetDisabledServers();
591
- ctx.output.writeLine(pc.green(t("repl.lsp_restarted")));
592
- break;
593
- }
594
- case "check": {
595
- const path = args[1];
596
- if (!path) {
597
- ctx.output.writeLine(pc.yellow(t("repl.lsp_check_usage")));
598
- return;
599
- }
600
- ctx.output.writeLine(pc.dim(t("repl.lsp_checking", { path })));
601
- // Запуск тула lsp_check через публичный API агента (не через приватные deps)
602
- try {
603
- const result = await ctx.agent.runTool("lsp_check", { path });
604
- if (result?.output) {
605
- ctx.output.writeLine(result.output);
606
- }
607
- }
608
- catch (e) {
609
- ctx.output.writeLine(pc.red(t("repl.lsp_check_error", { error: errMsg(e) })), "stderr");
610
- }
611
- break;
612
- }
613
- default: {
614
- ctx.output.writeLine(pc.dim(t("repl.lsp_usage")));
615
- break;
907
+ ctx.output.writeLine(pc.dim(` ${t("repl.plugins_hidden", { count: String(builtinCount) })}`));
616
908
  }
617
909
  }
618
910
  },
@@ -633,7 +925,23 @@ function registerSessionCommands(ctx) {
633
925
  description: t("repl.sessions"),
634
926
  aliases: ["ls"],
635
927
  usage: t("repl.sessions_usage"),
636
- action: () => {
928
+ action: (args) => {
929
+ if (args[0] === "prune") {
930
+ const dry = args.includes("--dry-run");
931
+ const active = ctx.sessionManager.getActive();
932
+ const candidates = ctx.sessionManager.list().filter((s) => s.id !== active && ctx.sessionManager.isEmptySession(s.id));
933
+ if (candidates.length === 0) {
934
+ ctx.output.writeLine(t("session.prune_empty"));
935
+ return;
936
+ }
937
+ if (!dry)
938
+ ctx.sessionManager.pruneEmpty(active ?? undefined);
939
+ ctx.output.writeLine(t(dry ? "session.prune_dry" : "session.pruned", { n: candidates.length }));
940
+ for (const s of candidates) {
941
+ ctx.output.writeLine(pc.dim(` ${s.id.slice(0, 12)} ${s.name}`));
942
+ }
943
+ return;
944
+ }
637
945
  const sessions = ctx.sessionManager.list();
638
946
  const active = ctx.sessionManager.getActive();
639
947
  if (sessions.length === 0) {
@@ -668,6 +976,14 @@ function registerSessionCommands(ctx) {
668
976
  const name = args.join(" ") || undefined;
669
977
  const meta = ctx.sessionManager.create(name);
670
978
  ctx.agent.clearContext();
979
+ // План следует за сессией: новая — без плана, трекер сбрасывается.
980
+ const exec = ctx.agent.getModule("execution");
981
+ try {
982
+ exec?.switchSession(meta.id);
983
+ }
984
+ catch {
985
+ // fail-open: сессия создана, план остался как был
986
+ }
671
987
  ctx.output.clearScreen();
672
988
  ctx.output.writeLine(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
673
989
  ctx.output.writeLine(pc.dim(` ${t("session.chat_cleared")}\n`));
@@ -676,8 +992,9 @@ function registerSessionCommands(ctx) {
676
992
  ctx.registerCommand({
677
993
  name: "resume",
678
994
  description: t("repl.resume"),
679
- aliases: ["switch", "use"],
995
+ aliases: ["switch"],
680
996
  usage: t("repl.resume_usage"),
997
+ examples: ["/resume", "/resume my-session"],
681
998
  action: (args) => {
682
999
  const query = args.join(" ");
683
1000
  const sessions = ctx.sessionManager.list();
@@ -701,6 +1018,17 @@ function registerSessionCommands(ctx) {
701
1018
  ctx.sessionManager.setActive(match.id);
702
1019
  const history = ctx.sessionManager.loadHistory();
703
1020
  ctx.agent.setContext(history);
1021
+ // План следует за сессией: трекер переключается на план этой сессии
1022
+ // (или пустеет, если у неё плана нет).
1023
+ const exec = ctx.agent.getModule("execution");
1024
+ let planLine = null;
1025
+ try {
1026
+ exec?.switchSession(match.id);
1027
+ planLine = exec?.getPlanSummary() ?? null;
1028
+ }
1029
+ catch {
1030
+ // fail-open: история загружена, план остался как был
1031
+ }
704
1032
  ctx.output.clearScreen();
705
1033
  ctx.output.writeLine(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
706
1034
  " " +
@@ -718,12 +1046,12 @@ function registerSessionCommands(ctx) {
718
1046
  ctx.output.writeLine("");
719
1047
  for (const msg of history) {
720
1048
  if (msg.role === "user") {
721
- ctx.output.writeLine(pc.cyan(t("session.user_label") + ":"));
1049
+ ctx.output.writeLine(pc.cyan(`${t("session.user_label")}:`));
722
1050
  ctx.output.writeLine(getMessageText(msg.content));
723
1051
  ctx.output.writeLine("");
724
1052
  }
725
1053
  else if (msg.role === "assistant") {
726
- ctx.output.writeLine(pc.green(t("session.assistant_label") + ":"));
1054
+ ctx.output.writeLine(pc.green(`${t("session.assistant_label")}:`));
727
1055
  ctx.output.writeLine(getMessageText(msg.content));
728
1056
  ctx.output.writeLine("");
729
1057
  }
@@ -731,6 +1059,7 @@ function registerSessionCommands(ctx) {
731
1059
  }
732
1060
  ctx.output.writeLine(pc.dim("─".repeat(50)));
733
1061
  ctx.output.writeLine(pc.dim(` ${t("session.chat_loaded")}`));
1062
+ ctx.output.writeLine(pc.dim(` ◇ ${planLine ?? t("plan.no_active")}`));
734
1063
  },
735
1064
  });
736
1065
  ctx.registerCommand({
@@ -770,105 +1099,99 @@ function registerSessionCommands(ctx) {
770
1099
  return;
771
1100
  }
772
1101
  ctx.sessionManager.delete(match.id);
1102
+ // План следует за сессией: слот удалённой сессии не оставляем мусором.
1103
+ try {
1104
+ ctx.agent
1105
+ .getModule("execution")
1106
+ ?.forgetSession(match.id);
1107
+ }
1108
+ catch {
1109
+ /* best-effort */
1110
+ }
773
1111
  ctx.output.writeLine(`${t("session.deleted", { id: match.id })}: ${match.name}`);
774
1112
  },
775
1113
  });
776
- }
777
- function registerSkillCommands(ctx) {
778
- if (!ctx.skillsModule)
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
- };
859
1114
  ctx.registerCommand({
860
- name: "skill",
861
- description: t("repl.skill"),
862
- usage: t("repl.skill_usage"),
863
- action: (args) => {
864
- const subcmd = args[0];
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"));
1115
+ name: "export",
1116
+ description: t("repl.export"),
1117
+ usage: t("repl.export_usage"),
1118
+ examples: [
1119
+ "/export",
1120
+ "/export my-session --format md",
1121
+ "/export --format json --out -",
1122
+ ],
1123
+ action: async (args) => {
1124
+ let format = "md";
1125
+ let out;
1126
+ let redact = true;
1127
+ let compact = false;
1128
+ const positional = [];
1129
+ for (let i = 0; i < args.length; i++) {
1130
+ const a = args[i];
1131
+ if (a === "--format") {
1132
+ const v = args[++i];
1133
+ if (!v) {
1134
+ ctx.output.writeLine(t("export.missing_value", { flag: a }));
1135
+ return;
1136
+ }
1137
+ format = v;
1138
+ }
1139
+ else if (a === "--out") {
1140
+ const v = args[++i];
1141
+ if (v === undefined) {
1142
+ ctx.output.writeLine(t("export.missing_value", { flag: a }));
1143
+ return;
1144
+ }
1145
+ out = v;
1146
+ }
1147
+ else if (a === "--no-redact") {
1148
+ redact = false;
1149
+ }
1150
+ else if (a === "--compact") {
1151
+ compact = true;
1152
+ }
1153
+ else {
1154
+ positional.push(a);
1155
+ }
1156
+ }
1157
+ if (format !== "md" && format !== "json" && format !== "jsonl") {
1158
+ ctx.output.writeLine(pc.yellow(t("export.invalid_format", { format })), "stderr");
1159
+ return;
1160
+ }
1161
+ const query = positional.join(" ");
1162
+ const sessions = ctx.sessionManager.list();
1163
+ const match = query
1164
+ ? sessions.find((s) => s.id === query ||
1165
+ s.id.startsWith(query) ||
1166
+ s.name.toLowerCase().includes(query.toLowerCase()))
1167
+ : sessions.find((s) => s.id === ctx.sessionManager.getActive());
1168
+ if (!match) {
1169
+ ctx.output.writeLine(query ? t("session.no_match", { query }) : t("session.no_active"));
1170
+ return;
1171
+ }
1172
+ const messages = ctx.sessionManager.getHistory(match.id);
1173
+ const log = ctx.sessionManager.loadSessionLog(match.id);
1174
+ if (format === "jsonl" ? log.length === 0 : messages.length === 0) {
1175
+ ctx.output.writeLine(pc.yellow(t("export.empty")));
1176
+ return;
1177
+ }
1178
+ const text = redactExport(formatExport({ meta: match, messages, log }, format, { compact }), redact);
1179
+ if (out === "-") {
1180
+ ctx.output.writeLine(text);
1181
+ return;
1182
+ }
1183
+ const baseDir = out ? dirname(out) : globalExportsDir();
1184
+ const path = out ?? join(baseDir, `${sanitizeFileName(match.name)}-${match.id.slice(0, 8)}.${extensionFor(format)}`);
1185
+ try {
1186
+ mkdirSync(baseDir, { recursive: true });
1187
+ writeFileSync(path, text, "utf-8");
1188
+ }
1189
+ catch (e) {
1190
+ ctx.output.writeLine(pc.red(t("export.write_failed", { error: errMsg(e) })), "stderr");
869
1191
  return;
870
1192
  }
871
- handler(args.slice(1));
1193
+ ctx.output.writeLine(pc.green(t("export.written", { path })));
1194
+ ctx.output.writeLine(pc.dim(redact ? t("export.redacted") : t("export.raw")));
872
1195
  },
873
1196
  });
874
1197
  }
@@ -889,7 +1212,7 @@ async function runConfigMigrate(ctx) {
889
1212
  const { config } = loadCfg({ configDir, projectConfigPath: join(configDir, ".mmrc") });
890
1213
  saveConfig(config, configPath, configDir);
891
1214
  const { renameSync, readdirSync } = await import("fs");
892
- renameSync(configPath, configPath + ".bak");
1215
+ renameSync(configPath, `${configPath}.bak`);
893
1216
  const domainFiles = readdirSync(join(configDir, "config")).filter((f) => f.endsWith(".json"));
894
1217
  ctx.output.writeLine(pc.green(t("config.migrate_done", { count: String(domainFiles.length) })));
895
1218
  }