micro-models-agent 0.41.0 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +9 -19
  3. package/dist/cli/completer.js +36 -37
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +48 -23
  6. package/dist/cli/plugin-commands.js +36 -0
  7. package/dist/cli/repl-commands.js +40 -12
  8. package/dist/cli/repl.js +217 -87
  9. package/dist/cli/run-result.js +22 -0
  10. package/dist/cli/security-commands.js +5 -7
  11. package/dist/cli/setup.js +8 -26
  12. package/dist/config/config.js +52 -5
  13. package/dist/config/defaults.js +29 -5
  14. package/dist/config/experts.js +1 -1
  15. package/dist/config/index.js +3 -3
  16. package/dist/config/security.js +3 -10
  17. package/dist/core/agent-moe.js +2 -10
  18. package/dist/core/agent.js +273 -82
  19. package/dist/core/bootstrap.js +80 -13
  20. package/dist/core/index.js +2 -2
  21. package/dist/core/prompt-builder.js +23 -2
  22. package/dist/core/session-logger.js +46 -4
  23. package/dist/core/version.js +24 -0
  24. package/dist/i18n/en.json +75 -2
  25. package/dist/i18n/ru.json +74 -1
  26. package/dist/index.js +1 -1
  27. package/dist/llm/image-utils.js +4 -5
  28. package/dist/llm/index.js +4 -4
  29. package/dist/llm/model-loader.js +6 -6
  30. package/dist/llm/openai-compat.js +40 -34
  31. package/dist/llm/orchestrator.js +33 -29
  32. package/dist/llm/response.js +9 -9
  33. package/dist/logger/app-logger.js +1 -1
  34. package/dist/logger/index.js +1 -1
  35. package/dist/main.js +1111 -954
  36. package/dist/migration/backup.js +13 -13
  37. package/dist/migration/detect.js +11 -11
  38. package/dist/migration/index.js +2 -2
  39. package/dist/modules/artifacts/store.js +61 -0
  40. package/dist/modules/browser/actions.js +34 -4
  41. package/dist/modules/browser/bridge-client.js +199 -0
  42. package/dist/modules/browser/bridge-path.js +10 -0
  43. package/dist/modules/browser/bridge-server.mjs +202 -202
  44. package/dist/modules/browser/cookie-store.js +6 -6
  45. package/dist/modules/browser/driver.js +136 -0
  46. package/dist/modules/browser/index.js +7 -5
  47. package/dist/modules/browser/module.js +8 -7
  48. package/dist/modules/browser/session.js +87 -84
  49. package/dist/modules/browser/snapshot.js +92 -58
  50. package/dist/modules/browser/types.js +4 -1
  51. package/dist/modules/certification/cli.js +2 -4
  52. package/dist/modules/certification/fact-checker.js +1 -3
  53. package/dist/modules/certification/loader.js +3 -9
  54. package/dist/modules/certification/runner.js +1 -4
  55. package/dist/modules/context/chunk-query.js +100 -0
  56. package/dist/modules/context/fact-extractor.js +162 -0
  57. package/dist/modules/context/history.js +15 -0
  58. package/dist/modules/context/index.js +1 -1
  59. package/dist/modules/context/manager.js +160 -86
  60. package/dist/modules/execution/audit-runners.js +152 -0
  61. package/dist/modules/execution/auditor.js +177 -25
  62. package/dist/modules/execution/execution-plugin.js +272 -0
  63. package/dist/modules/execution/module.js +201 -544
  64. package/dist/modules/execution/moe-executor.js +25 -0
  65. package/dist/modules/execution/plan-store.js +1 -3
  66. package/dist/modules/execution/plan-tool.js +508 -0
  67. package/dist/modules/execution/plan-validator.js +10 -10
  68. package/dist/modules/execution/planner.js +6 -1
  69. package/dist/modules/execution/stuck-detector.js +173 -10
  70. package/dist/modules/execution/verifier.js +86 -42
  71. package/dist/modules/execution/windows-commands.js +41 -0
  72. package/dist/modules/hallucination/confidence.js +8 -1
  73. package/dist/modules/hallucination/detector.js +2 -5
  74. package/dist/modules/hallucination/factual.js +3 -64
  75. package/dist/modules/hallucination/index.js +1 -1
  76. package/dist/modules/hallucination/js-identifiers.js +190 -0
  77. package/dist/modules/hallucination/llm-judge.js +1 -3
  78. package/dist/modules/indexer/cache.js +9 -7
  79. package/dist/modules/indexer/index.js +3 -3
  80. package/dist/modules/indexer/module.js +95 -42
  81. package/dist/modules/indexer/project-profile.js +183 -0
  82. package/dist/modules/indexer/walker.js +17 -17
  83. package/dist/modules/lsp/check-tool.js +58 -0
  84. package/dist/modules/lsp/client.js +74 -31
  85. package/dist/modules/lsp/command.js +60 -0
  86. package/dist/modules/lsp/config.js +87 -33
  87. package/dist/modules/lsp/index.js +3 -3
  88. package/dist/modules/lsp/module.js +185 -21
  89. package/dist/modules/lsp/probe.js +76 -0
  90. package/dist/modules/lsp/project-root.js +32 -0
  91. package/dist/modules/lsp/startup-check.js +141 -0
  92. package/dist/modules/mcp/module.js +2 -6
  93. package/dist/modules/memory/index.js +1 -1
  94. package/dist/modules/memory/module.js +71 -23
  95. package/dist/modules/memory/search.js +11 -9
  96. package/dist/modules/memory/store.js +13 -13
  97. package/dist/modules/pipelines/engine.js +10 -10
  98. package/dist/modules/pipelines/index.js +3 -3
  99. package/dist/modules/pipelines/parser.js +17 -14
  100. package/dist/modules/pipelines/template.js +1 -1
  101. package/dist/modules/plugins/builtin/lint-on-write.js +21 -16
  102. package/dist/modules/plugins/builtin/notify.js +3 -2
  103. package/dist/modules/plugins/index.js +1 -1
  104. package/dist/modules/plugins/loader.js +59 -17
  105. package/dist/modules/plugins/manager.js +73 -17
  106. package/dist/modules/processes/detect.js +34 -0
  107. package/dist/modules/processes/index.js +1 -1
  108. package/dist/modules/processes/registry.js +135 -46
  109. package/dist/modules/registry.js +4 -2
  110. package/dist/modules/security/audit-notifier.js +39 -39
  111. package/dist/modules/security/command-validator.js +2 -8
  112. package/dist/modules/security/data-sanitizer.js +1 -9
  113. package/dist/modules/security/encryption.js +58 -56
  114. package/dist/modules/security/network-validator.js +1 -9
  115. package/dist/modules/security/path-validator.js +1 -3
  116. package/dist/modules/security/security-policies.js +3 -19
  117. package/dist/modules/security/session-encryption.js +1 -1
  118. package/dist/modules/security/session-isolation.js +8 -8
  119. package/dist/modules/session/index.js +3 -3
  120. package/dist/modules/session/module.js +5 -5
  121. package/dist/modules/session/store.js +3 -9
  122. package/dist/modules/skills/matcher.js +27 -0
  123. package/dist/modules/skills/module.js +1 -2
  124. package/dist/modules/updater/checker.js +70 -6
  125. package/dist/modules/updater/index.js +2 -1
  126. package/dist/modules/updater/module.js +116 -0
  127. package/dist/modules/user-profile/compressor.js +2 -2
  128. package/dist/modules/user-profile/index.js +1 -1
  129. package/dist/modules/user-profile/profile.js +9 -9
  130. package/dist/tools/attach-image.js +1 -1
  131. package/dist/tools/bash.js +178 -19
  132. package/dist/tools/browser.js +46 -29
  133. package/dist/tools/chunk-query.js +99 -0
  134. package/dist/tools/download-file.js +116 -0
  135. package/dist/tools/enable-tools.js +58 -0
  136. package/dist/tools/executor.js +4 -5
  137. package/dist/tools/file-info.js +13 -12
  138. package/dist/tools/filter-tools.js +9 -2
  139. package/dist/tools/glob-tool.js +11 -11
  140. package/dist/tools/grep-tool.js +1 -3
  141. package/dist/tools/hidden-tools-block.js +37 -0
  142. package/dist/tools/index.js +13 -2
  143. package/dist/tools/list-dir.js +18 -17
  144. package/dist/tools/load-skill.js +1 -3
  145. package/dist/tools/path-utils.js +4 -4
  146. package/dist/tools/pipeline-run.js +25 -25
  147. package/dist/tools/process-kill.js +11 -11
  148. package/dist/tools/process-list.js +20 -22
  149. package/dist/tools/process-log.js +22 -18
  150. package/dist/tools/question.js +1 -3
  151. package/dist/tools/read-file.js +10 -2
  152. package/dist/tools/recall.js +44 -37
  153. package/dist/tools/registry.js +15 -4
  154. package/dist/tools/remember.js +29 -29
  155. package/dist/tools/scope-check.js +9 -9
  156. package/dist/tools/subagent.js +54 -9
  157. package/dist/tools/user-input.js +1 -1
  158. package/dist/tools/web-browse.js +3 -3
  159. package/dist/tools/web-fetch.js +3 -3
  160. package/dist/tools/web-search.js +3 -3
  161. package/dist/tools/write-file.js +1 -3
  162. package/dist/ui/box.js +1 -5
  163. package/dist/ui/index.js +6 -6
  164. package/dist/ui/line-editor.js +703 -0
  165. package/dist/ui/line-math.js +69 -0
  166. package/dist/ui/md-formatter.js +33 -33
  167. package/dist/ui/output.js +5 -5
  168. package/dist/ui/plan-view.js +103 -0
  169. package/dist/ui/renderer.js +15 -10
  170. package/dist/ui/table.js +1 -1
  171. package/package.json +48 -48
package/bin/mma.mjs CHANGED
@@ -1,41 +1,41 @@
1
- #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
3
- import { fileURLToPath, pathToFileURL } from "node:url";
4
- import { dirname, join } from "node:path";
5
-
6
- // MMA is designed to run under Bun: clipboard image paste relies on
7
- // `Bun.Image.fromClipboard()` (Node has no clipboard API), and on Windows a
8
- // bare `npx.cmd` shim can only be spawned reliably through Bun's child_process
9
- // (under Node it throws `spawn EINVAL`). The global npm shim (`mma` /
10
- // `mma.cmd`) launches this file under Node, so re-execute ourselves under Bun
11
- // when it is available. The `bun run mma` path already runs under Bun and
12
- // skips the respawn.
13
- const runningUnderBun =
14
- typeof globalThis.Bun !== "undefined" &&
15
- typeof globalThis.Bun?.version !== "undefined";
16
-
17
- if (process.env.MMA_DEBUG_RUNTIME) {
18
- console.error(
19
- "[mma] runtime=" +
20
- (runningUnderBun ? "bun " + globalThis.Bun.version : "node " + process.version),
21
- );
22
- }
23
-
24
- if (!runningUnderBun) {
25
- try {
26
- const res = spawnSync("bun", [process.argv[1], ...process.argv.slice(2)], {
27
- stdio: "inherit",
28
- windowsHide: true,
29
- });
30
- if (res.error) throw res.error;
31
- process.exit(res.status ?? 0);
32
- } catch {
33
- // `bun` is not on PATH — fall back to the Node import path (clipboard
34
- // paste and LSP will degrade, but the agent still works).
35
- }
36
- }
37
-
38
- const __dirname = dirname(fileURLToPath(import.meta.url));
39
- const main = join(__dirname, "..", "dist", "main.js");
40
-
41
- await import(pathToFileURL(main).href);
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+
6
+ // MMA is designed to run under Bun: clipboard image paste relies on
7
+ // `Bun.Image.fromClipboard()` (Node has no clipboard API), and on Windows a
8
+ // bare `npx.cmd` shim can only be spawned reliably through Bun's child_process
9
+ // (under Node it throws `spawn EINVAL`). The global npm shim (`mma` /
10
+ // `mma.cmd`) launches this file under Node, so re-execute ourselves under Bun
11
+ // when it is available. The `bun run mma` path already runs under Bun and
12
+ // skips the respawn.
13
+ const runningUnderBun =
14
+ typeof globalThis.Bun !== "undefined" &&
15
+ typeof globalThis.Bun?.version !== "undefined";
16
+
17
+ if (process.env.MMA_DEBUG_RUNTIME) {
18
+ console.error(
19
+ "[mma] runtime=" +
20
+ (runningUnderBun ? "bun " + globalThis.Bun.version : "node " + process.version),
21
+ );
22
+ }
23
+
24
+ if (!runningUnderBun) {
25
+ try {
26
+ const res = spawnSync("bun", [process.argv[1], ...process.argv.slice(2)], {
27
+ stdio: "inherit",
28
+ windowsHide: true,
29
+ });
30
+ if (res.error) throw res.error;
31
+ process.exit(res.status ?? 0);
32
+ } catch {
33
+ // `bun` is not on PATH — fall back to the Node import path (clipboard
34
+ // paste and LSP will degrade, but the agent still works).
35
+ }
36
+ }
37
+
38
+ const __dirname = dirname(fileURLToPath(import.meta.url));
39
+ const main = join(__dirname, "..", "dist", "main.js");
40
+
41
+ await import(pathToFileURL(main).href);
@@ -7,13 +7,11 @@ import { join, dirname } from "path";
7
7
  import { homedir } from "os";
8
8
  import { existsSync, readFileSync } from "fs";
9
9
  import { createSecurityCommand } from "./security-commands";
10
+ import { createPluginCommand } from "./plugin-commands";
10
11
  import { fileURLToPath } from "url";
11
12
  function readVersion() {
12
13
  const here = dirname(fileURLToPath(import.meta.url));
13
- const candidates = [
14
- join(here, "..", "..", "package.json"),
15
- join(here, "..", "package.json"),
16
- ];
14
+ const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
17
15
  for (const p of candidates) {
18
16
  if (existsSync(p)) {
19
17
  try {
@@ -55,9 +53,7 @@ export function createProgram() {
55
53
  // Apply security settings from wizard
56
54
  if (config.security) {
57
55
  config.security.enabled =
58
- answers.securityBashBlock ||
59
- answers.securityFlagsBlock ||
60
- !answers.securityPathsDeny;
56
+ answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
61
57
  config.security.bash.enabled = config.security.enabled;
62
58
  config.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
63
59
  if (answers.securityBashBlock) {
@@ -90,9 +86,7 @@ export function createProgram() {
90
86
  saveConfig(config, configPath);
91
87
  console.log(t("cli.config_saved"));
92
88
  });
93
- const configCmd = program
94
- .command("config")
95
- .description(t("cli.manage_config"));
89
+ const configCmd = program.command("config").description(t("cli.manage_config"));
96
90
  configCmd
97
91
  .command("set")
98
92
  .argument("<key>", t("cli.config_key"))
@@ -199,9 +193,7 @@ export function createProgram() {
199
193
  name,
200
194
  providerUrl: cmdOpts.providerUrl,
201
195
  providerKey: cmdOpts.providerKey,
202
- contextWindow: cmdOpts.contextWindow
203
- ? parseInt(cmdOpts.contextWindow, 10)
204
- : undefined,
196
+ contextWindow: cmdOpts.contextWindow ? parseInt(cmdOpts.contextWindow, 10) : undefined,
205
197
  tags: parseTags(cmdOpts.tags),
206
198
  reps: cmdOpts.reps ? parseInt(cmdOpts.reps, 10) : 3,
207
199
  force: cmdOpts.force === true,
@@ -251,9 +243,7 @@ export function createProgram() {
251
243
  saveConfig(config, configPath);
252
244
  console.log(t("cli.context_set", { size: contextWindow }));
253
245
  });
254
- const provider = program
255
- .command("provider")
256
- .description(t("cli.manage_providers"));
246
+ const provider = program.command("provider").description(t("cli.manage_providers"));
257
247
  provider
258
248
  .command("list")
259
249
  .description(t("cli.list_providers"))
@@ -273,9 +263,7 @@ export function createProgram() {
273
263
  saveConfig(config, configPath);
274
264
  console.log(t("cli.provider_set", { name }));
275
265
  });
276
- const session = program
277
- .command("session")
278
- .description(t("cli.manage_sessions"));
266
+ const session = program.command("session").description(t("cli.manage_sessions"));
279
267
  session
280
268
  .command("list")
281
269
  .description(t("cli.list_sessions"))
@@ -323,6 +311,8 @@ export function createProgram() {
323
311
  });
324
312
  // Add security commands
325
313
  createSecurityCommand(program);
314
+ // Add plugin commands
315
+ createPluginCommand(program);
326
316
  program
327
317
  .argument("[prompt...]", "Prompt to execute")
328
318
  .description("Run a single prompt")
@@ -1,22 +1,20 @@
1
- import { readdirSync, statSync } from 'fs';
2
- import { join } from 'path';
1
+ import { readdirSync, statSync } from "fs";
2
+ import { join } from "path";
3
3
  export class SlashCommandProvider {
4
- name = 'slash-commands';
4
+ name = "slash-commands";
5
5
  commands;
6
6
  constructor(commands) {
7
7
  this.commands = commands;
8
8
  }
9
9
  match(ctx) {
10
- return ctx.tokenIndex === 0 && ctx.line.startsWith('/');
10
+ return ctx.tokenIndex === 0 && ctx.line.startsWith("/");
11
11
  }
12
12
  complete(ctx) {
13
- return this.commands
14
- .filter(c => c.startsWith(ctx.partial.slice(1)))
15
- .map(c => `/${c}`);
13
+ return this.commands.filter((c) => c.startsWith(ctx.partial.slice(1))).map((c) => `/${c}`);
16
14
  }
17
15
  }
18
16
  export class SubcommandProvider {
19
- name = 'subcommands';
17
+ name = "subcommands";
20
18
  command;
21
19
  subcommands;
22
20
  constructor(command, subcommands) {
@@ -27,54 +25,54 @@ export class SubcommandProvider {
27
25
  return ctx.tokenIndex === 1 && ctx.tokens[0] === `/${this.command}`;
28
26
  }
29
27
  complete(ctx) {
30
- return this.subcommands.filter(s => s.startsWith(ctx.partial));
28
+ return this.subcommands.filter((s) => s.startsWith(ctx.partial));
31
29
  }
32
30
  }
33
31
  export class ModelArgProvider {
34
- name = 'model-args';
32
+ name = "model-args";
35
33
  models;
36
34
  constructor(models) {
37
35
  this.models = models;
38
36
  }
39
37
  match(ctx) {
40
- return ctx.tokenIndex === 2 && (ctx.tokens[0] === '/model' || ctx.tokens[0] === '/config');
38
+ return ctx.tokenIndex === 2 && (ctx.tokens[0] === "/model" || ctx.tokens[0] === "/config");
41
39
  }
42
40
  complete(ctx) {
43
- return this.models.filter(m => m.startsWith(ctx.partial));
41
+ return this.models.filter((m) => m.startsWith(ctx.partial));
44
42
  }
45
43
  }
46
44
  export class ConfigKeyProvider {
47
- name = 'config-keys';
45
+ name = "config-keys";
48
46
  keys;
49
47
  constructor(keys) {
50
48
  this.keys = keys;
51
49
  }
52
50
  match(ctx) {
53
- return ctx.tokenIndex >= 1 && ctx.tokens[0] === '/config' && ctx.tokens[1] === 'set';
51
+ return ctx.tokenIndex >= 1 && ctx.tokens[0] === "/config" && ctx.tokens[1] === "set";
54
52
  }
55
53
  complete(ctx) {
56
- return this.keys.filter(k => k.startsWith(ctx.partial));
54
+ return this.keys.filter((k) => k.startsWith(ctx.partial));
57
55
  }
58
56
  }
59
57
  export class FilePathProvider {
60
- name = 'file-paths';
58
+ name = "file-paths";
61
59
  match(ctx) {
62
- return ctx.partial.includes('/') || ctx.partial.includes('\\');
60
+ return ctx.partial.includes("/") || ctx.partial.includes("\\");
63
61
  }
64
62
  complete(ctx) {
65
63
  try {
66
64
  const cwd = process.cwd();
67
65
  const partialPath = ctx.partial;
68
- const lastSlash = Math.max(partialPath.lastIndexOf('/'), partialPath.lastIndexOf('\\'));
69
- const dirPath = lastSlash > 0 ? partialPath.slice(0, lastSlash) : '.';
70
- const prefix = lastSlash > 0 ? partialPath.slice(0, lastSlash + 1) : '';
66
+ const lastSlash = Math.max(partialPath.lastIndexOf("/"), partialPath.lastIndexOf("\\"));
67
+ const dirPath = lastSlash > 0 ? partialPath.slice(0, lastSlash) : ".";
68
+ const prefix = lastSlash > 0 ? partialPath.slice(0, lastSlash + 1) : "";
71
69
  const fullPath = join(cwd, dirPath);
72
70
  const entries = readdirSync(fullPath);
73
71
  const matches = [];
74
72
  for (const entry of entries) {
75
73
  const entryPath = join(fullPath, entry);
76
74
  const stat = statSync(entryPath);
77
- const completion = prefix + entry + (stat.isDirectory() ? '/' : '');
75
+ const completion = prefix + entry + (stat.isDirectory() ? "/" : "");
78
76
  if (completion.startsWith(ctx.partial)) {
79
77
  matches.push(completion);
80
78
  }
@@ -87,59 +85,60 @@ export class FilePathProvider {
87
85
  }
88
86
  }
89
87
  export class ProviderArgProvider {
90
- name = 'provider-args';
88
+ name = "provider-args";
91
89
  providers;
92
90
  constructor(providers) {
93
91
  this.providers = providers;
94
92
  }
95
93
  match(ctx) {
96
- return ctx.tokenIndex === 2 && ctx.tokens[0] === '/provider' && ctx.tokens[1] === 'use';
94
+ return ctx.tokenIndex === 2 && ctx.tokens[0] === "/provider" && ctx.tokens[1] === "use";
97
95
  }
98
96
  complete(ctx) {
99
- return this.providers.filter(p => p.startsWith(ctx.partial));
97
+ return this.providers.filter((p) => p.startsWith(ctx.partial));
100
98
  }
101
99
  }
102
100
  export class SessionNameProvider {
103
- name = 'session-names';
101
+ name = "session-names";
104
102
  getNames;
105
103
  constructor(getNames) {
106
104
  this.getNames = getNames;
107
105
  }
108
106
  match(ctx) {
109
- return ctx.tokenIndex >= 1 && ctx.tokens[0] === '/resume';
107
+ return ctx.tokenIndex >= 1 && ctx.tokens[0] === "/resume";
110
108
  }
111
109
  complete(ctx) {
112
110
  const names = this.getNames();
113
111
  if (!ctx.partial)
114
112
  return names;
115
- return names.filter(n => n.toLowerCase().includes(ctx.partial.toLowerCase()));
113
+ return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
116
114
  }
117
115
  }
118
116
  export class SkillNameProvider {
119
- name = 'skill-names';
117
+ name = "skill-names";
120
118
  skillsModule;
121
119
  constructor(skillsModule) {
122
120
  this.skillsModule = skillsModule;
123
121
  }
124
122
  match(ctx) {
125
- return ctx.tokenIndex >= 2 && ctx.tokens[0] === '/skill' &&
126
- (ctx.tokens[1] === 'load' || ctx.tokens[1] === 'unload');
123
+ return (ctx.tokenIndex >= 2 &&
124
+ ctx.tokens[0] === "/skill" &&
125
+ (ctx.tokens[1] === "load" || ctx.tokens[1] === "unload"));
127
126
  }
128
127
  complete(ctx) {
129
128
  const subcmd = ctx.tokens[1];
130
- if (subcmd === 'load') {
129
+ if (subcmd === "load") {
131
130
  const available = this.skillsModule.getAvailable();
132
- const names = available.map(s => s.name);
131
+ const names = available.map((s) => s.name);
133
132
  if (!ctx.partial)
134
133
  return names;
135
- return names.filter(n => n.toLowerCase().includes(ctx.partial.toLowerCase()));
134
+ return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
136
135
  }
137
- if (subcmd === 'unload') {
136
+ if (subcmd === "unload") {
138
137
  const loaded = this.skillsModule.getLoaded();
139
- const names = loaded.map(s => s.name);
138
+ const names = loaded.map((s) => s.name);
140
139
  if (!ctx.partial)
141
140
  return names;
142
- return names.filter(n => n.toLowerCase().includes(ctx.partial.toLowerCase()));
141
+ return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
143
142
  }
144
143
  return [];
145
144
  }
@@ -152,7 +151,7 @@ export class Completer {
152
151
  complete(line) {
153
152
  const cursor = line.length;
154
153
  const tokens = line.split(/\s+/);
155
- const partial = tokens[tokens.length - 1] || '';
154
+ const partial = tokens[tokens.length - 1] || "";
156
155
  const tokenIndex = tokens.length - 1;
157
156
  const ctx = { line, cursor, tokens, partial, tokenIndex };
158
157
  for (const provider of this.providers) {
package/dist/cli/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { main } from './main';
2
- export { createProgram } from './commands';
1
+ export { main } from "./main";
2
+ export { createProgram } from "./commands";
package/dist/cli/main.js CHANGED
@@ -1,15 +1,50 @@
1
1
  import { createProgram } from "./commands";
2
2
  import { bootstrap } from "../core/bootstrap";
3
3
  import { Repl } from "./repl";
4
- import { formatMarkdown } from "../ui/md-formatter";
5
4
  import { Renderer } from "../ui/renderer";
5
+ import { printRunResult } from "./run-result";
6
6
  import { runSetup } from "./setup";
7
7
  import { saveConfig, loadConfig } from "../config/config";
8
8
  import { t } from "../i18n/index";
9
9
  import { pc } from "../ui/colors";
10
- import { existsSync } from "fs";
11
- import { join } from "path";
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { join, dirname } from "path";
12
12
  import { homedir } from "os";
13
+ import { fileURLToPath } from "url";
14
+ import { UpdaterModule } from "../modules/updater/index";
15
+ function readVersion() {
16
+ const here = dirname(fileURLToPath(import.meta.url));
17
+ const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
18
+ for (const p of candidates) {
19
+ if (existsSync(p)) {
20
+ try {
21
+ const raw = JSON.parse(readFileSync(p, "utf8"));
22
+ if (raw.version)
23
+ return raw.version;
24
+ }
25
+ catch {
26
+ // fall through
27
+ }
28
+ }
29
+ }
30
+ return "0.0.0";
31
+ }
32
+ /** Fire-and-forget self-update in the background (never blocks startup). */
33
+ function startAutoUpdate(config) {
34
+ try {
35
+ const module = new UpdaterModule(config.updater, readVersion(), "micro-models-agent", {
36
+ info: (m) => process.stderr.write(pc.dim(m) + "\n"),
37
+ warn: (m) => process.stderr.write(pc.yellow(m) + "\n"),
38
+ error: (m) => process.stderr.write(pc.red(m) + "\n"),
39
+ });
40
+ module.start();
41
+ return module;
42
+ }
43
+ catch {
44
+ // Auto-update is best-effort; never let it crash startup.
45
+ return undefined;
46
+ }
47
+ }
13
48
  async function main() {
14
49
  const program = createProgram();
15
50
  program.parse(process.argv);
@@ -26,6 +61,7 @@ async function main() {
26
61
  if (program.args.length > 0) {
27
62
  const prompt = program.args.join(" ");
28
63
  const { agent, config } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
64
+ const updater = exitOnComplete ? undefined : startAutoUpdate(config);
29
65
  if (jsonMode) {
30
66
  const result = await agent.run(prompt);
31
67
  agent.shutdown();
@@ -41,6 +77,7 @@ async function main() {
41
77
  totalTokens: result.totalTokens ?? null,
42
78
  }, null, 2));
43
79
  process.stdout.write("\n");
80
+ await updater?.waitForIdle();
44
81
  process.exit(result.success ? 0 : 1);
45
82
  }
46
83
  const renderer = new Renderer({
@@ -63,19 +100,10 @@ async function main() {
63
100
  }
64
101
  });
65
102
  renderer.flush();
66
- if (result.success) {
67
- if (!result.text) {
68
- console.log(pc.yellow(t("cli.no_output")));
69
- }
70
- }
71
- else {
72
- console.error(`${t("error.prefix")}${result.error}`);
73
- if (result.text) {
74
- console.log(formatMarkdown(result.text));
75
- }
76
- process.exit(1);
77
- }
103
+ const exitCode = printRunResult(result, () => renderer.flush());
78
104
  agent.shutdown();
105
+ await updater?.waitForIdle();
106
+ process.exit(exitCode);
79
107
  }
80
108
  else {
81
109
  const configPath = join(homedir(), ".mma", "config.json");
@@ -84,9 +112,7 @@ async function main() {
84
112
  const answers = await runSetup();
85
113
  const config = loadConfig({
86
114
  configDir: join(homedir(), ".mma"),
87
- projectConfigPath: projectDir
88
- ? join(projectDir, ".mmrc")
89
- : join(process.cwd(), ".mmrc"),
115
+ projectConfigPath: projectDir ? join(projectDir, ".mmrc") : join(process.cwd(), ".mmrc"),
90
116
  });
91
117
  config.provider.type = answers.provider;
92
118
  config.provider.baseUrl = answers.apiBase;
@@ -97,9 +123,7 @@ async function main() {
97
123
  config.locale = answers.locale;
98
124
  if (config.security) {
99
125
  config.security.enabled =
100
- answers.securityBashBlock ||
101
- answers.securityFlagsBlock ||
102
- !answers.securityPathsDeny;
126
+ answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
103
127
  config.security.bash.enabled = config.security.enabled;
104
128
  config.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
105
129
  if (answers.securityBashBlock) {
@@ -132,8 +156,9 @@ async function main() {
132
156
  saveConfig(config, configPath);
133
157
  }
134
158
  const { agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, logger, } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
135
- const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger);
136
- repl.start();
159
+ startAutoUpdate(config);
160
+ const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, true);
161
+ await repl.start();
137
162
  }
138
163
  }
139
164
  main();
@@ -0,0 +1,36 @@
1
+ import { bootstrap } from "../core/bootstrap";
2
+ import { readMmaVersion } from "../core/version";
3
+ import { t } from "../i18n/index";
4
+ /**
5
+ * Create the `mma plugins` subcommand group.
6
+ */
7
+ export function createPluginCommand(program) {
8
+ const pluginsCmd = program.command("plugins").description(t("cli.plugins.description"));
9
+ pluginsCmd
10
+ .command("list")
11
+ .description(t("cli.plugins.list"))
12
+ .option("-a, --all", t("cli.plugins.all"))
13
+ .action(async (cmdOpts) => {
14
+ const { pluginManager } = await bootstrap();
15
+ const all = cmdOpts.all === true;
16
+ let infos = pluginManager.getPluginInfos();
17
+ if (!all) {
18
+ infos = infos.filter(({ plugin }) => !plugin.isBuiltin);
19
+ }
20
+ if (infos.length === 0) {
21
+ console.log(t("cli.plugins.none"));
22
+ return;
23
+ }
24
+ const mmaVersion = readMmaVersion();
25
+ console.log(t("cli.plugins.header", { count: String(infos.length), mma: mmaVersion }));
26
+ for (const { plugin, source } of infos) {
27
+ const version = plugin.version || "-";
28
+ const origin = source || "builtin";
29
+ const builtin = plugin.isBuiltin ? t("cli.plugins.builtin_mark") : " ";
30
+ console.log(` ${builtin} ${plugin.name} v${version} [${origin}]`);
31
+ }
32
+ if (!all) {
33
+ console.log(t("cli.plugins.only_external"));
34
+ }
35
+ });
36
+ }
@@ -10,10 +10,7 @@ import { existsSync, readFileSync } from "fs";
10
10
  import { fileURLToPath } from "url";
11
11
  function readVersion() {
12
12
  const here = dirname(fileURLToPath(import.meta.url));
13
- const candidates = [
14
- join(here, "..", "..", "package.json"),
15
- join(here, "..", "package.json"),
16
- ];
13
+ const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
17
14
  for (const p of candidates) {
18
15
  if (existsSync(p)) {
19
16
  try {
@@ -50,6 +47,7 @@ export const COMMAND_GROUPS = {
50
47
  rename: "session",
51
48
  delete: "session",
52
49
  skill: "skill",
50
+ plugins: "agent",
53
51
  };
54
52
  export function registerAllCommands(ctx) {
55
53
  registerBuiltinCommands(ctx);
@@ -122,8 +120,7 @@ function registerMmaCommands(ctx) {
122
120
  dataUrl = result.dataUrl;
123
121
  label = "clipboard";
124
122
  }
125
- else if (source.startsWith("http://") ||
126
- source.startsWith("https://")) {
123
+ else if (source.startsWith("http://") || source.startsWith("https://")) {
127
124
  const result = await loadUrlAsDataUrl(source);
128
125
  dataUrl = result.dataUrl;
129
126
  label = source;
@@ -184,9 +181,7 @@ function registerMmaCommands(ctx) {
184
181
  usage: t("repl.reasoning_usage"),
185
182
  action: () => {
186
183
  ctx.config.showReasoning = !ctx.config.showReasoning;
187
- const status = ctx.config.showReasoning
188
- ? pc.green(t("repl.show"))
189
- : pc.dim(t("repl.hide"));
184
+ const status = ctx.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"));
190
185
  console.log(t("repl.reasoning_status", { status }));
191
186
  },
192
187
  });
@@ -391,6 +386,41 @@ function registerMmaCommands(ctx) {
391
386
  console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
392
387
  },
393
388
  });
389
+ ctx.registerCommand({
390
+ name: "plugins",
391
+ description: t("repl.plugins"),
392
+ usage: t("repl.plugins_usage"),
393
+ action: (args) => {
394
+ if (!ctx.pluginManager) {
395
+ console.log(t("cli.plugins.none"));
396
+ return;
397
+ }
398
+ const showAll = args.includes("--all");
399
+ const infos = ctx.pluginManager.getPluginInfos();
400
+ const filtered = showAll ? infos : infos.filter(({ plugin }) => !plugin.isBuiltin);
401
+ if (filtered.length === 0) {
402
+ console.log(t("cli.plugins.none"));
403
+ return;
404
+ }
405
+ const mmaVersion = version;
406
+ console.log(t("cli.plugins.header", {
407
+ count: String(filtered.length),
408
+ mma: mmaVersion,
409
+ }));
410
+ for (const { plugin, source } of filtered) {
411
+ const v = plugin.version || "-";
412
+ const origin = source || "builtin";
413
+ const mark = plugin.isBuiltin ? t("cli.plugins.builtin_mark") : " ";
414
+ console.log(` ${mark} ${plugin.name} v${v} [${origin}]`);
415
+ }
416
+ if (!showAll) {
417
+ const builtinCount = infos.filter(({ plugin }) => plugin.isBuiltin).length;
418
+ if (builtinCount > 0) {
419
+ console.log(pc.dim(` (${builtinCount} builtin hidden — /plugins --all to show)`));
420
+ }
421
+ }
422
+ },
423
+ });
394
424
  }
395
425
  function registerSessionCommands(ctx) {
396
426
  if (!ctx.sessionManager)
@@ -559,9 +589,7 @@ function registerSkillCommands(ctx) {
559
589
  console.log(pc.bold(t("repl.available_skills")));
560
590
  for (const skill of available) {
561
591
  const tokens = Math.ceil(skill.content.length / 4);
562
- const loaded = ctx
563
- .skillsModule.getLoaded()
564
- .some((s) => s.name === skill.name);
592
+ const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
565
593
  const marker = loaded ? pc.green(" [loaded]") : "";
566
594
  console.log(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
567
595
  }