micro-models-agent 1.5.0 → 1.6.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.6.0] - 2026-09-22
8
+
9
+ ### Added
10
+
11
+ - **Tab completion descriptions & navigation** (`src/cli/completer.ts`, `src/ui/line-editor.ts`): completions carry descriptions, file-path completion is registered in the REPL, arrow keys/Tab cycle candidates, Esc restores.
12
+ - **`redactConfigSecrets`** (`src/config/redact.ts`): deep secret redaction for config display.
13
+
14
+ ### Fixed
15
+
16
+ - **Security-paths wizard answer** (`src/cli/setup.ts`, `src/cli/setup-order.ts`): the question was inverted and a no-op; reworded to restrict semantics and `security.paths.enabled` is now applied.
17
+ - **First-run wizard suppression** (`src/config/config.ts`, `src/cli/main.ts`): `loadConfig` no longer persists default config on first load; one-shot runs warn instead of silently using defaults.
18
+ - **`/wizard` dropped security answers and ignored `provider.entries[]`** (`src/cli/repl-commands.ts`, `src/cli/setup-order.ts`): applies answers centrally and updates the active entry.
19
+ - **`mma config show` leaked secrets** (`src/cli/commands.ts`): secrets redacted by default, `--show-secrets` opts out.
20
+
21
+ ### Changed
22
+
23
+ - **Chat UI: tool-label accent & separators** (`src/ui/theme.ts`, `src/ui/renderer.ts`, `docs/design/ui/STYLE.md`): the action glyph and the localized tool label now share the category color (`toolAccent`), and a dim `·` separates a status label from its content (reasoning label from reasoning text, tool label from arguments) — the rotating status verb no longer blurs into the model's own words. `Ctrl+O` reprint follows the same accent.
24
+
7
25
  ## [1.5.0] - 2026-09-21
8
26
 
9
27
  ### Added
package/dist/i18n/en.json CHANGED
@@ -273,7 +273,7 @@
273
273
  "cli.config_key": "Config key (e.g., model, provider.baseUrl)",
274
274
  "cli.set_value": "Set a config value",
275
275
  "cli.set_done": "Set {key}={value}",
276
- "cli.show_config": "Show current config",
276
+ "cli.show_config": "Show the effective configuration (secrets redacted; use --show-secrets to reveal)",
277
277
  "cli.migrate_config": "Migrate legacy config.json to domain format",
278
278
  "cli.manage_models": "Manage models",
279
279
  "cli.list_models": "List available models",
@@ -348,6 +348,7 @@
348
348
  "cli.show_details": "Show session details",
349
349
  "cli.delete_session": "Delete a session",
350
350
  "cli.first_run": "First run detected. Running setup wizard...",
351
+ "cli.first_run_oneshot": "No configuration found — running with defaults. Run `mma` (no arguments) once to set up the wizard.",
351
352
  "cli.setup_saved_restart": "Settings saved. Restarting with the new configuration...",
352
353
  "cli.unknown_cmd": "Unknown command: {name}.",
353
354
  "cli.unknown_command": "Unknown command \"{input}\". Did you mean \"{suggestion}\"?",
@@ -558,7 +559,7 @@
558
559
  "setup.security_configure": " Configure security? (y/N)",
559
560
  "setup.security_bash_block": " Block dangerous commands (rm, sudo, ssh, etc.)? (y/N)",
560
561
  "setup.security_flags_block": " Block dangerous flags (--force, -rf, etc.)? (y/N)",
561
- "setup.security_paths_deny": " Allow access to .git/, .env, node_modules/? (y/N)",
562
+ "setup.security_paths_restrict": " Restrict access to .git/, .env, node_modules/? (Y/n)",
562
563
  "skill.already_loaded": "Skill \"{name}\" is already loaded",
563
564
  "skill.not_found": "Skill \"{name}\" not found in any source directory",
564
565
  "skill.no_match": "No skills match for: {task}",
package/dist/i18n/ru.json CHANGED
@@ -271,7 +271,7 @@
271
271
  "cli.config_key": "Ключ конфига (напр., model, provider.baseUrl)",
272
272
  "cli.set_value": "Установить значение",
273
273
  "cli.set_done": "Установлено {key}={value}",
274
- "cli.show_config": "Показать текущий конфиг",
274
+ "cli.show_config": "Показать действующую конфигурацию (секреты скрыты; используйте --show-secrets)",
275
275
  "cli.migrate_config": "Мигрировать legacy config.json в domain-формат",
276
276
  "cli.manage_models": "Управление моделями",
277
277
  "cli.list_models": "Список доступных моделей",
@@ -342,6 +342,7 @@
342
342
  "cli.show_details": "Показать детали сессии",
343
343
  "cli.delete_session": "Удалить сессию",
344
344
  "cli.first_run": "Первый запуск. Запускаем мастер настройки...",
345
+ "cli.first_run_oneshot": "Конфигурация не найдена — запуск с настройками по умолчанию. Запустите `mma` без аргументов один раз для настройки.",
345
346
  "cli.setup_saved_restart": "Настройки сохранены. Перезапуск с новой конфигурацией...",
346
347
  "cli.unknown_cmd": "Неизвестная команда: {name}.",
347
348
  "cli.unknown_command": "Неизвестная команда \"{input}\". Возможно, вы имели в виду \"{suggestion}\"?",
@@ -556,7 +557,7 @@
556
557
  "setup.security_configure": " Настроить безопасность? (y/N)",
557
558
  "setup.security_bash_block": " Блокировать опасные команды (rm, sudo, ssh и т.д.)? (y/N)",
558
559
  "setup.security_flags_block": " Блокировать опасные флаги (--force, -rf и т.д.)? (y/N)",
559
- "setup.security_paths_deny": " Разрешить доступ к .git/, .env, node_modules/? (y/N)",
560
+ "setup.security_paths_restrict": " Ограничить доступ к .git/, .env, node_modules/? (Y/n)",
560
561
  "skill.already_loaded": "Скилл \"{name}\" уже загружен",
561
562
  "skill.not_found": "Скилл \"{name}\" не найден ни в одном каталоге",
562
563
  "skill.no_match": "Нет скиллов подходящих для: {task}",
package/dist/main.js CHANGED
@@ -2729,7 +2729,7 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2729
2729
  "cli.config_key": "Config key (e.g., model, provider.baseUrl)",
2730
2730
  "cli.set_value": "Set a config value",
2731
2731
  "cli.set_done": "Set {key}={value}",
2732
- "cli.show_config": "Show current config",
2732
+ "cli.show_config": "Show the effective configuration (secrets redacted; use --show-secrets to reveal)",
2733
2733
  "cli.migrate_config": "Migrate legacy config.json to domain format",
2734
2734
  "cli.manage_models": "Manage models",
2735
2735
  "cli.list_models": "List available models",
@@ -2804,6 +2804,7 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2804
2804
  "cli.show_details": "Show session details",
2805
2805
  "cli.delete_session": "Delete a session",
2806
2806
  "cli.first_run": "First run detected. Running setup wizard...",
2807
+ "cli.first_run_oneshot": "No configuration found — running with defaults. Run `mma` (no arguments) once to set up the wizard.",
2807
2808
  "cli.setup_saved_restart": "Settings saved. Restarting with the new configuration...",
2808
2809
  "cli.unknown_cmd": "Unknown command: {name}.",
2809
2810
  "cli.unknown_command": 'Unknown command "{input}". Did you mean "{suggestion}"?',
@@ -3028,7 +3029,7 @@ Excluded blocks: {count}`,
3028
3029
  "setup.security_configure": " Configure security? (y/N)",
3029
3030
  "setup.security_bash_block": " Block dangerous commands (rm, sudo, ssh, etc.)? (y/N)",
3030
3031
  "setup.security_flags_block": " Block dangerous flags (--force, -rf, etc.)? (y/N)",
3031
- "setup.security_paths_deny": " Allow access to .git/, .env, node_modules/? (y/N)",
3032
+ "setup.security_paths_restrict": " Restrict access to .git/, .env, node_modules/? (Y/n)",
3032
3033
  "skill.already_loaded": 'Skill "{name}" is already loaded',
3033
3034
  "skill.not_found": 'Skill "{name}" not found in any source directory',
3034
3035
  "skill.no_match": "No skills match for: {task}",
@@ -3605,7 +3606,7 @@ var init_ru = __esm(() => {
3605
3606
  "cli.config_key": "Ключ конфига (напр., model, provider.baseUrl)",
3606
3607
  "cli.set_value": "Установить значение",
3607
3608
  "cli.set_done": "Установлено {key}={value}",
3608
- "cli.show_config": "Показать текущий конфиг",
3609
+ "cli.show_config": "Показать действующую конфигурацию (секреты скрыты; используйте --show-secrets)",
3609
3610
  "cli.migrate_config": "Мигрировать legacy config.json в domain-формат",
3610
3611
  "cli.manage_models": "Управление моделями",
3611
3612
  "cli.list_models": "Список доступных моделей",
@@ -3676,6 +3677,7 @@ var init_ru = __esm(() => {
3676
3677
  "cli.show_details": "Показать детали сессии",
3677
3678
  "cli.delete_session": "Удалить сессию",
3678
3679
  "cli.first_run": "Первый запуск. Запускаем мастер настройки...",
3680
+ "cli.first_run_oneshot": "Конфигурация не найдена — запуск с настройками по умолчанию. Запустите `mma` без аргументов один раз для настройки.",
3679
3681
  "cli.setup_saved_restart": "Настройки сохранены. Перезапуск с новой конфигурацией...",
3680
3682
  "cli.unknown_cmd": "Неизвестная команда: {name}.",
3681
3683
  "cli.unknown_command": 'Неизвестная команда "{input}". Возможно, вы имели в виду "{suggestion}"?',
@@ -3904,7 +3906,7 @@ var init_ru = __esm(() => {
3904
3906
  "setup.security_configure": " Настроить безопасность? (y/N)",
3905
3907
  "setup.security_bash_block": " Блокировать опасные команды (rm, sudo, ssh и т.д.)? (y/N)",
3906
3908
  "setup.security_flags_block": " Блокировать опасные флаги (--force, -rf и т.д.)? (y/N)",
3907
- "setup.security_paths_deny": " Разрешить доступ к .git/, .env, node_modules/? (y/N)",
3909
+ "setup.security_paths_restrict": " Ограничить доступ к .git/, .env, node_modules/? (Y/n)",
3908
3910
  "skill.already_loaded": 'Скилл "{name}" уже загружен',
3909
3911
  "skill.not_found": 'Скилл "{name}" не найден ни в одном каталоге',
3910
3912
  "skill.no_match": "Нет скиллов подходящих для: {task}",
@@ -5324,9 +5326,6 @@ function loadConfig(options) {
5324
5326
  config = deepMerge(config, domainConfig);
5325
5327
  legacyDetected = false;
5326
5328
  }
5327
- if (!globalData && !domainConfig) {
5328
- saveDomainFiles(config, options.configDir);
5329
- }
5330
5329
  const projectData = loadJSON(options.projectConfigPath);
5331
5330
  if (projectData) {
5332
5331
  config = deepMerge(config, projectData);
@@ -29592,11 +29591,12 @@ async function runSetup(externalRl) {
29592
29591
  const configureSecurity = await ask(rl, t("setup.security_configure"), "n");
29593
29592
  let securityBashBlock = false;
29594
29593
  let securityFlagsBlock = false;
29595
- let securityPathsDeny = true;
29594
+ let securityPathsRestrict = false;
29596
29595
  if (configureSecurity.toLowerCase() === "y" || configureSecurity.toLowerCase() === "yes") {
29597
29596
  securityBashBlock = (await ask(rl, t("setup.security_bash_block"), "n")).toLowerCase() === "y";
29598
29597
  securityFlagsBlock = (await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() === "y";
29599
- securityPathsDeny = (await ask(rl, t("setup.security_paths_deny"), "Y")).toLowerCase() !== "n";
29598
+ const pathsAnswer = (await ask(rl, t("setup.security_paths_restrict"), "Y")).toLowerCase();
29599
+ securityPathsRestrict = pathsAnswer !== "n" && pathsAnswer !== "no";
29600
29600
  }
29601
29601
  if (ownRl) {
29602
29602
  rl.close();
@@ -29611,7 +29611,7 @@ async function runSetup(externalRl) {
29611
29611
  locale,
29612
29612
  securityBashBlock,
29613
29613
  securityFlagsBlock,
29614
- securityPathsDeny
29614
+ securityPathsRestrict
29615
29615
  };
29616
29616
  getDefaultChannel().writeLine(pc.green(pc.bold(t("setup.complete"))));
29617
29617
  const summary = [
@@ -29652,6 +29652,73 @@ var init_setup = __esm(() => {
29652
29652
  ];
29653
29653
  });
29654
29654
 
29655
+ // src/cli/setup-order.ts
29656
+ function applyAnswersToConfig(config, a) {
29657
+ config.provider = {
29658
+ ...config.provider,
29659
+ type: a.provider,
29660
+ baseUrl: a.apiBase,
29661
+ apiKey: a.apiKey
29662
+ };
29663
+ config.model = a.model;
29664
+ config.contextWindow = a.contextWindow;
29665
+ config.maxToolIterations = a.maxToolIterations;
29666
+ config.locale = a.locale;
29667
+ if ((config.provider.entries?.length ?? 0) > 0) {
29668
+ const entries = config.provider.entries ?? [];
29669
+ const activeName = config.provider.active || entries[0]?.label || entries[0]?.type;
29670
+ const idx = entries.findIndex((e) => e.label === activeName || e.type === activeName);
29671
+ const target = idx >= 0 ? idx : 0;
29672
+ const current = entries[target];
29673
+ const keepLabel = Boolean(current.label && current.label !== current.type);
29674
+ const updated = {
29675
+ ...current,
29676
+ type: a.provider,
29677
+ baseUrl: a.apiBase,
29678
+ apiKey: a.apiKey,
29679
+ contextWindow: a.contextWindow,
29680
+ ...keepLabel ? {} : { label: a.provider }
29681
+ };
29682
+ const next = entries.slice();
29683
+ next[target] = updated;
29684
+ config.provider.entries = next;
29685
+ config.provider.active = updated.label || updated.type;
29686
+ }
29687
+ if (config.security) {
29688
+ config.security.enabled = true;
29689
+ config.security.bash.enabled = true;
29690
+ config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
29691
+ if (a.securityBashBlock) {
29692
+ config.security.bash.blacklist = [
29693
+ ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
29694
+ ];
29695
+ }
29696
+ config.security.paths.enabled = a.securityPathsRestrict;
29697
+ }
29698
+ return config;
29699
+ }
29700
+ var SECURITY_BASH_BLOCKLIST;
29701
+ var init_setup_order = __esm(() => {
29702
+ SECURITY_BASH_BLOCKLIST = [
29703
+ "rm",
29704
+ "dd",
29705
+ "chmod",
29706
+ "wget",
29707
+ "curl",
29708
+ "scp",
29709
+ "ssh",
29710
+ "nc",
29711
+ "netcat",
29712
+ "sudo",
29713
+ "su",
29714
+ "kill",
29715
+ "pkill",
29716
+ "killall",
29717
+ "shutdown",
29718
+ "reboot"
29719
+ ];
29720
+ });
29721
+
29655
29722
  // src/modules/indexer/map-command.ts
29656
29723
  async function runMapAction(module, action, query) {
29657
29724
  const summary = async () => {
@@ -37947,7 +38014,7 @@ __export(exports_repl_commands, {
37947
38014
  registerAllCommands: () => registerAllCommands,
37948
38015
  COMMAND_GROUPS: () => COMMAND_GROUPS
37949
38016
  });
37950
- import { join as join54, dirname as dirname25 } from "path";
38017
+ import { join as join55, dirname as dirname25 } from "path";
37951
38018
  import { existsSync as existsSync60 } from "fs";
37952
38019
  function registerAllCommands(ctx) {
37953
38020
  registerBuiltinCommands(ctx);
@@ -38148,14 +38215,8 @@ function registerConfigCommands(ctx) {
38148
38215
  ctx.output.writeLine(pc.yellow(t("repl.wizard_running")));
38149
38216
  await ctx.withExclusiveInput(async () => {
38150
38217
  const answers = await runSetup(ctx.rl);
38151
- const configPath = join54(ctx.configDir, "config.json");
38152
- ctx.config.provider.type = answers.provider;
38153
- ctx.config.provider.baseUrl = answers.apiBase;
38154
- ctx.config.provider.apiKey = answers.apiKey;
38155
- ctx.config.model = answers.model;
38156
- ctx.config.contextWindow = answers.contextWindow;
38157
- ctx.config.maxToolIterations = answers.maxToolIterations;
38158
- ctx.config.locale = answers.locale;
38218
+ const configPath = join55(ctx.configDir, "config.json");
38219
+ applyAnswersToConfig(ctx.config, answers);
38159
38220
  saveConfig(ctx.config, configPath, dirname25(configPath));
38160
38221
  await ctx.agent.reconfigure(ctx.config);
38161
38222
  ctx.output.writeLine(pc.green(t("cli.config_saved")));
@@ -38279,7 +38340,7 @@ function registerProviderCommands(ctx) {
38279
38340
  });
38280
38341
  ctx.config.provider.entries = entries;
38281
38342
  ctx.config.provider.active = ctx.config.provider.active || ctx.config.provider.type;
38282
- const configPath = join54(ctx.configDir, "config.json");
38343
+ const configPath = join55(ctx.configDir, "config.json");
38283
38344
  saveConfig(ctx.config, configPath, dirname25(configPath));
38284
38345
  await ctx.agent.reconfigure(ctx.config);
38285
38346
  ctx.output.writeLine(pc.green(t("cli.provider_added", { name })));
@@ -38346,7 +38407,7 @@ function registerProviderCommands(ctx) {
38346
38407
  return;
38347
38408
  }
38348
38409
  ctx.config.model = name;
38349
- const configPath = join54(ctx.configDir, "config.json");
38410
+ const configPath = join55(ctx.configDir, "config.json");
38350
38411
  saveConfig(ctx.config, configPath, dirname25(configPath));
38351
38412
  await ctx.agent.reconfigure(ctx.config);
38352
38413
  ctx.output.writeLine(pc.green(t("repl.model_set", { name })));
@@ -38375,7 +38436,7 @@ function registerProviderCommands(ctx) {
38375
38436
  usage: t("repl.context_usage"),
38376
38437
  action: async (args) => {
38377
38438
  const save = async () => {
38378
- const configPath = join54(ctx.configDir, "config.json");
38439
+ const configPath = join55(ctx.configDir, "config.json");
38379
38440
  saveConfig(ctx.config, configPath, dirname25(configPath));
38380
38441
  await ctx.agent.reconfigure(ctx.config);
38381
38442
  };
@@ -38768,11 +38829,11 @@ function registerSkillCommands(ctx) {
38768
38829
  });
38769
38830
  }
38770
38831
  async function runConfigMigrate(ctx) {
38771
- const { hasDomainFiles: hasDomainFiles3 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
38832
+ const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
38772
38833
  const { loadConfig: loadCfg } = await Promise.resolve().then(() => (init_config2(), exports_config));
38773
38834
  const configDir = ctx.configDir;
38774
- const configPath = join54(configDir, "config.json");
38775
- if (hasDomainFiles3(configDir)) {
38835
+ const configPath = join55(configDir, "config.json");
38836
+ if (hasDomainFiles2(configDir)) {
38776
38837
  ctx.output.writeLine(pc.yellow(t("config.migrate_no_legacy")));
38777
38838
  return;
38778
38839
  }
@@ -38781,11 +38842,11 @@ async function runConfigMigrate(ctx) {
38781
38842
  return;
38782
38843
  }
38783
38844
  ctx.output.writeLine(t("config.migrate_start"));
38784
- const { config } = loadCfg({ configDir, projectConfigPath: join54(configDir, ".mmrc") });
38845
+ const { config } = loadCfg({ configDir, projectConfigPath: join55(configDir, ".mmrc") });
38785
38846
  saveConfig(config, configPath, configDir);
38786
- const { renameSync: renameSync4, readdirSync: readdirSync19 } = await import("fs");
38847
+ const { renameSync: renameSync4, readdirSync: readdirSync20 } = await import("fs");
38787
38848
  renameSync4(configPath, configPath + ".bak");
38788
- const domainFiles = readdirSync19(join54(configDir, "config")).filter((f) => f.endsWith(".json"));
38849
+ const domainFiles = readdirSync20(join55(configDir, "config")).filter((f) => f.endsWith(".json"));
38789
38850
  ctx.output.writeLine(pc.green(t("config.migrate_done", { count: String(domainFiles.length) })));
38790
38851
  }
38791
38852
  var version2, COMMAND_GROUPS;
@@ -38795,6 +38856,7 @@ var init_repl_commands = __esm(() => {
38795
38856
  init_table();
38796
38857
  init_i18n();
38797
38858
  init_setup();
38859
+ init_setup_order();
38798
38860
  init_config2();
38799
38861
  init_presets();
38800
38862
  init_budget();
@@ -38850,6 +38912,7 @@ var {
38850
38912
  init_bootstrap();
38851
38913
  init_config2();
38852
38914
  init_setup();
38915
+ init_setup_order();
38853
38916
  init_i18n();
38854
38917
  init_colors();
38855
38918
  import { join as join53, dirname as dirname24 } from "path";
@@ -39363,7 +39426,7 @@ function createSecurityCommand(program2) {
39363
39426
  const auditNotifier = security.auditNotifier || {};
39364
39427
  getDefaultChannel().writeLine(t("cli.security.current_policy"));
39365
39428
  getDefaultChannel().writeLine(` ${t("cli.security.bash_enabled")}: ${bash.blacklist?.length > 0 ? t("cli.yes") : t("cli.no")}`);
39366
- getDefaultChannel().writeLine(` ${t("cli.security.path_validation")}: ${paths.denied?.length > 0 ? t("cli.yes") : t("cli.no")}`);
39429
+ getDefaultChannel().writeLine(` ${t("cli.security.path_validation")}: ${paths.enabled ? t("cli.yes") : t("cli.no")}`);
39367
39430
  getDefaultChannel().writeLine(` ${t("cli.security.network_validation")}: ${network.deniedDomains?.length > 0 || network.allowedDomains?.length > 0 ? t("cli.yes") : t("cli.no")}`);
39368
39431
  getDefaultChannel().writeLine(` ${t("cli.security.content_scanning")}: ${contentScan.enabled ? t("cli.yes") : t("cli.no")}`);
39369
39432
  getDefaultChannel().writeLine(` ${t("cli.security.max_recursion")}: ${security.maxRecursionDepth ?? 3}`);
@@ -39671,50 +39734,60 @@ function extractLatestChangelog(changelog) {
39671
39734
 
39672
39735
  // src/cli/commands.ts
39673
39736
  init_output();
39737
+
39738
+ // src/config/redact.ts
39739
+ var SECRET_KEYS = new Set([
39740
+ "apiKey",
39741
+ "api_key",
39742
+ "apikey",
39743
+ "token",
39744
+ "tokens",
39745
+ "secret",
39746
+ "password",
39747
+ "authorization",
39748
+ "webhookUrl"
39749
+ ]);
39750
+ var SECRET_MAPS = new Set(["headers", "env"]);
39751
+ var MASK = "***";
39752
+ function walk(value, parentKey, depth = 0) {
39753
+ if (depth > 100)
39754
+ return value;
39755
+ if (typeof value === "string") {
39756
+ return parentKey && SECRET_KEYS.has(parentKey) ? MASK : value;
39757
+ }
39758
+ if (Array.isArray(value)) {
39759
+ return value.map((v) => walk(v, parentKey, depth + 1));
39760
+ }
39761
+ if (value && typeof value === "object" && !(value instanceof RegExp)) {
39762
+ if (parentKey && SECRET_MAPS.has(parentKey)) {
39763
+ const masked = {};
39764
+ for (const k of Object.keys(value))
39765
+ masked[k] = MASK;
39766
+ return masked;
39767
+ }
39768
+ const out = {};
39769
+ for (const [k, v] of Object.entries(value)) {
39770
+ out[k] = walk(v, k, depth + 1);
39771
+ }
39772
+ return out;
39773
+ }
39774
+ return value;
39775
+ }
39776
+ function redactConfigSecrets(config) {
39777
+ return walk(config);
39778
+ }
39779
+
39780
+ // src/cli/commands.ts
39674
39781
  var version = readMmaVersion();
39782
+ function configForDisplay(config, showSecrets) {
39783
+ return showSecrets ? config : redactConfigSecrets(config);
39784
+ }
39675
39785
  function buildInitCommand(program2) {
39676
39786
  program2.command("init").description(t("cli.init")).action(async () => {
39677
39787
  const answers = await runSetup();
39678
39788
  const configPath = join53(homedir17(), ".mma", "config.json");
39679
39789
  const { config } = await bootstrap();
39680
- config.provider.type = answers.provider;
39681
- config.provider.baseUrl = answers.apiBase;
39682
- config.provider.apiKey = answers.apiKey;
39683
- config.model = answers.model;
39684
- config.contextWindow = answers.contextWindow;
39685
- config.maxToolIterations = answers.maxToolIterations;
39686
- config.locale = answers.locale;
39687
- if (config.security) {
39688
- config.security.enabled = true;
39689
- config.security.bash.enabled = true;
39690
- config.security.bash.blockDangerousFlags = answers.securityFlagsBlock || config.security.bash.blockDangerousFlags;
39691
- if (answers.securityBashBlock) {
39692
- config.security.bash.blacklist = [
39693
- ...new Set([
39694
- ...config.security.bash.blacklist,
39695
- "rm",
39696
- "dd",
39697
- "chmod",
39698
- "wget",
39699
- "curl",
39700
- "scp",
39701
- "ssh",
39702
- "nc",
39703
- "netcat",
39704
- "sudo",
39705
- "su",
39706
- "kill",
39707
- "pkill",
39708
- "killall",
39709
- "shutdown",
39710
- "reboot"
39711
- ])
39712
- ];
39713
- }
39714
- if (!answers.securityPathsDeny) {
39715
- config.security.paths.denied = [];
39716
- }
39717
- }
39790
+ applyAnswersToConfig(config, answers);
39718
39791
  saveConfig(config, configPath, dirname24(configPath));
39719
39792
  getDefaultChannel().writeLine(t("cli.config_saved"));
39720
39793
  });
@@ -39745,16 +39818,16 @@ function buildConfigCommands(program2) {
39745
39818
  saveConfig(config, configPath, dirname24(configPath));
39746
39819
  getDefaultChannel().writeLine(t("cli.set_done", { key, value }));
39747
39820
  });
39748
- configCmd.command("show").description(t("cli.show_config")).action(async () => {
39821
+ configCmd.command("show").description(t("cli.show_config")).option("--show-secrets", "Print secrets (API keys, tokens) unredacted").action(async (opts) => {
39749
39822
  const { config } = await bootstrap();
39750
- writeMachineJson(config);
39823
+ writeMachineJson(configForDisplay(config, opts.showSecrets));
39751
39824
  });
39752
39825
  configCmd.command("migrate").description(t("cli.migrate_config")).action(async () => {
39753
39826
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
39754
- const { hasDomainFiles: hasDomainFiles3 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
39827
+ const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
39755
39828
  const configDir = join53(homedir17(), ".mma");
39756
39829
  const configPath = join53(configDir, "config.json");
39757
- if (hasDomainFiles3(configDir)) {
39830
+ if (hasDomainFiles2(configDir)) {
39758
39831
  getDefaultChannel().writeLine(pc.yellow(t("config.migrate_no_legacy")));
39759
39832
  return;
39760
39833
  }
@@ -40176,6 +40249,7 @@ import * as readline2 from "readline";
40176
40249
 
40177
40250
  // src/ui/line-editor.ts
40178
40251
  init_string_width();
40252
+ init_colors();
40179
40253
  import * as readline from "readline";
40180
40254
 
40181
40255
  // src/ui/line-math.ts
@@ -40288,6 +40362,7 @@ class LineEditor {
40288
40362
  pasteLastWasCR = false;
40289
40363
  lastWasCR = false;
40290
40364
  questionCb = null;
40365
+ completionSession = null;
40291
40366
  rawMode = false;
40292
40367
  listeners = {};
40293
40368
  dsrDataHandler;
@@ -40421,6 +40496,7 @@ class LineEditor {
40421
40496
  this.col = 0;
40422
40497
  this.historyIndex = -1;
40423
40498
  this.stash = null;
40499
+ this.completionSession = null;
40424
40500
  this.invalidateAnchor();
40425
40501
  if (this.input.isTTY)
40426
40502
  this.render();
@@ -40431,6 +40507,7 @@ class LineEditor {
40431
40507
  this.lines = [""];
40432
40508
  this.row = 0;
40433
40509
  this.col = 0;
40510
+ this.completionSession = null;
40434
40511
  this.invalidateAnchor();
40435
40512
  if (this.input.isTTY)
40436
40513
  this.render();
@@ -40496,6 +40573,27 @@ class LineEditor {
40496
40573
  }
40497
40574
  return;
40498
40575
  }
40576
+ if (this.completionSession) {
40577
+ const ck = key;
40578
+ const plain = !ck.ctrl && !ck.meta;
40579
+ if (ck?.name === "down" && plain) {
40580
+ this.cycleCompletion(1);
40581
+ return;
40582
+ }
40583
+ if (ck?.name === "up" && plain) {
40584
+ this.cycleCompletion(-1);
40585
+ return;
40586
+ }
40587
+ if (ck?.name === "tab") {
40588
+ this.cycleCompletion(1);
40589
+ return;
40590
+ }
40591
+ if (ck?.name === "escape") {
40592
+ this.cancelCompletion();
40593
+ return;
40594
+ }
40595
+ this.completionSession = null;
40596
+ }
40499
40597
  if (this.onKeyInput && this.onKeyInput(str, key))
40500
40598
  return;
40501
40599
  const k = key;
@@ -40674,6 +40772,7 @@ class LineEditor {
40674
40772
  }
40675
40773
  }
40676
40774
  submit() {
40775
+ this.completionSession = null;
40677
40776
  if (this.questionCb) {
40678
40777
  const cb = this.questionCb;
40679
40778
  this.questionCb = null;
@@ -40729,6 +40828,7 @@ class LineEditor {
40729
40828
  this.lines = [""];
40730
40829
  this.row = 0;
40731
40830
  this.col = 0;
40831
+ this.completionSession = null;
40732
40832
  this.invalidateAnchor();
40733
40833
  this.render();
40734
40834
  if (this.onInterrupt) {
@@ -40753,6 +40853,7 @@ class LineEditor {
40753
40853
  this.lines[this.row] = chars.join("");
40754
40854
  }
40755
40855
  insertText(text) {
40856
+ this.completionSession = null;
40756
40857
  const sanitized = text.replace(/\r\n/g, `
40757
40858
  `).replace(/\r/g, "");
40758
40859
  const chars = this.currentChars();
@@ -40967,6 +41068,7 @@ class LineEditor {
40967
41068
  }
40968
41069
  }
40969
41070
  applyHistoryEntry() {
41071
+ this.completionSession = null;
40970
41072
  const entry = this.history[this.history.length - 1 - this.historyIndex];
40971
41073
  this.lines = entry.split(`
40972
41074
  `);
@@ -40976,6 +41078,49 @@ class LineEditor {
40976
41078
  this.col = charLen(this.lines[this.row]);
40977
41079
  this.render();
40978
41080
  }
41081
+ replaceCompletionToken(from, removeText, insertText) {
41082
+ const line = this.lines[this.row];
41083
+ const head = line.slice(0, from);
41084
+ const tail = line.slice(from + removeText.length);
41085
+ this.lines[this.row] = head + insertText + tail;
41086
+ this.col = charLen(head) + charLen(insertText);
41087
+ }
41088
+ partialStart(line, partial) {
41089
+ const chars = Array.from(line);
41090
+ const beforeCursor = chars.slice(0, this.col).join("");
41091
+ let from = beforeCursor.lastIndexOf(partial);
41092
+ if (from === -1)
41093
+ from = line.lastIndexOf(partial);
41094
+ return from;
41095
+ }
41096
+ applyCompletionCandidate(index) {
41097
+ const s = this.completionSession;
41098
+ if (!s)
41099
+ return;
41100
+ const chosen = s.items[index].value;
41101
+ this.replaceCompletionToken(s.from, s.inserted, chosen);
41102
+ s.index = index;
41103
+ s.inserted = chosen;
41104
+ this.render();
41105
+ }
41106
+ cycleCompletion(delta) {
41107
+ const s = this.completionSession;
41108
+ if (!s)
41109
+ return;
41110
+ const n = s.items.length;
41111
+ if (n === 0)
41112
+ return;
41113
+ const next = (s.index + delta + n) % n;
41114
+ this.applyCompletionCandidate(next);
41115
+ }
41116
+ cancelCompletion() {
41117
+ const s = this.completionSession;
41118
+ if (!s)
41119
+ return;
41120
+ this.replaceCompletionToken(s.from, s.inserted, s.original);
41121
+ this.completionSession = null;
41122
+ this.render();
41123
+ }
40979
41124
  tabComplete() {
40980
41125
  if (!this.completer)
40981
41126
  return;
@@ -40986,28 +41131,53 @@ class LineEditor {
40986
41131
  return;
40987
41132
  }
40988
41133
  if (matches.length === 1) {
40989
- const chars = Array.from(line);
40990
- const beforeCursor = chars.slice(0, this.col).join("");
40991
- let from = beforeCursor.lastIndexOf(partial);
40992
- if (from === -1)
40993
- from = line.lastIndexOf(partial);
40994
- if (from === -1) {
41134
+ const from2 = this.partialStart(line, partial);
41135
+ if (from2 === -1) {
40995
41136
  this.output.write("\x07");
40996
41137
  return;
40997
41138
  }
40998
- const head = line.slice(0, from);
40999
- const tail = line.slice(from + partial.length);
41000
- this.lines[this.row] = head + matches[0] + tail;
41001
- this.col = charLen(head) + charLen(matches[0]);
41139
+ this.replaceCompletionToken(from2, partial, matches[0].value);
41002
41140
  this.render();
41003
41141
  return;
41004
41142
  }
41005
- const rows = matches.map((m) => " " + m);
41143
+ const from = this.partialStart(line, partial);
41144
+ if (from === -1) {
41145
+ this.output.write("\x07");
41146
+ return;
41147
+ }
41148
+ const width = this.columns();
41149
+ const limit = width - 1;
41150
+ const rows = matches.map((m) => {
41151
+ const plain = m.description ? ` ${m.value} ${m.description}` : ` ${m.value}`;
41152
+ if (limit <= 0)
41153
+ return "";
41154
+ if (stringWidth(plain) <= limit) {
41155
+ return m.description ? ` ${m.value} ${pc.dim(m.description)}` : ` ${m.value}`;
41156
+ }
41157
+ let acc = "";
41158
+ let w = 0;
41159
+ for (const ch of plain) {
41160
+ const cw = stringWidth(ch);
41161
+ if (w + cw > limit - 1)
41162
+ break;
41163
+ acc += ch;
41164
+ w += cw;
41165
+ }
41166
+ return acc + "…";
41167
+ });
41006
41168
  this.output.write(`\r
41007
41169
  ` + rows.join(`\r
41008
41170
  `) + `\r
41009
41171
  `);
41010
41172
  this.prevCursorRow += rows.length + 1;
41173
+ this.completionSession = {
41174
+ items: matches,
41175
+ index: 0,
41176
+ from,
41177
+ original: partial,
41178
+ inserted: matches[0].value
41179
+ };
41180
+ this.replaceCompletionToken(from, partial, matches[0].value);
41011
41181
  this.render();
41012
41182
  }
41013
41183
  clearScreen() {
@@ -41144,10 +41314,19 @@ class LineEditor {
41144
41314
 
41145
41315
  // src/cli/repl.ts
41146
41316
  import { existsSync as existsSync61, mkdirSync as mkdirSync25, readFileSync as readFileSync42, writeFileSync as writeFileSync24 } from "fs";
41147
- import { dirname as dirname26, join as join55 } from "path";
41317
+ import { dirname as dirname26, join as join56 } from "path";
41148
41318
  import { homedir as homedir18 } from "os";
41149
41319
 
41150
41320
  // src/cli/completer.ts
41321
+ import { readdirSync as readdirSync19, statSync as statSync11 } from "fs";
41322
+ import { join as join54 } from "path";
41323
+ function buildSlashCompletions(commands) {
41324
+ return Array.from(commands.entries()).map(([name, cmd]) => ({
41325
+ name,
41326
+ description: cmd.description
41327
+ }));
41328
+ }
41329
+
41151
41330
  class SlashCommandProvider {
41152
41331
  name = "slash-commands";
41153
41332
  commands;
@@ -41158,7 +41337,7 @@ class SlashCommandProvider {
41158
41337
  return ctx.tokenIndex === 0 && ctx.line.startsWith("/");
41159
41338
  }
41160
41339
  complete(ctx) {
41161
- return this.commands.filter((c) => c.startsWith(ctx.partial.slice(1))).map((c) => `/${c}`);
41340
+ return this.commands.filter((c) => c.name.startsWith(ctx.partial.slice(1))).map((c) => ({ value: `/${c.name}`, description: c.description }));
41162
41341
  }
41163
41342
  }
41164
41343
 
@@ -41168,13 +41347,13 @@ class SubcommandProvider {
41168
41347
  subcommands;
41169
41348
  constructor(command, subcommands) {
41170
41349
  this.command = command;
41171
- this.subcommands = subcommands;
41350
+ this.subcommands = subcommands.map((s) => typeof s === "string" ? { name: s } : s);
41172
41351
  }
41173
41352
  match(ctx) {
41174
41353
  return ctx.tokenIndex === 1 && ctx.tokens[0] === `/${this.command}`;
41175
41354
  }
41176
41355
  complete(ctx) {
41177
- return this.subcommands.filter((s) => s.startsWith(ctx.partial));
41356
+ return this.subcommands.filter((s) => s.name.startsWith(ctx.partial)).map((s) => ({ value: s.name, description: s.description }));
41178
41357
  }
41179
41358
  }
41180
41359
 
@@ -41188,9 +41367,44 @@ class ModelArgProvider {
41188
41367
  return ctx.tokenIndex === 2 && ctx.tokens[0] === "/model" && ctx.tokens[1] === "use";
41189
41368
  }
41190
41369
  complete(ctx) {
41191
- return this.getModels().filter((m) => m.startsWith(ctx.partial));
41370
+ return this.getModels().filter((m) => m.startsWith(ctx.partial)).map((m) => ({ value: m }));
41192
41371
  }
41193
41372
  }
41373
+
41374
+ class FilePathProvider {
41375
+ name = "file-paths";
41376
+ root;
41377
+ constructor(root) {
41378
+ this.root = root;
41379
+ }
41380
+ match(ctx) {
41381
+ return ctx.tokenIndex >= 1 && (ctx.partial.includes("/") || ctx.partial.includes("\\"));
41382
+ }
41383
+ complete(ctx) {
41384
+ try {
41385
+ const cwd = this.root ?? process.cwd();
41386
+ const partialPath = ctx.partial;
41387
+ const lastSlash = Math.max(partialPath.lastIndexOf("/"), partialPath.lastIndexOf("\\"));
41388
+ const dirPath = lastSlash > 0 ? partialPath.slice(0, lastSlash) : ".";
41389
+ const prefix = lastSlash > 0 ? partialPath.slice(0, lastSlash + 1) : "";
41390
+ const fullPath = join54(cwd, dirPath);
41391
+ const entries = readdirSync19(fullPath);
41392
+ const matches = [];
41393
+ for (const entry of entries) {
41394
+ const entryPath = join54(fullPath, entry);
41395
+ const stat2 = statSync11(entryPath);
41396
+ const completion = prefix + entry + (stat2.isDirectory() ? "/" : "");
41397
+ if (completion.startsWith(ctx.partial)) {
41398
+ matches.push(completion);
41399
+ }
41400
+ }
41401
+ return matches.slice(0, 20).map((m) => ({ value: m }));
41402
+ } catch {
41403
+ return [];
41404
+ }
41405
+ }
41406
+ }
41407
+
41194
41408
  class ProviderArgProvider {
41195
41409
  name = "provider-args";
41196
41410
  providers;
@@ -41201,7 +41415,7 @@ class ProviderArgProvider {
41201
41415
  return ctx.tokenIndex === 2 && ctx.tokens[0] === "/provider" && ctx.tokens[1] === "use";
41202
41416
  }
41203
41417
  complete(ctx) {
41204
- return this.providers.filter((p) => p.startsWith(ctx.partial));
41418
+ return this.providers.filter((p) => p.startsWith(ctx.partial)).map((p) => ({ value: p }));
41205
41419
  }
41206
41420
  }
41207
41421
 
@@ -41216,9 +41430,8 @@ class SessionNameProvider {
41216
41430
  }
41217
41431
  complete(ctx) {
41218
41432
  const names = this.getNames();
41219
- if (!ctx.partial)
41220
- return names;
41221
- return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
41433
+ const filtered = !ctx.partial ? names : names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
41434
+ return filtered.map((n) => ({ value: n }));
41222
41435
  }
41223
41436
  }
41224
41437
 
@@ -41240,9 +41453,8 @@ class SkillNameProvider {
41240
41453
  if (!source)
41241
41454
  return [];
41242
41455
  const names = source();
41243
- if (!ctx.partial)
41244
- return names;
41245
- return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
41456
+ const filtered = !ctx.partial ? names : names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
41457
+ return filtered.map((n) => ({ value: n }));
41246
41458
  }
41247
41459
  }
41248
41460
 
@@ -41354,19 +41566,22 @@ function toolCategory(tool) {
41354
41566
  }
41355
41567
  return "meta";
41356
41568
  }
41357
- function actionMarkFor(tool) {
41569
+ function toolAccent(tool) {
41358
41570
  switch (toolCategory(tool)) {
41359
41571
  case "recon":
41360
- return pc.cyan(glyph.action);
41572
+ return pc.cyan;
41361
41573
  case "mutate":
41362
- return pc.magenta(glyph.action);
41574
+ return pc.magenta;
41363
41575
  case "execute":
41364
- return pc.blue(glyph.action);
41576
+ return pc.blue;
41365
41577
  case "meta":
41366
41578
  default:
41367
- return pc.dim(glyph.action);
41579
+ return pc.dim;
41368
41580
  }
41369
41581
  }
41582
+ function actionMarkFor(tool) {
41583
+ return toolAccent(tool)(glyph.action);
41584
+ }
41370
41585
  function successMark() {
41371
41586
  return pc.green(glyph.success);
41372
41587
  }
@@ -41861,11 +42076,7 @@ class Renderer {
41861
42076
  meta(chunk) {
41862
42077
  this.spinner.stop();
41863
42078
  if (this.thoughtStarted) {
41864
- if (!this.thoughtHeaderPrinted) {
41865
- this.channel.writeRaw(`
41866
- ${pc.magenta(glyph.thinking)} ${pc.dim(this.thinkingLabel)} `);
41867
- this.thoughtHeaderPrinted = true;
41868
- }
42079
+ this.writeThoughtHeader();
41869
42080
  this.channel.writeRaw(pc.dim(chunk));
41870
42081
  } else if (this.card) {
41871
42082
  this.writeInlineBody(chunk);
@@ -41901,13 +42112,16 @@ ${pc.magenta(glyph.thinking)} ${pc.dim(this.thinkingLabel)} `);
41901
42112
  reasoning(chunk) {
41902
42113
  this.spinner.stop();
41903
42114
  this.flushRecon();
41904
- if (this.thoughtStarted && !this.thoughtHeaderPrinted) {
41905
- this.channel.writeRaw(`
41906
- ${pc.magenta(glyph.thinking)} ${pc.dim(this.thinkingLabel)} `);
41907
- this.thoughtHeaderPrinted = true;
41908
- }
42115
+ this.writeThoughtHeader();
41909
42116
  this.channel.writeRaw(pc.dim(chunk));
41910
42117
  }
42118
+ writeThoughtHeader() {
42119
+ if (!this.thoughtStarted || this.thoughtHeaderPrinted)
42120
+ return;
42121
+ this.channel.writeRaw(`
42122
+ ${pc.magenta(glyph.thinking)} ${pc.magenta(this.thinkingLabel)} ${pc.dim("·")} `);
42123
+ this.thoughtHeaderPrinted = true;
42124
+ }
41911
42125
  thinkingStart() {
41912
42126
  this.flushRecon();
41913
42127
  this.thoughtStarted = true;
@@ -41946,6 +42160,7 @@ ${pc.magenta(glyph.thinking)} ${pc.dim(this.thinkingLabel)} `);
41946
42160
  const summary = humanArgs(tool, displayArgs);
41947
42161
  const label = friendlyTool(tool);
41948
42162
  const mark = actionMarkFor(tool);
42163
+ const accent = toolAccent(tool);
41949
42164
  const deferred = this.verbosity === "quiet";
41950
42165
  this.card = {
41951
42166
  tool,
@@ -41964,7 +42179,7 @@ ${pc.dim(glyph.detail)} ${pc.cyan(stepContext)}
41964
42179
  `);
41965
42180
  }
41966
42181
  if (!deferred) {
41967
- this.channel.writeRaw(`${mark} ${label}${summary ? ` ${pc.dim(summary)}` : ""}
42182
+ this.channel.writeRaw(`${mark} ${accent(label)}${summary ? ` ${pc.dim("·")} ${pc.dim(summary)}` : ""}
41968
42183
  `);
41969
42184
  }
41970
42185
  if (!SPINNERLESS_TOOLS.has(tool)) {
@@ -41981,7 +42196,8 @@ ${pc.dim(glyph.detail)} ${pc.cyan(stepContext)}
41981
42196
  this.reconBuffer = [];
41982
42197
  if (items.length === 1) {
41983
42198
  const it = items[0];
41984
- const head = `${it.mark} ${it.label}${it.summary ? ` ${pc.dim(it.summary)}` : ""}`;
42199
+ const accent = toolAccent(it.tool);
42200
+ const head = `${it.mark} ${accent(it.label)}${it.summary ? ` ${pc.dim("·")} ${pc.dim(it.summary)}` : ""}`;
41985
42201
  this.channel.writeRaw(`${head} ${successMark()} ${pc.dim(formatDuration(it.duration))}
41986
42202
  `);
41987
42203
  return;
@@ -42018,13 +42234,19 @@ ${pc.dim(glyph.detail)} ${pc.cyan(stepContext)}
42018
42234
  }
42019
42235
  if (deferred) {
42020
42236
  if (!error && RECON_TOOLS.has(this.card.tool)) {
42021
- this.reconBuffer.push({ mark: leadMark, label, summary, duration });
42237
+ this.reconBuffer.push({
42238
+ tool: this.card.tool,
42239
+ mark: leadMark,
42240
+ label,
42241
+ summary,
42242
+ duration
42243
+ });
42022
42244
  this.recordLast(label, summary, error, duration);
42023
42245
  this.card = null;
42024
42246
  return;
42025
42247
  }
42026
42248
  this.flushRecon();
42027
- const head = `${leadMark} ${label}${summary ? ` ${pc.dim(summary)}` : ""}`;
42249
+ const head = `${leadMark} ${toolAccent(this.card.tool)(label)}${summary ? ` ${pc.dim("·")} ${pc.dim(summary)}` : ""}`;
42028
42250
  this.channel.writeRaw(`${head} ${tail}
42029
42251
  `);
42030
42252
  if (error && firstBody) {
@@ -42049,6 +42271,7 @@ ${pc.dim(glyph.detail)} ${pc.cyan(stepContext)}
42049
42271
  const firstBody = this.card?.firstBody ?? "";
42050
42272
  const status = !error ? "ok" : firstBody.startsWith("⚠") ? "warn" : "error";
42051
42273
  recordLastAction({
42274
+ tool: this.card?.tool ?? "",
42052
42275
  label,
42053
42276
  args: summary,
42054
42277
  status,
@@ -42425,10 +42648,10 @@ class Repl {
42425
42648
  this.execModule = execModule;
42426
42649
  this.slog = new SessionLogger(sessionManager, logger4);
42427
42650
  this.bindSessionOutput();
42428
- this.configDir = configDir || join55(homedir18(), ".mma");
42651
+ this.configDir = configDir || join56(homedir18(), ".mma");
42429
42652
  this.baseDir = baseDir || process.cwd();
42430
42653
  this.noAgentsMd = noAgentsMd === true;
42431
- this.historyPath = historyPath ?? join55(homedir18(), ".mma", "repl-history");
42654
+ this.historyPath = historyPath ?? join56(homedir18(), ".mma", "repl-history");
42432
42655
  this.loadHistory();
42433
42656
  const editorCompleter = (line) => {
42434
42657
  const [matches, partial] = this.completer.complete(line);
@@ -42436,6 +42659,10 @@ class Repl {
42436
42659
  return [matches, partial];
42437
42660
  return [[], line];
42438
42661
  };
42662
+ const readlineCompleter = (line) => {
42663
+ const [items, partial] = editorCompleter(line);
42664
+ return [items.map((i) => i.value), partial];
42665
+ };
42439
42666
  if (process.stdin.isTTY) {
42440
42667
  this.rl = new LineEditor({
42441
42668
  input: process.stdin,
@@ -42453,7 +42680,7 @@ class Repl {
42453
42680
  history: this.history,
42454
42681
  historySize: this.maxHistory,
42455
42682
  tabSize: 2,
42456
- completer: editorCompleter
42683
+ completer: readlineCompleter
42457
42684
  });
42458
42685
  }
42459
42686
  this.output = output ?? getDefaultChannel();
@@ -42508,7 +42735,7 @@ class Repl {
42508
42735
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
42509
42736
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
42510
42737
  this.agent.shutdown();
42511
- const projectConfigPath = join55(this.baseDir, ".mmrc");
42738
+ const projectConfigPath = join56(this.baseDir, ".mmrc");
42512
42739
  const { config: freshConfig } = loadConfig2({
42513
42740
  configDir: this.configDir,
42514
42741
  projectConfigPath
@@ -42548,7 +42775,7 @@ class Repl {
42548
42775
  }
42549
42776
  setupCompleter() {
42550
42777
  this.completer.reset();
42551
- const slashCommands = Array.from(this.commands.keys());
42778
+ const slashCommands = buildSlashCompletions(this.commands);
42552
42779
  this.completer.registerProvider(new SlashCommandProvider(slashCommands));
42553
42780
  if (this.sessionManager) {
42554
42781
  this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
@@ -42564,6 +42791,7 @@ class Repl {
42564
42791
  this.completer.registerProvider(new ProviderArgProvider(entryLabels));
42565
42792
  }
42566
42793
  this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
42794
+ this.completer.registerProvider(new FilePathProvider(this.baseDir));
42567
42795
  this.refreshModelCache();
42568
42796
  }
42569
42797
  refreshModelCache() {
@@ -42744,7 +42972,8 @@ ${t("repl.interrupt")}`));
42744
42972
  return;
42745
42973
  }
42746
42974
  const mark = last2.status === "error" ? pc.red(glyph.failure) : last2.status === "warn" ? pc.yellow(glyph.warning) : pc.green(glyph.success);
42747
- const head = `${pc.cyan(glyph.action)} ${last2.label}${last2.args ? ` ${pc.dim(last2.args)}` : ""} ${mark} ${pc.dim(`${last2.durationMs}ms`)}`;
42975
+ const accent = toolAccent(last2.tool);
42976
+ const head = `${accent(glyph.action)} ${accent(last2.label)}${last2.args ? ` ${pc.dim("·")} ${pc.dim(last2.args)}` : ""} ${mark} ${pc.dim(`${last2.durationMs}ms`)}`;
42748
42977
  const lines = [head, ...last2.body.map((l) => ` ${l}`)];
42749
42978
  this.output.writeLine(`
42750
42979
  ${lines.join(`
@@ -43022,9 +43251,9 @@ ${divider()}`);
43022
43251
  row(t("repl.agents_label"), pc.red(t("repl.disabled")));
43023
43252
  } else {
43024
43253
  const agentsMdCandidates = [
43025
- join55(this.baseDir, "AGENTS.md"),
43026
- join55(this.baseDir, ".mma", "AGENTS.md"),
43027
- join55(this.configDir, "AGENTS.md")
43254
+ join56(this.baseDir, "AGENTS.md"),
43255
+ join56(this.baseDir, ".mma", "AGENTS.md"),
43256
+ join56(this.configDir, "AGENTS.md")
43028
43257
  ];
43029
43258
  const foundAgents = agentsMdCandidates.filter((p) => existsSync61(p));
43030
43259
  if (foundAgents.length > 0) {
@@ -43037,7 +43266,7 @@ ${divider()}`);
43037
43266
  }
43038
43267
  const meta = this.sessionManager?.getActiveMeta();
43039
43268
  if (meta) {
43040
- const sessionPath = join55(this.configDir, "sessions", meta.id);
43269
+ const sessionPath = join56(this.configDir, "sessions", meta.id);
43041
43270
  row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
43042
43271
  }
43043
43272
  const isTty2 = process.stdout.isTTY === true;
@@ -43049,9 +43278,9 @@ ${divider()}`);
43049
43278
  try {
43050
43279
  const { existsSync: exists } = await import("fs");
43051
43280
  const { join: pathJoin } = await import("path");
43052
- const { hasDomainFiles: hasDomainFiles3 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
43281
+ const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
43053
43282
  const legacyPath = pathJoin(this.configDir, "config.json");
43054
- if (exists(legacyPath) && !hasDomainFiles3(this.configDir)) {
43283
+ if (exists(legacyPath) && !hasDomainFiles2(this.configDir)) {
43055
43284
  this.output.writeLine(pc.yellow(` ${t("config.legacy_hint")}`));
43056
43285
  this.output.writeLine("");
43057
43286
  }
@@ -43166,51 +43395,7 @@ function printRunResult(result, flush, channel = getDefaultChannel()) {
43166
43395
 
43167
43396
  // src/cli/main.ts
43168
43397
  init_setup();
43169
-
43170
- // src/cli/setup-order.ts
43171
- var SECURITY_BASH_BLOCKLIST = [
43172
- "rm",
43173
- "dd",
43174
- "chmod",
43175
- "wget",
43176
- "curl",
43177
- "scp",
43178
- "ssh",
43179
- "nc",
43180
- "netcat",
43181
- "sudo",
43182
- "su",
43183
- "kill",
43184
- "pkill",
43185
- "killall",
43186
- "shutdown",
43187
- "reboot"
43188
- ];
43189
- function applyAnswersToConfig(config, a) {
43190
- config.provider = {
43191
- ...config.provider,
43192
- type: a.provider,
43193
- baseUrl: a.apiBase,
43194
- apiKey: a.apiKey
43195
- };
43196
- config.model = a.model;
43197
- config.contextWindow = a.contextWindow;
43198
- config.maxToolIterations = a.maxToolIterations;
43199
- config.locale = a.locale;
43200
- if (config.security) {
43201
- config.security.enabled = true;
43202
- config.security.bash.enabled = true;
43203
- config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
43204
- if (a.securityBashBlock) {
43205
- config.security.bash.blacklist = [
43206
- ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
43207
- ];
43208
- }
43209
- if (!a.securityPathsDeny)
43210
- config.security.paths.denied = [];
43211
- }
43212
- return config;
43213
- }
43398
+ init_setup_order();
43214
43399
 
43215
43400
  // src/cli/relaunch.ts
43216
43401
  init_output();
@@ -43270,7 +43455,7 @@ init_config2();
43270
43455
  init_i18n();
43271
43456
  init_colors();
43272
43457
  import { existsSync as existsSync62 } from "fs";
43273
- import { join as join57, dirname as dirname27 } from "path";
43458
+ import { join as join58, dirname as dirname27 } from "path";
43274
43459
  import { homedir as homedir20 } from "os";
43275
43460
  import { fileURLToPath as fileURLToPath6 } from "url";
43276
43461
 
@@ -43392,9 +43577,9 @@ init_data_sanitizer();
43392
43577
  init_i18n();
43393
43578
  init_utils();
43394
43579
  import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync26 } from "fs";
43395
- import { join as join56 } from "path";
43580
+ import { join as join57 } from "path";
43396
43581
  import { homedir as homedir19 } from "os";
43397
- var CRASH_LOG_DIR = join56(homedir19(), ".mma", "logs");
43582
+ var CRASH_LOG_DIR = join57(homedir19(), ".mma", "logs");
43398
43583
  var CRASH_LOG_FILE = "crash.jsonl";
43399
43584
  function formatCrashEntry(type2, err) {
43400
43585
  const message = errMsg(err);
@@ -43410,7 +43595,7 @@ function formatCrashEntry(type2, err) {
43410
43595
  function writeCrashEntry(dir, entry) {
43411
43596
  try {
43412
43597
  mkdirSync26(dir, { recursive: true });
43413
- appendFileSync8(join56(dir, CRASH_LOG_FILE), JSON.stringify(entry) + `
43598
+ appendFileSync8(join57(dir, CRASH_LOG_FILE), JSON.stringify(entry) + `
43414
43599
  `, "utf-8");
43415
43600
  } catch {}
43416
43601
  }
@@ -43538,6 +43723,9 @@ async function main() {
43538
43723
  }
43539
43724
  const jsonSink = jsonOpt ? new JsonOutputSink(defaultOutputBus) : undefined;
43540
43725
  const prompt = program2.args.join(" ");
43726
+ const mmaDir = process.env.MMA_CONFIG_DIR || join58(homedir20(), ".mma");
43727
+ const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
43728
+ const virgin = !existsSync62(join58(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
43541
43729
  const { agent, config, baseDir, legacyDetected, contextProbe } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete, reasoningLevel, contextLog);
43542
43730
  if (verboseOpt)
43543
43731
  config.ui.verbosity = "verbose";
@@ -43545,6 +43733,9 @@ async function main() {
43545
43733
  if (legacyDetected) {
43546
43734
  getDefaultChannel().writeLine(pc.yellow(` ${t("config.legacy_hint")}`), "stderr");
43547
43735
  }
43736
+ if (virgin && !jsonOpt) {
43737
+ getDefaultChannel().writeLine(pc.yellow(` ${t("cli.first_run_oneshot")}`), "stderr");
43738
+ }
43548
43739
  if (contextProbe && !jsonOpt) {
43549
43740
  try {
43550
43741
  const probe = await contextProbe;
@@ -43620,16 +43811,16 @@ async function main() {
43620
43811
  await updater?.waitForIdle();
43621
43812
  process.exit(exitCode);
43622
43813
  } else {
43623
- const mmaDir = process.env.MMA_CONFIG_DIR || join57(homedir20(), ".mma");
43624
- const legacyConfigPath = join57(mmaDir, "config.json");
43814
+ const mmaDir = process.env.MMA_CONFIG_DIR || join58(homedir20(), ".mma");
43815
+ const legacyConfigPath = join58(mmaDir, "config.json");
43625
43816
  let hasAnyConfig = existsSync62(legacyConfigPath);
43626
43817
  if (!hasAnyConfig) {
43627
43818
  try {
43628
- const { hasDomainFiles: hasDomainFiles3 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
43629
- hasAnyConfig = hasDomainFiles3(mmaDir);
43819
+ const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
43820
+ hasAnyConfig = hasDomainFiles2(mmaDir);
43630
43821
  } catch {}
43631
43822
  }
43632
- const projectConfigPath = projectDir ? join57(projectDir, ".mmrc") : join57(process.cwd(), ".mmrc");
43823
+ const projectConfigPath = projectDir ? join58(projectDir, ".mmrc") : join58(process.cwd(), ".mmrc");
43633
43824
  if (!hasAnyConfig && !exitOnComplete && process.env.MMA_POST_SETUP !== "1") {
43634
43825
  getDefaultChannel().writeLine(`
43635
43826
  ` + t("cli.first_run") + `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {