micro-models-agent 0.39.1 → 0.40.1

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 (96) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +116 -3
  3. package/dist/cli/main.js +35 -8
  4. package/dist/cli/repl-commands.js +633 -0
  5. package/dist/cli/repl.js +110 -611
  6. package/dist/cli/setup.js +32 -12
  7. package/dist/config/config.js +46 -30
  8. package/dist/config/defaults.js +10 -1
  9. package/dist/config/security.js +15 -8
  10. package/dist/core/agent-moe.js +24 -12
  11. package/dist/core/agent.js +281 -47
  12. package/dist/core/bootstrap.js +52 -36
  13. package/dist/core/session-logger.js +35 -2
  14. package/dist/core/workspace.js +76 -0
  15. package/dist/i18n/en.json +79 -15
  16. package/dist/i18n/index.js +12 -9
  17. package/dist/i18n/ru.json +79 -15
  18. package/dist/index.js +13 -13
  19. package/dist/llm/openai-compat.js +39 -10
  20. package/dist/logger/app-logger.js +83 -16
  21. package/dist/logger/file-log.js +151 -0
  22. package/dist/main.js +634 -311
  23. package/dist/modules/browser/bridge-server.mjs +113 -105
  24. package/dist/modules/browser/session.js +108 -60
  25. package/dist/modules/certification/cli.js +176 -0
  26. package/dist/modules/certification/fact-checker.js +84 -0
  27. package/dist/modules/certification/loader.js +111 -0
  28. package/dist/modules/certification/manifest.js +50 -0
  29. package/dist/modules/certification/runner.js +162 -0
  30. package/dist/modules/certification/scenarios.js +124 -0
  31. package/dist/modules/certification/types.js +1 -0
  32. package/dist/modules/context/manager.js +119 -10
  33. package/dist/modules/execution/auditor.js +33 -39
  34. package/dist/modules/execution/index.js +8 -6
  35. package/dist/modules/execution/module.js +474 -32
  36. package/dist/modules/execution/moe-executor.js +97 -40
  37. package/dist/modules/execution/plan-coverage.js +68 -0
  38. package/dist/modules/execution/plan-persister.js +46 -0
  39. package/dist/modules/execution/plan-store.js +159 -0
  40. package/dist/modules/execution/planner.js +63 -13
  41. package/dist/modules/execution/stuck-detector.js +252 -39
  42. package/dist/modules/execution/tracker.js +21 -7
  43. package/dist/modules/execution/verifier.js +46 -17
  44. package/dist/modules/hallucination/confidence.js +7 -2
  45. package/dist/modules/hallucination/consistency.js +8 -42
  46. package/dist/modules/hallucination/detector.js +26 -21
  47. package/dist/modules/hallucination/factual.js +170 -150
  48. package/dist/modules/hallucination/index.js +5 -4
  49. package/dist/modules/hallucination/js-identifiers.js +72 -0
  50. package/dist/modules/hallucination/llm-judge.js +103 -0
  51. package/dist/modules/index.js +5 -5
  52. package/dist/modules/lsp/client.js +235 -0
  53. package/dist/modules/lsp/config.js +81 -0
  54. package/dist/modules/lsp/index.js +3 -0
  55. package/dist/modules/lsp/module.js +68 -0
  56. package/dist/modules/lsp/types.js +1 -0
  57. package/dist/modules/mcp/client.js +8 -2
  58. package/dist/modules/memory/store.js +4 -0
  59. package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
  60. package/dist/modules/processes/index.js +1 -2
  61. package/dist/modules/processes/registry.js +125 -35
  62. package/dist/modules/processes/runner.js +9 -110
  63. package/dist/modules/security/audit-log.js +30 -10
  64. package/dist/modules/security/command-validator.js +42 -16
  65. package/dist/modules/security/content-scanner.js +9 -8
  66. package/dist/modules/security/network-validator.js +2 -2
  67. package/dist/modules/security/path-validator.js +64 -10
  68. package/dist/modules/security/security-policies.js +221 -67
  69. package/dist/modules/security/session-encryption.js +42 -25
  70. package/dist/modules/session/manager.js +15 -10
  71. package/dist/modules/session/store.js +62 -8
  72. package/dist/modules/skills/index.js +2 -3
  73. package/dist/modules/skills/module.js +10 -23
  74. package/dist/tools/bash.js +287 -90
  75. package/dist/tools/create-dir.js +0 -1
  76. package/dist/tools/delete-file.js +0 -1
  77. package/dist/tools/edit-file.js +10 -8
  78. package/dist/tools/executor.js +57 -7
  79. package/dist/tools/grep-tool.js +51 -29
  80. package/dist/tools/index.js +55 -40
  81. package/dist/tools/load-skill.js +14 -18
  82. package/dist/tools/move-file.js +3 -2
  83. package/dist/tools/pipeline-run.js +1 -1
  84. package/dist/tools/read-file.js +15 -5
  85. package/dist/tools/search-history.js +42 -22
  86. package/dist/tools/subagent.js +21 -12
  87. package/dist/tools/web-browse.js +54 -25
  88. package/dist/tools/web-fetch.js +60 -34
  89. package/dist/tools/web-search.js +39 -20
  90. package/dist/tools/write-file.js +13 -10
  91. package/dist/ui/diff.js +9 -16
  92. package/dist/ui/renderer.js +69 -6
  93. package/package.json +48 -45
  94. package/dist/modules/context/history.js +0 -15
  95. package/dist/modules/processes/detect.js +0 -34
  96. package/dist/modules/skills/matcher.js +0 -27
@@ -0,0 +1,633 @@
1
+ import { pc } from "../ui/colors";
2
+ import { renderTable } from "../ui/table";
3
+ import { t } from "../i18n/index";
4
+ import { runSetup } from "./setup";
5
+ import { saveConfig } from "../config/config";
6
+ import { getMessageText } from "../llm/provider";
7
+ import { join, dirname } from "path";
8
+ import { homedir } from "os";
9
+ import { existsSync, readFileSync } from "fs";
10
+ import { fileURLToPath } from "url";
11
+ function readVersion() {
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const candidates = [
14
+ join(here, "..", "..", "package.json"),
15
+ join(here, "..", "package.json"),
16
+ ];
17
+ for (const p of candidates) {
18
+ if (existsSync(p)) {
19
+ try {
20
+ const raw = JSON.parse(readFileSync(p, "utf8"));
21
+ if (raw.version)
22
+ return raw.version;
23
+ }
24
+ catch {
25
+ // Broken package.json — fall through to the next candidate
26
+ }
27
+ }
28
+ }
29
+ return "0.0.0";
30
+ }
31
+ const version = readVersion();
32
+ export const COMMAND_GROUPS = {
33
+ help: "general",
34
+ exit: "general",
35
+ clear: "general",
36
+ run: "general",
37
+ image: "general",
38
+ config: "agent",
39
+ status: "agent",
40
+ reasoning: "agent",
41
+ provider: "agent",
42
+ model: "agent",
43
+ context: "agent",
44
+ reload: "agent",
45
+ wizard: "agent",
46
+ sysprompt: "agent",
47
+ sessions: "session",
48
+ new: "session",
49
+ resume: "session",
50
+ rename: "session",
51
+ delete: "session",
52
+ skill: "skill",
53
+ };
54
+ export function registerAllCommands(ctx) {
55
+ registerBuiltinCommands(ctx);
56
+ registerMmaCommands(ctx);
57
+ registerSessionCommands(ctx);
58
+ registerSkillCommands(ctx);
59
+ }
60
+ function registerBuiltinCommands(ctx) {
61
+ ctx.registerCommand({
62
+ name: "help",
63
+ description: t("repl.help"),
64
+ usage: t("repl.help_usage"),
65
+ action: () => ctx.showHelp(),
66
+ });
67
+ ctx.registerCommand({
68
+ name: "exit",
69
+ description: t("repl.exit"),
70
+ aliases: ["quit", "q"],
71
+ usage: t("repl.exit_usage"),
72
+ action: () => ctx.stop(),
73
+ });
74
+ ctx.registerCommand({
75
+ name: "clear",
76
+ description: t("repl.clear"),
77
+ usage: t("repl.clear_usage"),
78
+ action: () => {
79
+ console.clear();
80
+ },
81
+ });
82
+ }
83
+ function registerMmaCommands(ctx) {
84
+ ctx.registerCommand({
85
+ name: "run",
86
+ description: t("repl.run"),
87
+ usage: t("repl.run_usage"),
88
+ action: async (args) => {
89
+ if (args.length === 0) {
90
+ console.log(t("repl.run_usage"));
91
+ return;
92
+ }
93
+ const prompt = args.join(" ");
94
+ await ctx.runAgent(prompt);
95
+ },
96
+ });
97
+ ctx.registerCommand({
98
+ name: "image",
99
+ description: t("repl.image"),
100
+ aliases: ["img"],
101
+ usage: t("repl.image_usage"),
102
+ action: async (args) => {
103
+ const source = args.join(" ");
104
+ if (!source) {
105
+ console.log(t("repl.image_usage"));
106
+ return;
107
+ }
108
+ try {
109
+ const { loadFileAsDataUrl, loadUrlAsDataUrl, readClipboardImage } = await import("../llm/image-utils");
110
+ const { existsSync } = await import("fs");
111
+ const { resolve } = await import("path");
112
+ let dataUrl;
113
+ let label;
114
+ if (source.toLowerCase() === "clipboard") {
115
+ const clipBuf = await readClipboardImage();
116
+ if (!clipBuf) {
117
+ console.log(pc.yellow(t("image.clipboard_empty")));
118
+ return;
119
+ }
120
+ const { bufferToDataUrl } = await import("../llm/image-utils");
121
+ const result = await bufferToDataUrl(clipBuf);
122
+ dataUrl = result.dataUrl;
123
+ label = "clipboard";
124
+ }
125
+ else if (source.startsWith("http://") ||
126
+ source.startsWith("https://")) {
127
+ const result = await loadUrlAsDataUrl(source);
128
+ dataUrl = result.dataUrl;
129
+ label = source;
130
+ }
131
+ else {
132
+ const absPath = resolve(process.cwd(), source);
133
+ if (!existsSync(absPath)) {
134
+ console.log(pc.red(t("image.not_found", { path: source })));
135
+ return;
136
+ }
137
+ const result = await loadFileAsDataUrl(absPath);
138
+ dataUrl = result.dataUrl;
139
+ label = source;
140
+ }
141
+ const contextManager = ctx.agent.contextManager;
142
+ if (!contextManager) {
143
+ console.log(pc.red(t("image.no_context")));
144
+ return;
145
+ }
146
+ contextManager.addPendingImage({
147
+ type: "image_url",
148
+ image_url: { url: dataUrl },
149
+ });
150
+ const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
151
+ console.log(pc.green(t("image.attached", { source: label, size: `${sizeKb} KB` })));
152
+ }
153
+ catch (err) {
154
+ console.log(pc.red(t("image.error", { message: err.message })));
155
+ }
156
+ },
157
+ });
158
+ ctx.registerCommand({
159
+ name: "config",
160
+ description: t("repl.config"),
161
+ usage: t("repl.config_usage"),
162
+ action: () => {
163
+ const cfg = ctx.config;
164
+ const ctxW = cfg.contextWindow;
165
+ const sys = Math.floor(ctxW * cfg.contextBudget.systemPrompt);
166
+ const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
167
+ console.log(`${t("repl.model")} ${cfg.model}`);
168
+ console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
169
+ console.log(`${t("repl.context")} ${ctxW} (sys:${sys} res:${res} hist:${ctxW - sys - res})`);
170
+ console.log(`${t("repl.max_iters")} ${cfg.maxToolIterations}`);
171
+ console.log(`${t("repl.stuck_thresh")} ${cfg.stuckThreshold}`);
172
+ console.log(`${t("repl.reasoning_label")} ${cfg.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"))}`);
173
+ console.log(`${t("repl.log_level")} ${cfg.logLevel}`);
174
+ console.log(`${t("repl.locale")} ${cfg.locale}`);
175
+ const meta = ctx.sessionManager?.getActiveMeta();
176
+ if (meta) {
177
+ console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
178
+ }
179
+ },
180
+ });
181
+ ctx.registerCommand({
182
+ name: "reasoning",
183
+ description: t("repl.reasoning"),
184
+ usage: t("repl.reasoning_usage"),
185
+ action: () => {
186
+ ctx.config.showReasoning = !ctx.config.showReasoning;
187
+ const status = ctx.config.showReasoning
188
+ ? pc.green(t("repl.show"))
189
+ : pc.dim(t("repl.hide"));
190
+ console.log(t("repl.reasoning_status", { status }));
191
+ },
192
+ });
193
+ ctx.registerCommand({
194
+ name: "status",
195
+ description: t("repl.status"),
196
+ usage: t("repl.status_usage"),
197
+ action: () => {
198
+ console.log(`${t("repl.model")} ${ctx.config.model}`);
199
+ console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
200
+ const meta = ctx.sessionManager?.getActiveMeta();
201
+ if (meta) {
202
+ console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
203
+ }
204
+ },
205
+ });
206
+ ctx.registerCommand({
207
+ name: "wizard",
208
+ description: t("repl.wizard"),
209
+ aliases: ["setup"],
210
+ usage: t("repl.wizard_usage"),
211
+ action: async () => {
212
+ console.log(pc.yellow(t("repl.wizard_running")));
213
+ await ctx.withExclusiveInput(async () => {
214
+ const answers = await runSetup(ctx.rl);
215
+ const configPath = join(homedir(), ".mma", "config.json");
216
+ ctx.config.provider.type = answers.provider;
217
+ ctx.config.provider.baseUrl = answers.apiBase;
218
+ ctx.config.provider.apiKey = answers.apiKey;
219
+ ctx.config.model = answers.model;
220
+ ctx.config.contextWindow = answers.contextWindow;
221
+ ctx.config.maxToolIterations = answers.maxToolIterations;
222
+ ctx.config.locale = answers.locale;
223
+ saveConfig(ctx.config, configPath);
224
+ await ctx.agent.reconfigure(ctx.config);
225
+ console.log(pc.green(t("cli.config_saved")));
226
+ });
227
+ },
228
+ });
229
+ ctx.registerCommand({
230
+ name: "sysprompt",
231
+ description: t("repl.sysprompt_desc"),
232
+ usage: t("repl.sysprompt_desc"),
233
+ action: () => {
234
+ const info = ctx.agent.getSystemPromptInfo();
235
+ console.log(pc.bold(`System prompt (${info.tokenCount} tokens):`));
236
+ console.log(pc.dim("─".repeat(60)));
237
+ for (const line of info.text.split("\n")) {
238
+ console.log(line);
239
+ }
240
+ console.log(pc.dim("─".repeat(60)));
241
+ if (info.excluded.length > 0) {
242
+ console.log(pc.yellow(`\nExcluded blocks: ${info.excluded.length}`));
243
+ for (const e of info.excluded) {
244
+ console.log(pc.dim(` - ${e.slice(0, 80)}${e.length > 80 ? "..." : ""}`));
245
+ }
246
+ }
247
+ },
248
+ });
249
+ ctx.registerCommand({
250
+ name: "provider",
251
+ description: t("repl.provider_list"),
252
+ usage: t("repl.provider_usage"),
253
+ action: async (args) => {
254
+ const subcmd = args[0];
255
+ if (!subcmd || subcmd === "list") {
256
+ console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
257
+ console.log(` ${ctx.config.provider.baseUrl}`);
258
+ return;
259
+ }
260
+ if (subcmd === "use") {
261
+ const name = args[1];
262
+ if (!name) {
263
+ console.log(t("repl.provider_usage"));
264
+ return;
265
+ }
266
+ ctx.config.provider.type = name;
267
+ const configPath = join(homedir(), ".mma", "config.json");
268
+ saveConfig(ctx.config, configPath);
269
+ await ctx.agent.reconfigure(ctx.config);
270
+ console.log(pc.green(t("repl.provider_set", { name })));
271
+ return;
272
+ }
273
+ console.log(t("repl.provider_usage"));
274
+ },
275
+ });
276
+ ctx.registerCommand({
277
+ name: "model",
278
+ description: t("repl.model_list"),
279
+ usage: t("repl.model_usage"),
280
+ action: async (args) => {
281
+ const subcmd = args[0];
282
+ if (!subcmd || subcmd === "list") {
283
+ console.log(`${t("repl.model_current")} ${ctx.config.model}`);
284
+ const { OpenAICompatProvider } = await import("../llm/openai-compat");
285
+ const provider = new OpenAICompatProvider({
286
+ model: ctx.config.model,
287
+ baseUrl: ctx.config.provider.baseUrl,
288
+ apiKey: ctx.config.provider.apiKey,
289
+ contextWindow: ctx.config.contextWindow,
290
+ });
291
+ const { Spinner } = await import("../ui/spinner");
292
+ const s = new Spinner();
293
+ s.start(t("cli.fetching_models"));
294
+ try {
295
+ const models = await provider.listModels();
296
+ s.stop();
297
+ if (models.length > 0) {
298
+ console.log(t("cli.available_models"));
299
+ const { getCertMark } = await import("../modules/certification/manifest");
300
+ for (const m of models) {
301
+ const marker = m === ctx.config.model ? pc.green("* ") : " ";
302
+ const mark = getCertMark(m, ctx.config.provider.baseUrl, version);
303
+ const cert = mark === "certified"
304
+ ? pc.green("✔")
305
+ : mark === "stale"
306
+ ? pc.yellow("○")
307
+ : pc.dim("·");
308
+ console.log(` ${marker}${cert} ${m}`);
309
+ }
310
+ }
311
+ else {
312
+ console.log(t("cli.no_models_found"));
313
+ }
314
+ }
315
+ catch (err) {
316
+ s.stop();
317
+ console.log(t("cli.model_fetch_failed", { error: String(err) }));
318
+ }
319
+ console.log(t("cli.model_hint"));
320
+ return;
321
+ }
322
+ if (subcmd === "use") {
323
+ const name = args[1];
324
+ if (!name) {
325
+ console.log(t("repl.model_usage"));
326
+ return;
327
+ }
328
+ ctx.config.model = name;
329
+ const configPath = join(homedir(), ".mma", "config.json");
330
+ saveConfig(ctx.config, configPath);
331
+ await ctx.agent.reconfigure(ctx.config);
332
+ console.log(pc.green(t("repl.model_set", { name })));
333
+ return;
334
+ }
335
+ console.log(t("repl.model_usage"));
336
+ },
337
+ });
338
+ ctx.registerCommand({
339
+ name: "context",
340
+ description: t("cli.manage_context"),
341
+ usage: "/context <size>",
342
+ action: async (args) => {
343
+ if (args.length === 0) {
344
+ console.log(`/context ${t("repl.context")} ${ctx.config.contextWindow}`);
345
+ console.log(`Usage: /context <size> (min 1024)`);
346
+ return;
347
+ }
348
+ const size = parseInt(args[0], 10);
349
+ if (isNaN(size) || size < 1024) {
350
+ console.log(t("cli.invalid_context_size"));
351
+ return;
352
+ }
353
+ ctx.config.contextWindow = size;
354
+ const configPath = join(homedir(), ".mma", "config.json");
355
+ saveConfig(ctx.config, configPath);
356
+ await ctx.agent.reconfigure(ctx.config);
357
+ console.log(pc.green(t("cli.context_set", { size })));
358
+ },
359
+ });
360
+ ctx.registerCommand({
361
+ name: "reload",
362
+ description: t("repl.reload"),
363
+ usage: t("repl.reload_usage"),
364
+ action: async () => {
365
+ console.log(pc.yellow(t("repl.reloading")));
366
+ if (ctx.sessionManager && ctx.config.session.autoSave) {
367
+ const active = ctx.sessionManager.getActiveMeta();
368
+ if (active) {
369
+ // Session is already auto-saved on each message
370
+ }
371
+ }
372
+ ctx.agent.shutdown();
373
+ const { loadConfig } = await import("../config/config");
374
+ const { homedir } = await import("os");
375
+ const { join } = await import("path");
376
+ const configDir = ctx.configDir;
377
+ const baseDir = ctx.baseDir;
378
+ const projectConfigPath = join(baseDir, ".mmrc");
379
+ const freshConfig = loadConfig({ configDir, projectConfigPath });
380
+ Object.assign(ctx.config, freshConfig);
381
+ const { bootstrap } = await import("../core/bootstrap");
382
+ const result = await bootstrap(configDir, baseDir, false, false);
383
+ ctx.agent = result.agent;
384
+ ctx.sessionManager = result.sessionManager;
385
+ ctx.skillsModule = result.skillsModule;
386
+ ctx.pluginManager = result.pluginManager;
387
+ ctx.setupCompleter();
388
+ console.log(pc.green(t("repl.reloaded")));
389
+ console.log(`${t("repl.model")} ${ctx.config.model}`);
390
+ console.log(`${t("repl.context")} ${ctx.config.contextWindow}`);
391
+ console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
392
+ },
393
+ });
394
+ }
395
+ function registerSessionCommands(ctx) {
396
+ if (!ctx.sessionManager)
397
+ return;
398
+ ctx.registerCommand({
399
+ name: "sessions",
400
+ description: t("repl.sessions"),
401
+ aliases: ["ls"],
402
+ usage: t("repl.sessions_usage"),
403
+ action: () => {
404
+ const sessions = ctx.sessionManager.list();
405
+ const active = ctx.sessionManager.getActive();
406
+ if (sessions.length === 0) {
407
+ console.log(t("session.no_sessions_hint"));
408
+ return;
409
+ }
410
+ const rows = sessions.map((s) => [
411
+ s.id === active ? pc.green("●") : "",
412
+ s.id.slice(0, 12),
413
+ s.name,
414
+ s.updatedAt.slice(0, 19).replace("T", " "),
415
+ String(s.messageCount),
416
+ ]);
417
+ for (const line of renderTable([
418
+ t("session.col_active"),
419
+ t("session.col_id"),
420
+ t("session.col_name"),
421
+ t("session.col_updated"),
422
+ t("session.col_msgs"),
423
+ ], rows)) {
424
+ console.log(line);
425
+ }
426
+ console.log(pc.dim(`\n ${t("repl.resume_hint")}`));
427
+ },
428
+ });
429
+ ctx.registerCommand({
430
+ name: "new",
431
+ description: t("repl.new"),
432
+ aliases: ["create"],
433
+ usage: t("repl.new_usage"),
434
+ action: (args) => {
435
+ const name = args.join(" ") || undefined;
436
+ const meta = ctx.sessionManager.create(name);
437
+ ctx.agent.clearContext();
438
+ console.clear();
439
+ console.log(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
440
+ console.log(pc.dim(` ${t("session.chat_cleared")}\n`));
441
+ },
442
+ });
443
+ ctx.registerCommand({
444
+ name: "resume",
445
+ description: t("repl.resume"),
446
+ aliases: ["switch", "use"],
447
+ usage: t("repl.resume_usage"),
448
+ action: (args) => {
449
+ const query = args.join(" ");
450
+ const sessions = ctx.sessionManager.list();
451
+ if (!query) {
452
+ console.log(pc.dim(t("session.available")));
453
+ const active = ctx.sessionManager.getActive();
454
+ for (const s of sessions) {
455
+ const marker = s.id === active ? pc.green(" *") : " ";
456
+ console.log(pc.dim(` ${marker} ${s.id.slice(0, 12)} ${s.name}`));
457
+ }
458
+ console.log(pc.dim(`\n ${t("repl.resume_usage")}`));
459
+ return;
460
+ }
461
+ const match = sessions.find((s) => s.id === query ||
462
+ s.id.startsWith(query) ||
463
+ s.name.toLowerCase().includes(query.toLowerCase()));
464
+ if (!match) {
465
+ console.log(t("session.no_match", { query }));
466
+ return;
467
+ }
468
+ ctx.sessionManager.setActive(match.id);
469
+ const history = ctx.sessionManager.loadHistory();
470
+ ctx.agent.setContext(history);
471
+ console.clear();
472
+ console.log(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
473
+ " " +
474
+ pc.dim(`(${match.id.slice(0, 12)})`) +
475
+ " — " +
476
+ match.messageCount +
477
+ " msgs");
478
+ console.log(pc.dim("─".repeat(50)));
479
+ if (history.length === 0) {
480
+ console.log(pc.dim(t("session.no_history")));
481
+ }
482
+ else {
483
+ console.log(pc.dim(t("session.chat_history")));
484
+ console.log();
485
+ for (const msg of history) {
486
+ if (msg.role === "user") {
487
+ console.log(pc.cyan(t("session.user_label") + ":"));
488
+ console.log(getMessageText(msg.content));
489
+ console.log();
490
+ }
491
+ else if (msg.role === "assistant") {
492
+ console.log(pc.green(t("session.assistant_label") + ":"));
493
+ console.log(getMessageText(msg.content));
494
+ console.log();
495
+ }
496
+ }
497
+ }
498
+ console.log(pc.dim("─".repeat(50)));
499
+ console.log(pc.dim(` ${t("session.chat_loaded")}`));
500
+ },
501
+ });
502
+ ctx.registerCommand({
503
+ name: "rename",
504
+ description: t("repl.rename"),
505
+ usage: t("repl.rename_usage"),
506
+ action: (args) => {
507
+ const name = args.join(" ");
508
+ if (!name) {
509
+ console.log(t("repl.rename_usage"));
510
+ return;
511
+ }
512
+ const active = ctx.sessionManager.getActive();
513
+ if (!active) {
514
+ console.log(t("session.no_active"));
515
+ return;
516
+ }
517
+ ctx.sessionManager.rename(active, name);
518
+ console.log(t("session.renamed", { name }));
519
+ },
520
+ });
521
+ ctx.registerCommand({
522
+ name: "delete",
523
+ description: t("repl.delete"),
524
+ aliases: ["rm"],
525
+ usage: t("repl.delete_usage"),
526
+ action: (args) => {
527
+ const query = args[0];
528
+ if (!query) {
529
+ console.log(t("repl.delete_usage"));
530
+ return;
531
+ }
532
+ const sessions = ctx.sessionManager.list();
533
+ const match = sessions.find((s) => s.id === query || s.id.startsWith(query));
534
+ if (!match) {
535
+ console.log(t("session.no_match", { query }));
536
+ return;
537
+ }
538
+ ctx.sessionManager.delete(match.id);
539
+ console.log(`${t("session.deleted", { id: match.id })}: ${match.name}`);
540
+ },
541
+ });
542
+ }
543
+ function registerSkillCommands(ctx) {
544
+ if (!ctx.skillsModule)
545
+ return;
546
+ ctx.registerCommand({
547
+ name: "skill",
548
+ description: t("repl.skill"),
549
+ usage: t("repl.skill_usage"),
550
+ action: (args) => {
551
+ const subcmd = args[0];
552
+ const arg = args.slice(1).join(" ");
553
+ if (!subcmd || subcmd === "list") {
554
+ const available = ctx.skillsModule.getAvailable();
555
+ if (available.length === 0) {
556
+ console.log(t("repl.no_skills"));
557
+ return;
558
+ }
559
+ console.log(pc.bold(t("repl.available_skills")));
560
+ for (const skill of available) {
561
+ const tokens = Math.ceil(skill.content.length / 4);
562
+ const loaded = ctx
563
+ .skillsModule.getLoaded()
564
+ .some((s) => s.name === skill.name);
565
+ const marker = loaded ? pc.green(" [loaded]") : "";
566
+ console.log(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
567
+ }
568
+ return;
569
+ }
570
+ if (subcmd === "loaded") {
571
+ const loaded = ctx.skillsModule.getLoaded();
572
+ const budget = ctx.skillsModule.getBudget();
573
+ if (loaded.length === 0) {
574
+ console.log(t("repl.no_loaded"));
575
+ return;
576
+ }
577
+ console.log(pc.bold(t("repl.loaded_skills")));
578
+ for (const skill of loaded) {
579
+ const tokens = Math.ceil(skill.content.length / 4);
580
+ console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
581
+ }
582
+ console.log(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
583
+ return;
584
+ }
585
+ if (subcmd === "load") {
586
+ if (!arg) {
587
+ console.log(t("repl.skill_load_usage"));
588
+ return;
589
+ }
590
+ const result = ctx.skillsModule.loadByName(arg);
591
+ if (result.success) {
592
+ console.log(pc.green(result.message));
593
+ }
594
+ else {
595
+ console.log(pc.red(result.message));
596
+ }
597
+ return;
598
+ }
599
+ if (subcmd === "unload") {
600
+ if (!arg) {
601
+ console.log(t("repl.skill_unload_usage"));
602
+ return;
603
+ }
604
+ if (ctx.skillsModule.unload(arg)) {
605
+ console.log(pc.green(t("repl.skill_unloaded", { name: arg })));
606
+ }
607
+ else {
608
+ console.log(pc.red(t("repl.skill_not_loaded", { name: arg })));
609
+ }
610
+ return;
611
+ }
612
+ if (subcmd === "search") {
613
+ if (!arg) {
614
+ console.log(t("repl.skill_search_usage"));
615
+ return;
616
+ }
617
+ const results = ctx.skillsModule.search(arg);
618
+ if (results.length === 0) {
619
+ console.log(t("repl.no_skill_match", { query: arg }));
620
+ return;
621
+ }
622
+ console.log(pc.bold(t("repl.skills_matching", { query: arg })));
623
+ for (const skill of results) {
624
+ const tokens = Math.ceil(skill.content.length / 4);
625
+ console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
626
+ }
627
+ return;
628
+ }
629
+ console.log(pc.red(t("repl.skill_unknown_sub", { subcmd })));
630
+ console.log(t("repl.skill_usage"));
631
+ },
632
+ });
633
+ }