metheus-governance-mcp-cli 0.2.63 → 0.2.65

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/README.md CHANGED
@@ -213,8 +213,11 @@ Direct commands:
213
213
  metheus-governance-mcp-cli bot list
214
214
  metheus-governance-mcp-cli bot show --provider telegram --bot-key main
215
215
  metheus-governance-mcp-cli bot add --provider telegram
216
+ metheus-governance-mcp-cli bot edit
216
217
  metheus-governance-mcp-cli bot edit --provider telegram
217
218
  metheus-governance-mcp-cli bot remove --provider telegram
219
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main
220
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-key main
218
221
  metheus-governance-mcp-cli bot global --provider telegram
219
222
  metheus-governance-mcp-cli bot verify --provider telegram --bot-key main
220
223
  ```
@@ -222,6 +225,8 @@ metheus-governance-mcp-cli bot verify --provider telegram --bot-key main
222
225
  Behavior:
223
226
 
224
227
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
228
+ - `bot add` without flags starts a guided question flow: provider -> server bot -> local bot key -> token -> verify -> role/AI binding -> default bot choice.
229
+ - `bot edit` without flags starts a guided numbered flow: provider -> bot entry -> guided edit -> step-by-step field choices.
225
230
  - Telegram supports named local bot entries with:
226
231
  - `SERVER_BOT_ID`
227
232
  - `USERNAME`
@@ -235,13 +240,17 @@ Behavior:
235
240
  - `bot verify` checks the configured local token and prints the current AI binding summary.
236
241
  - `bot show` prints one local bot entry in detail.
237
242
  - `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
243
+ - `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
244
+ - `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
238
245
 
239
246
  Non-interactive examples:
240
247
 
241
248
  ```bash
242
249
  metheus-governance-mcp-cli bot global --provider telegram --non-interactive true --api-base-url http://127.0.0.1:8999/telegram --auto-clear-webhook false --allowed-updates message,edited_message,channel_post
243
- metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --bot-key main --bot-id <server_bot_uuid> --token <telegram_bot_token> --role-profile monitor --client codex --permission-mode read_only --reasoning-effort low --default true
244
- metheus-governance-mcp-cli bot edit --provider telegram --bot-key main --non-interactive true --client claude --model claude-3.7-sonnet --permission-mode workspace_write --reasoning-effort medium
250
+ metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --bot-key main --server-bot-id <server_bot_uuid> --username <telegram_username> --token <telegram_bot_token> --role-profile monitor --ai-client codex --ai-model gpt-5-codex --ai-permission-mode read_only --ai-reasoning-effort low --default true
251
+ metheus-governance-mcp-cli bot edit --provider telegram --bot-key main --non-interactive true --ai-client claude --ai-model claude-sonnet --ai-permission-mode workspace_write --ai-reasoning-effort medium
252
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main --non-interactive true
253
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-key main --server-bot-id <server_bot_uuid> --bot-name <telegram_username> --role-profile monitor --ai-client codex --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
245
254
  metheus-governance-mcp-cli bot remove --provider telegram --bot-key main --non-interactive true
246
255
  metheus-governance-mcp-cli bot verify --provider telegram --bot-key main --json true
247
256
  ```
@@ -276,6 +285,9 @@ Checks:
276
285
  - role profile -> local CLI availability
277
286
  - route dry-run / trigger-policy safety warnings
278
287
  - local provider token presence for active project destinations
288
+ - enabled runner route -> local bot env binding resolution
289
+ - enabled runner route -> server bot UUID cross-check
290
+ - legacy `TELEGRAM_BOT_TOKEN` fallback still in use
279
291
  - codex/claude/gemini/antigravity/cursor registration state
280
292
  - gateway `tools/list` reachability
281
293
  - `project.summary` access
package/cli.mjs CHANGED
@@ -240,6 +240,8 @@ function printUsage() {
240
240
  ` ${cmd} bot add [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>]`,
241
241
  ` ${cmd} bot edit [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>]`,
242
242
  ` ${cmd} bot remove [--provider <telegram|slack|kakaotalk>]`,
243
+ ` ${cmd} bot set-default --provider telegram [--bot-key <key>]`,
244
+ ` ${cmd} bot migrate --provider telegram [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>]`,
243
245
  ` ${cmd} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-key <key>] [--timeout-seconds <n>] [--json <true|false>]`,
244
246
  ` ${cmd} bot global --provider telegram [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <key>]`,
245
247
  ` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>] [--strict <true|false>]`,
@@ -3527,6 +3529,8 @@ async function runDoctor(flags) {
3527
3529
  includeDrafts: true,
3528
3530
  }, { defaultBaseURL: DEFAULT_BASE_URL });
3529
3531
  const rows = [];
3532
+ let runnerConfig = null;
3533
+ let enabledRoutes = [];
3530
3534
  const providerTemplates = ensureAllProviderEnvTemplates();
3531
3535
  const runnerTemplate = ensureBotRunnerConfigTemplate();
3532
3536
  for (const provider of PROVIDER_ENV_ORDER) {
@@ -3541,9 +3545,9 @@ async function runDoctor(flags) {
3541
3545
  if (runnerTemplate.error) {
3542
3546
  addDoctorCheck(rows, "warn", "local bot runner config", `${runnerTemplate.error} (${runnerTemplate.filePath})`);
3543
3547
  } else {
3544
- const runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
3548
+ runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
3545
3549
  const runnerState = loadBotRunnerState();
3546
- const enabledRoutes = ensureArray(runnerConfig.routes).filter((routeRaw) => safeObject(routeRaw).enabled);
3550
+ enabledRoutes = ensureArray(runnerConfig.routes).filter((routeRaw) => safeObject(routeRaw).enabled);
3547
3551
  if (!enabledRoutes.length) {
3548
3552
  addDoctorCheck(rows, "warn", "local bot runner config", `template ready (${runnerTemplate.filePath}), no enabled routes`);
3549
3553
  } else {
@@ -3662,6 +3666,82 @@ async function runDoctor(flags) {
3662
3666
  deps: buildDoctorChecksDeps(),
3663
3667
  });
3664
3668
  }
3669
+
3670
+ if (runnerConfig && enabledRoutes.length > 0) {
3671
+ let serverBots = null;
3672
+ try {
3673
+ serverBots = await listUserBotsForRunner({
3674
+ siteBaseURL: normalizeSiteBaseURL(context.baseURL),
3675
+ token,
3676
+ timeoutSeconds,
3677
+ }, buildRunnerDataDeps());
3678
+ } catch {
3679
+ serverBots = null;
3680
+ }
3681
+ for (const routeRaw of enabledRoutes) {
3682
+ const route = normalizeRunnerRoute(routeRaw);
3683
+ const routeLabel = route.name || route.projectID || route.provider;
3684
+ const envConfig = loadProviderEnvConfig(route.provider, {
3685
+ botID: route.botID,
3686
+ botName: route.botName,
3687
+ route,
3688
+ });
3689
+ if (!envConfig.ok) {
3690
+ addDoctorCheck(
3691
+ rows,
3692
+ "fail",
3693
+ `runner route ${routeLabel} binding`,
3694
+ `${providerEnvConfig(route.provider).label} local bot env is unresolved (${envConfig.error})`,
3695
+ );
3696
+ continue;
3697
+ }
3698
+ const detailParts = [];
3699
+ if (envConfig.botKey) detailParts.push(`bot=${envConfig.botKey}`);
3700
+ if (envConfig.botUsername) detailParts.push(`@${envConfig.botUsername}`);
3701
+ if (envConfig.serverBotID) detailParts.push(`server_bot_id=${envConfig.serverBotID}`);
3702
+ if (envConfig.client) detailParts.push(`ai=${envConfig.client}`);
3703
+ addDoctorCheck(
3704
+ rows,
3705
+ "ok",
3706
+ `runner route ${routeLabel} binding`,
3707
+ detailParts.join(", ") || "resolved",
3708
+ );
3709
+ if (route.botID && envConfig.serverBotID && route.botID !== envConfig.serverBotID) {
3710
+ addDoctorCheck(
3711
+ rows,
3712
+ strictMode ? "fail" : "warn",
3713
+ `runner route ${routeLabel} server_bot_id`,
3714
+ `route bot_id ${route.botID} != local env ${envConfig.serverBotID}`,
3715
+ );
3716
+ }
3717
+ if (route.botID && !envConfig.serverBotID) {
3718
+ addDoctorCheck(
3719
+ rows,
3720
+ strictMode ? "fail" : "warn",
3721
+ `runner route ${routeLabel} server_bot_id`,
3722
+ `route bot_id ${route.botID} is set, but local env entry does not carry SERVER_BOT_ID`,
3723
+ );
3724
+ }
3725
+ if (serverBots && route.botID) {
3726
+ const match = ensureArray(serverBots).find((bot) => String(bot.id || "").trim() === String(route.botID || "").trim());
3727
+ addDoctorCheck(
3728
+ rows,
3729
+ match ? "ok" : (strictMode ? "fail" : "warn"),
3730
+ `runner route ${routeLabel} server bot`,
3731
+ match ? `${match.name || "(unnamed)"} [${match.role || "-"}]` : `server bot ${route.botID} not found`,
3732
+ );
3733
+ }
3734
+ }
3735
+ const telegramDefaultConfig = loadProviderEnvConfig("telegram");
3736
+ if (telegramDefaultConfig.ok && !telegramDefaultConfig.botKey && String(telegramDefaultConfig.token || "").trim()) {
3737
+ addDoctorCheck(
3738
+ rows,
3739
+ "warn",
3740
+ "telegram legacy token",
3741
+ "TELEGRAM_BOT_TOKEN fallback is still active; run bot migrate --provider telegram to move to named entries",
3742
+ );
3743
+ }
3744
+ }
3665
3745
  }
3666
3746
 
3667
3747
  for (const cliBin of MCP_CLIENTS) {
@@ -43,6 +43,35 @@ function firstNonEmptyString(values) {
43
43
  return "";
44
44
  }
45
45
 
46
+ function hasOwnFlag(flags, key) {
47
+ return Object.prototype.hasOwnProperty.call(safeObject(flags), key);
48
+ }
49
+
50
+ function getServerBotIDFlag(flags) {
51
+ const parsedFlags = safeObject(flags);
52
+ return firstNonEmptyString([parsedFlags["server-bot-id"], parsedFlags["bot-id"]]);
53
+ }
54
+
55
+ function getAIClientFlag(flags) {
56
+ const parsedFlags = safeObject(flags);
57
+ return firstNonEmptyString([parsedFlags["ai-client"], parsedFlags.client]);
58
+ }
59
+
60
+ function getAIModelFlag(flags) {
61
+ const parsedFlags = safeObject(flags);
62
+ return firstNonEmptyString([parsedFlags["ai-model"], parsedFlags.model]);
63
+ }
64
+
65
+ function getAIPermissionModeFlag(flags) {
66
+ const parsedFlags = safeObject(flags);
67
+ return firstNonEmptyString([parsedFlags["ai-permission-mode"], parsedFlags["permission-mode"]]);
68
+ }
69
+
70
+ function getAIReasoningEffortFlag(flags) {
71
+ const parsedFlags = safeObject(flags);
72
+ return firstNonEmptyString([parsedFlags["ai-reasoning-effort"], parsedFlags["reasoning-effort"]]);
73
+ }
74
+
46
75
  function maskSecret(rawValue) {
47
76
  const text = String(rawValue || "").trim();
48
77
  if (!text) return "";
@@ -76,9 +105,11 @@ function printBotUsage(deps) {
76
105
  ` ${cliName} bot setup`,
77
106
  ` ${cliName} bot list [--provider <telegram|slack|kakaotalk>] [--json <true|false>]`,
78
107
  ` ${cliName} bot show [--provider <telegram|slack|kakaotalk>] [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>] [--json <true|false>]`,
79
- ` ${cliName} bot add [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--yes <true|false>]`,
80
- ` ${cliName} bot edit [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--bot-key <key>]`,
108
+ ` ${cliName} bot add [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>] [--non-interactive <true|false>] [--bot-key <key>] [--server-bot-id <uuid>] [--username <name>] [--token <token>] [--role-profile <name>] [--ai-client <name>] [--ai-model <name>] [--ai-permission-mode <mode>] [--ai-reasoning-effort <level>] [--default <true|false>] [--verify <true|false>]`,
109
+ ` ${cliName} bot edit [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>] [--non-interactive <true|false>] [--bot-key <key>] [--server-bot-id <uuid>] [--username <name>] [--token <token>] [--role-profile <name>] [--ai-client <name>] [--ai-model <name>] [--ai-permission-mode <mode>] [--ai-reasoning-effort <level>]`,
81
110
  ` ${cliName} bot remove [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--bot-key <key>]`,
111
+ ` ${cliName} bot set-default --provider telegram [--bot-key <key>] [--non-interactive <true|false>]`,
112
+ ` ${cliName} bot migrate --provider telegram [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>] [--keep-legacy-token <true|false>] [--non-interactive <true|false>]`,
82
113
  ` ${cliName} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-key <key>] [--timeout-seconds <n>] [--json <true|false>]`,
83
114
  ` ${cliName} bot global --provider telegram [--non-interactive <true|false>] [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <key>]`,
84
115
  "",
@@ -87,6 +118,27 @@ function printBotUsage(deps) {
87
118
  }
88
119
 
89
120
  function createPrompter() {
121
+ const scriptedPromptAnswersRaw = String(process.env.METHEUS_SCRIPTED_PROMPT_ANSWERS || "").trim();
122
+ if (scriptedPromptAnswersRaw) {
123
+ let scriptedAnswers = [];
124
+ try {
125
+ const parsed = JSON.parse(scriptedPromptAnswersRaw);
126
+ scriptedAnswers = ensureArray(parsed).map((value) => String(value ?? ""));
127
+ } catch {
128
+ scriptedAnswers = scriptedPromptAnswersRaw.split(/\r?\n/).map((value) => String(value ?? ""));
129
+ }
130
+ let answerIndex = 0;
131
+ return {
132
+ ask(promptText) {
133
+ process.stdout.write(promptText);
134
+ const answer = answerIndex < scriptedAnswers.length ? scriptedAnswers[answerIndex] : "";
135
+ answerIndex += 1;
136
+ process.stdout.write(`${answer}\n`);
137
+ return Promise.resolve(String(answer || ""));
138
+ },
139
+ close() {},
140
+ };
141
+ }
90
142
  return {
91
143
  ask(promptText) {
92
144
  return new Promise((resolve) => {
@@ -161,6 +213,19 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
161
213
  }
162
214
  }
163
215
 
216
+ async function promptKeepChangeClear(ui, title, { allowClear = true, defaultValue = "keep" } = {}) {
217
+ const options = [
218
+ { value: "keep", label: "Keep current value" },
219
+ { value: "change", label: "Change value" },
220
+ ];
221
+ if (allowClear) {
222
+ options.push({ value: "clear", label: "Clear value" });
223
+ }
224
+ const defaultIndex = Math.max(0, options.findIndex((option) => option.value === defaultValue));
225
+ const selected = await promptChoice(ui, title, options, { defaultIndex });
226
+ return selected?.value || "keep";
227
+ }
228
+
164
229
  function loadProviderEnvState(provider, deps) {
165
230
  const ensureTemplate = requireDependency(deps, "ensureProviderEnvTemplate");
166
231
  const filePathResolver = requireDependency(deps, "providerEnvFilePath");
@@ -201,6 +266,10 @@ function removeTelegramEntry(parsedEnv, botKey) {
201
266
  return parsed;
202
267
  }
203
268
 
269
+ function hasNamedTelegramEntry(parsedEnv, deps) {
270
+ return telegramEntriesForDisplay(parsedEnv, deps).length > 0;
271
+ }
272
+
204
273
  function upsertTelegramEntry(parsedEnv, entry) {
205
274
  const parsed = removeTelegramEntry(parsedEnv, entry.key);
206
275
  const keys = telegramEntryEnvKeys(entry.key);
@@ -393,6 +462,175 @@ function findTelegramEntryByFlags(parsedEnv, flags, deps) {
393
462
  return null;
394
463
  }
395
464
 
465
+ function saveTelegramBotEdit(parsed, selected, current, deps) {
466
+ let nextParsed = parsed;
467
+ if (current.key !== selected.key) {
468
+ nextParsed = removeTelegramEntry(nextParsed, selected.key);
469
+ }
470
+ nextParsed = upsertTelegramEntry(nextParsed, current);
471
+ if (String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key) {
472
+ nextParsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
473
+ }
474
+ const filePath = writeProviderEnvState("telegram", nextParsed, deps);
475
+ process.stdout.write(`Saved Telegram bot entry "${current.key}" to ${filePath}\n`);
476
+ return filePath;
477
+ }
478
+
479
+ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps) {
480
+ const bindingAction = await promptKeepChangeClear(ui, "Server bot binding", {
481
+ allowClear: true,
482
+ defaultValue: current.serverBotID ? "keep" : "change",
483
+ });
484
+ if (bindingAction === "change") {
485
+ const serverBot = await chooseServerBot(
486
+ ui,
487
+ "telegram",
488
+ flags["base-url"] || deps.defaultSiteURL,
489
+ intFromRaw(flags["timeout-seconds"], 15) || 15,
490
+ deps,
491
+ );
492
+ current.serverBotID = serverBot.botID;
493
+ if (!current.roleProfile && serverBot.role) {
494
+ current.roleProfile = serverBot.role;
495
+ }
496
+ } else if (bindingAction === "clear") {
497
+ current.serverBotID = "";
498
+ }
499
+
500
+ const usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
501
+ allowClear: true,
502
+ defaultValue: current.username ? "keep" : "change",
503
+ });
504
+ if (usernameAction === "change") {
505
+ current.username = requireDependency(deps, "normalizeTelegramBotUsername")(
506
+ await promptRequiredLine(ui, "Telegram username (without @)", current.username),
507
+ "",
508
+ );
509
+ } else if (usernameAction === "clear") {
510
+ current.username = "";
511
+ }
512
+
513
+ const tokenAction = await promptKeepChangeClear(ui, "Telegram token", {
514
+ allowClear: false,
515
+ defaultValue: "keep",
516
+ });
517
+ if (tokenAction === "change") {
518
+ current.token = await promptRequiredLine(ui, "Telegram bot token", current.token);
519
+ if (await promptYesNo(ui, "Verify updated token now?", true)) {
520
+ const verifyResult = await verifyTelegramTokenCandidate(
521
+ "telegram",
522
+ current.token,
523
+ String(parsed.TELEGRAM_API_BASE_URL || "").trim(),
524
+ intFromRaw(flags["timeout-seconds"], 15) || 15,
525
+ deps,
526
+ );
527
+ process.stdout.write(`Verify: ${verifyResult.ok ? "OK" : "FAIL"}${verifyResult.detail ? ` - ${verifyResult.detail}` : ""}\n`);
528
+ const maybeUsername = extractVerifiedTelegramUsername(verifyResult.detail);
529
+ if (verifyResult.ok && maybeUsername && !current.username) {
530
+ current.username = maybeUsername;
531
+ }
532
+ }
533
+ }
534
+
535
+ const roleAction = await promptKeepChangeClear(ui, "Role profile", {
536
+ allowClear: true,
537
+ defaultValue: current.roleProfile ? "keep" : "change",
538
+ });
539
+ if (roleAction === "change") {
540
+ current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
541
+ await promptTelegramRoleProfile(ui, deps, current.roleProfile),
542
+ );
543
+ } else if (roleAction === "clear") {
544
+ current.roleProfile = "";
545
+ }
546
+
547
+ const clientAction = await promptKeepChangeClear(ui, "AI client", {
548
+ allowClear: true,
549
+ defaultValue: current.client ? "keep" : "change",
550
+ });
551
+ if (clientAction === "change") {
552
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(
553
+ await promptAIClient(ui, deps, current.client),
554
+ "",
555
+ );
556
+ } else if (clientAction === "clear") {
557
+ current.client = "";
558
+ }
559
+
560
+ const modelAction = await promptKeepChangeClear(ui, "AI model", {
561
+ allowClear: true,
562
+ defaultValue: current.model ? "keep" : "change",
563
+ });
564
+ if (modelAction === "change") {
565
+ current.model = await promptLine(ui, "AI model", current.model);
566
+ } else if (modelAction === "clear") {
567
+ current.model = "";
568
+ }
569
+
570
+ const permissionAction = await promptKeepChangeClear(ui, "AI permission mode", {
571
+ allowClear: true,
572
+ defaultValue: current.permissionMode ? "keep" : "change",
573
+ });
574
+ if (permissionAction === "change") {
575
+ current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
576
+ await promptPermissionMode(ui, current.permissionMode),
577
+ "",
578
+ );
579
+ } else if (permissionAction === "clear") {
580
+ current.permissionMode = "";
581
+ }
582
+
583
+ const reasoningAction = await promptKeepChangeClear(ui, "AI reasoning effort", {
584
+ allowClear: true,
585
+ defaultValue: current.reasoningEffort ? "keep" : "change",
586
+ });
587
+ if (reasoningAction === "change") {
588
+ current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
589
+ await promptReasoningEffort(ui, current.reasoningEffort),
590
+ "",
591
+ );
592
+ } else if (reasoningAction === "clear") {
593
+ current.reasoningEffort = "";
594
+ }
595
+
596
+ const botKeyAction = await promptKeepChangeClear(ui, "Local bot key", {
597
+ allowClear: false,
598
+ defaultValue: "keep",
599
+ });
600
+ if (botKeyAction === "change") {
601
+ const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
602
+ let nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", current.key), current.key);
603
+ const existing = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key).filter((key) => key !== current.key));
604
+ while (existing.has(nextKey)) {
605
+ process.stdout.write(`Telegram bot key "${nextKey}" already exists.\n`);
606
+ nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", `${nextKey}_2`), `${nextKey}_2`);
607
+ }
608
+ current.key = nextKey;
609
+ }
610
+
611
+ const defaultChoice = await promptChoice(
612
+ ui,
613
+ "Default Telegram bot setting",
614
+ [
615
+ { value: "keep", label: "Keep current default setting", description: String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key ? "currently default" : "not default" },
616
+ { value: "set", label: "Set this bot as default" },
617
+ { value: "unset", label: "Do not make this bot default" },
618
+ ],
619
+ { defaultIndex: 0 },
620
+ );
621
+ if (defaultChoice?.value === "set") {
622
+ parsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
623
+ } else if (defaultChoice?.value === "unset" && String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key) {
624
+ parsed.TELEGRAM_DEFAULT_BOT_KEY = "";
625
+ }
626
+
627
+ if (!await promptYesNo(ui, `Save changes for "${current.key}" now?`, true)) {
628
+ process.stdout.write("Cancelled.\n");
629
+ return;
630
+ }
631
+ saveTelegramBotEdit(parsed, selected, current, deps);
632
+ }
633
+
396
634
  async function selectProvider(ui, initialProvider, deps) {
397
635
  const normalizeProvider = requireDependency(deps, "normalizeBotProvider");
398
636
  const provider = String(initialProvider || "").trim();
@@ -702,7 +940,7 @@ async function addTelegramBot(ui, flags, deps) {
702
940
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
703
941
  const serverBot = nonInteractive
704
942
  ? {
705
- botID: String(flags["server-bot-id"] || flags["bot-id"] || "").trim(),
943
+ botID: getServerBotIDFlag(flags),
706
944
  role: String(flags.role || "").trim(),
707
945
  name: "",
708
946
  }
@@ -777,27 +1015,27 @@ async function addTelegramBot(ui, flags, deps) {
777
1015
  );
778
1016
  const client = requireDependency(deps, "normalizeLocalAIClientName")(
779
1017
  nonInteractive
780
- ? String(flags.client || "").trim()
781
- : await promptAIClient(ui, deps, flags.client || ""),
1018
+ ? getAIClientFlag(flags)
1019
+ : await promptAIClient(ui, deps, getAIClientFlag(flags)),
782
1020
  "",
783
1021
  );
784
- const model = nonInteractive ? String(flags.model || "").trim() : await promptLine(ui, "AI model", String(flags.model || "").trim());
1022
+ const model = nonInteractive ? getAIModelFlag(flags) : await promptLine(ui, "AI model", getAIModelFlag(flags));
785
1023
  const permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
786
1024
  nonInteractive
787
- ? String(flags["permission-mode"] || "").trim()
788
- : await promptPermissionMode(ui, String(flags["permission-mode"] || "").trim()),
1025
+ ? getAIPermissionModeFlag(flags)
1026
+ : await promptPermissionMode(ui, getAIPermissionModeFlag(flags)),
789
1027
  "",
790
1028
  );
791
1029
  const reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
792
1030
  nonInteractive
793
- ? String(flags["reasoning-effort"] || "").trim()
794
- : await promptReasoningEffort(ui, String(flags["reasoning-effort"] || "").trim()),
1031
+ ? getAIReasoningEffortFlag(flags)
1032
+ : await promptReasoningEffort(ui, getAIReasoningEffortFlag(flags)),
795
1033
  "",
796
1034
  );
797
1035
 
798
1036
  const nextParsed = upsertTelegramEntry(parsed, {
799
1037
  key: botKey,
800
- serverBotID: String(flags["server-bot-id"] || serverBot.botID || "").trim(),
1038
+ serverBotID: String(getServerBotIDFlag(flags) || serverBot.botID || "").trim(),
801
1039
  username,
802
1040
  token,
803
1041
  roleProfile,
@@ -882,8 +1120,8 @@ async function editTelegramBot(ui, flags, deps) {
882
1120
  }
883
1121
  let current = { ...selected };
884
1122
  if (nonInteractive) {
885
- if (flags["server-bot-id"] || flags["bot-id"]) {
886
- current.serverBotID = String(flags["server-bot-id"] || flags["bot-id"] || "").trim();
1123
+ if (getServerBotIDFlag(flags)) {
1124
+ current.serverBotID = getServerBotIDFlag(flags);
887
1125
  }
888
1126
  if (Object.prototype.hasOwnProperty.call(flags, "username")) {
889
1127
  current.username = requireDependency(deps, "normalizeTelegramBotUsername")(flags.username || "");
@@ -894,25 +1132,35 @@ async function editTelegramBot(ui, flags, deps) {
894
1132
  if (Object.prototype.hasOwnProperty.call(flags, "role-profile")) {
895
1133
  current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(flags["role-profile"] || "");
896
1134
  }
897
- if (Object.prototype.hasOwnProperty.call(flags, "client")) {
898
- current.client = requireDependency(deps, "normalizeLocalAIClientName")(flags.client || "", "");
1135
+ if (hasOwnFlag(flags, "client") || hasOwnFlag(flags, "ai-client")) {
1136
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(getAIClientFlag(flags), "");
899
1137
  }
900
- if (Object.prototype.hasOwnProperty.call(flags, "model")) {
901
- current.model = String(flags.model || "").trim();
1138
+ if (hasOwnFlag(flags, "model") || hasOwnFlag(flags, "ai-model")) {
1139
+ current.model = getAIModelFlag(flags);
902
1140
  }
903
- if (Object.prototype.hasOwnProperty.call(flags, "permission-mode")) {
904
- current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(flags["permission-mode"] || "", "");
1141
+ if (hasOwnFlag(flags, "permission-mode") || hasOwnFlag(flags, "ai-permission-mode")) {
1142
+ current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(getAIPermissionModeFlag(flags), "");
905
1143
  }
906
- if (Object.prototype.hasOwnProperty.call(flags, "reasoning-effort")) {
907
- current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(flags["reasoning-effort"] || "", "");
1144
+ if (hasOwnFlag(flags, "reasoning-effort") || hasOwnFlag(flags, "ai-reasoning-effort")) {
1145
+ current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(getAIReasoningEffortFlag(flags), "");
908
1146
  }
909
1147
  if (boolFromRaw(flags.default, false)) {
910
1148
  parsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
911
1149
  }
912
- let nextParsed = parsed;
913
- nextParsed = upsertTelegramEntry(nextParsed, current);
914
- const filePath = writeProviderEnvState("telegram", nextParsed, deps);
915
- process.stdout.write(`Saved Telegram bot entry "${current.key}" to ${filePath}\n`);
1150
+ saveTelegramBotEdit(parsed, selected, current, deps);
1151
+ return;
1152
+ }
1153
+ const editMode = await promptChoice(
1154
+ ui,
1155
+ `How do you want to edit Telegram bot "${current.key}"?`,
1156
+ [
1157
+ { value: "guided", label: "Guided edit (Recommended)", description: "Step-by-step prompts with numbered choices" },
1158
+ { value: "field_menu", label: "Field menu", description: "Choose one field at a time until save" },
1159
+ ],
1160
+ { defaultIndex: 0 },
1161
+ );
1162
+ if (editMode?.value === "guided") {
1163
+ await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
916
1164
  return;
917
1165
  }
918
1166
  while (true) {
@@ -936,16 +1184,7 @@ async function editTelegramBot(ui, flags, deps) {
936
1184
  );
937
1185
  if (!choice) return;
938
1186
  if (choice.value === "save") {
939
- let nextParsed = parsed;
940
- if (current.key !== selected.key) {
941
- nextParsed = removeTelegramEntry(nextParsed, selected.key);
942
- }
943
- nextParsed = upsertTelegramEntry(nextParsed, current);
944
- if (String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key) {
945
- nextParsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
946
- }
947
- const filePath = writeProviderEnvState("telegram", nextParsed, deps);
948
- process.stdout.write(`Saved Telegram bot entry "${current.key}" to ${filePath}\n`);
1187
+ saveTelegramBotEdit(parsed, selected, current, deps);
949
1188
  return;
950
1189
  }
951
1190
  if (choice.value === "server_bot_id") {
@@ -1194,6 +1433,8 @@ async function runBotSetup(ui, flags, deps) {
1194
1433
  { value: "add", label: "Add bot" },
1195
1434
  { value: "edit", label: "Edit bot" },
1196
1435
  { value: "remove", label: "Remove bot" },
1436
+ { value: "set_default", label: "Set default bot" },
1437
+ { value: "migrate", label: "Migrate legacy token" },
1197
1438
  { value: "verify", label: "Verify bot" },
1198
1439
  { value: "global", label: "Telegram global settings" },
1199
1440
  ];
@@ -1244,6 +1485,14 @@ async function runBotSetup(ui, flags, deps) {
1244
1485
  }
1245
1486
  return;
1246
1487
  }
1488
+ if (action.value === "set_default" && provider === "telegram") {
1489
+ await runBotSetDefault(ui, flags, deps);
1490
+ return;
1491
+ }
1492
+ if (action.value === "migrate" && provider === "telegram") {
1493
+ await runBotMigrate(ui, flags, deps);
1494
+ return;
1495
+ }
1247
1496
  if (action.value === "verify") {
1248
1497
  await verifyProviderEntry(ui, provider, flags, deps);
1249
1498
  return;
@@ -1344,6 +1593,77 @@ async function runBotVerify(ui, flags, deps) {
1344
1593
  await verifyProviderEntry(ui, provider, flags, deps);
1345
1594
  }
1346
1595
 
1596
+ async function runBotSetDefault(ui, flags, deps) {
1597
+ const provider = String(flags.provider || "").trim()
1598
+ ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
1599
+ : "telegram";
1600
+ if (provider !== "telegram") {
1601
+ throw new Error("bot set-default currently supports only --provider telegram");
1602
+ }
1603
+ const state = loadProviderEnvState("telegram", deps);
1604
+ const parsed = { ...state.parsed };
1605
+ const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
1606
+ const selected = nonInteractive
1607
+ ? findTelegramEntryByFlags(parsed, flags, deps)
1608
+ : await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot");
1609
+ if (!selected) {
1610
+ throw new Error("Telegram bot selector is required for set-default");
1611
+ }
1612
+ parsed.TELEGRAM_DEFAULT_BOT_KEY = selected.key;
1613
+ const filePath = writeProviderEnvState("telegram", parsed, deps);
1614
+ process.stdout.write(`Set TELEGRAM_DEFAULT_BOT_KEY=${selected.key} in ${filePath}\n`);
1615
+ }
1616
+
1617
+ async function runBotMigrate(ui, flags, deps) {
1618
+ const provider = String(flags.provider || "").trim()
1619
+ ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
1620
+ : "telegram";
1621
+ if (provider !== "telegram") {
1622
+ throw new Error("bot migrate currently supports only --provider telegram");
1623
+ }
1624
+ const state = loadProviderEnvState("telegram", deps);
1625
+ const parsed = { ...state.parsed };
1626
+ const legacyToken = String(parsed.TELEGRAM_BOT_TOKEN || "").trim();
1627
+ if (!legacyToken) {
1628
+ process.stdout.write("No legacy TELEGRAM_BOT_TOKEN value is set.\n");
1629
+ return;
1630
+ }
1631
+ const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
1632
+ const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
1633
+ const existingKeys = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key));
1634
+ const defaultKeyHint = normalizeBotKey(flags["bot-key"] || parsed.TELEGRAM_DEFAULT_BOT_KEY || "main", "main");
1635
+ const botKey = normalizeBotKey(
1636
+ nonInteractive
1637
+ ? String(flags["bot-key"] || defaultKeyHint).trim()
1638
+ : await promptRequiredLine(ui, "Local Telegram bot key for migrated entry", defaultKeyHint),
1639
+ defaultKeyHint,
1640
+ );
1641
+ if (existingKeys.has(botKey)) {
1642
+ throw new Error(`Telegram bot key "${botKey}" already exists`);
1643
+ }
1644
+ const nextParsed = upsertTelegramEntry(parsed, {
1645
+ key: botKey,
1646
+ serverBotID: getServerBotIDFlag(flags),
1647
+ username: requireDependency(deps, "normalizeTelegramBotUsername")(flags["bot-name"] || flags.username || ""),
1648
+ token: legacyToken,
1649
+ roleProfile: requireDependency(deps, "normalizeRunnerRoleProfileName")(flags["role-profile"] || ""),
1650
+ client: requireDependency(deps, "normalizeLocalAIClientName")(getAIClientFlag(flags), ""),
1651
+ model: getAIModelFlag(flags),
1652
+ permissionMode: requireDependency(deps, "normalizeLocalAIPermissionMode")(getAIPermissionModeFlag(flags), ""),
1653
+ reasoningEffort: requireDependency(deps, "normalizeLocalAIReasoningEffort")(getAIReasoningEffortFlag(flags), ""),
1654
+ });
1655
+ if (!hasNamedTelegramEntry(parsed, deps) || !String(nextParsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim()) {
1656
+ nextParsed.TELEGRAM_DEFAULT_BOT_KEY = botKey;
1657
+ }
1658
+ if (!boolFromRaw(flags["keep-legacy-token"], false)) {
1659
+ nextParsed.TELEGRAM_BOT_TOKEN = "";
1660
+ }
1661
+ const filePath = writeProviderEnvState("telegram", nextParsed, deps);
1662
+ process.stdout.write(
1663
+ `Migrated TELEGRAM_BOT_TOKEN to named entry "${botKey}" in ${filePath}${boolFromRaw(flags["keep-legacy-token"], false) ? " (legacy token preserved)" : ""}\n`,
1664
+ );
1665
+ }
1666
+
1347
1667
  async function runBotGlobal(ui, flags, deps) {
1348
1668
  const provider = String(flags.provider || "").trim()
1349
1669
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
@@ -1389,6 +1709,14 @@ export async function runBotCommand(argv, deps) {
1389
1709
  await runBotRemove(ui, flags, deps);
1390
1710
  return;
1391
1711
  }
1712
+ if (command === "set-default") {
1713
+ await runBotSetDefault(ui, flags, deps);
1714
+ return;
1715
+ }
1716
+ if (command === "migrate") {
1717
+ await runBotMigrate(ui, flags, deps);
1718
+ return;
1719
+ }
1392
1720
  if (command === "verify") {
1393
1721
  await runBotVerify(ui, flags, deps);
1394
1722
  return;
@@ -1397,7 +1725,7 @@ export async function runBotCommand(argv, deps) {
1397
1725
  await runBotGlobal(ui, flags, deps);
1398
1726
  return;
1399
1727
  }
1400
- throw new Error("bot requires a subcommand: setup | list | show | add | edit | remove | verify | global");
1728
+ throw new Error("bot requires a subcommand: setup | list | show | add | edit | remove | set-default | migrate | verify | global");
1401
1729
  } finally {
1402
1730
  ui.close();
1403
1731
  }
@@ -225,29 +225,36 @@ export async function runSelftestBotCommands(push, deps) {
225
225
  cliPath,
226
226
  args: [
227
227
  "bot", "add",
228
- "--provider", "telegram",
229
- "--non-interactive", "true",
230
228
  "--base-url", baseURL,
231
229
  "--timeout-seconds", "5",
232
- "--bot-key", "main_test",
233
- "--bot-id", mock.bots[0].id,
234
- "--token", "selftest-main-token",
235
- "--role-profile", "monitor",
236
- "--client", "codex",
237
- "--permission-mode", "read_only",
238
- "--reasoning-effort", "low",
239
- "--default", "true",
240
- "--verify", "true",
241
230
  ],
242
- env,
231
+ env: {
232
+ ...env,
233
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
234
+ "1", // provider: telegram
235
+ "1", // server bot: first listed bot
236
+ "main_test",
237
+ "selftest-main-token",
238
+ "y", // verify now
239
+ "", // keep verified username
240
+ "3", // role profile: monitor
241
+ "2", // ai client: codex
242
+ "gpt-5-codex",
243
+ "2", // permission: read_only
244
+ "2", // reasoning: low
245
+ "y", // set as default
246
+ ]),
247
+ },
243
248
  });
244
249
  const addState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
245
250
  push(
246
- "bot_add_creates_named_telegram_entry",
251
+ "bot_add_guided_creates_named_telegram_entry",
247
252
  String(addState.TELEGRAM_BOT_MAIN_TEST_SERVER_BOT_ID || "") === mock.bots[0].id
253
+ && String(addState.TELEGRAM_BOT_MAIN_TEST_USERNAME || "").trim().toLowerCase() === "monitorselftestbot"
248
254
  && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "") === "codex"
255
+ && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "") === "gpt-5-codex"
249
256
  && String(addState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
250
- `default=${String(addState.TELEGRAM_DEFAULT_BOT_KEY || "")} client=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")}`,
257
+ `default=${String(addState.TELEGRAM_DEFAULT_BOT_KEY || "")} client=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
251
258
  );
252
259
 
253
260
  const showResult = await runCLI({
@@ -268,6 +275,43 @@ export async function runSelftestBotCommands(push, deps) {
268
275
  `key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")}`,
269
276
  );
270
277
 
278
+ await runCLI({
279
+ cliPath,
280
+ args: ["bot", "edit"],
281
+ env: {
282
+ ...env,
283
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
284
+ "1", // provider: telegram
285
+ "1", // bot entry: main_test
286
+ "1", // edit mode: guided
287
+ "1", // keep server bot binding
288
+ "1", // keep username
289
+ "1", // keep token
290
+ "1", // keep role profile
291
+ "2", // change AI client
292
+ "4", // gemini
293
+ "2", // change AI model
294
+ "guided-gemini-pro",
295
+ "2", // change permission mode
296
+ "3", // workspace_write
297
+ "2", // change reasoning effort
298
+ "3", // medium
299
+ "1", // keep bot key
300
+ "1", // keep default setting
301
+ "y", // save
302
+ ]),
303
+ },
304
+ });
305
+ const guidedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
306
+ push(
307
+ "bot_edit_guided_prompts_update_ai_binding_fields",
308
+ String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "") === "gemini"
309
+ && String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "") === "guided-gemini-pro"
310
+ && String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_PERMISSION_MODE || "") === "workspace_write"
311
+ && String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_REASONING_EFFORT || "") === "medium",
312
+ `client=${String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(guidedState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
313
+ );
314
+
271
315
  await runCLI({
272
316
  cliPath,
273
317
  args: [
@@ -294,6 +338,23 @@ export async function runSelftestBotCommands(push, deps) {
294
338
  `client=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
295
339
  );
296
340
 
341
+ await runCLI({
342
+ cliPath,
343
+ args: [
344
+ "bot", "set-default",
345
+ "--provider", "telegram",
346
+ "--bot-key", "main_test",
347
+ "--non-interactive", "true",
348
+ ],
349
+ env,
350
+ });
351
+ const defaultState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
352
+ push(
353
+ "bot_set_default_updates_default_bot_key",
354
+ String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
355
+ `default=${String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "")}`,
356
+ );
357
+
297
358
  const verifyResult = await runCLI({
298
359
  cliPath,
299
360
  args: [
@@ -341,6 +402,80 @@ export async function runSelftestBotCommands(push, deps) {
341
402
  ensureArray(telegramEntry.entries).length === 0,
342
403
  `entries=${String(ensureArray(telegramEntry.entries).length)}`,
343
404
  );
405
+
406
+ await runCLI({
407
+ cliPath,
408
+ args: [
409
+ "bot", "add",
410
+ "--provider", "telegram",
411
+ "--non-interactive", "true",
412
+ "--base-url", baseURL,
413
+ "--timeout-seconds", "5",
414
+ "--bot-key", "explicit_test",
415
+ "--server-bot-id", mock.bots[0].id,
416
+ "--username", "MonitorSelftestBot",
417
+ "--token", "selftest-main-token",
418
+ "--role-profile", "monitor",
419
+ "--ai-client", "codex",
420
+ "--ai-model", "gpt-5-codex",
421
+ "--ai-permission-mode", "read_only",
422
+ "--ai-reasoning-effort", "low",
423
+ "--verify", "true",
424
+ ],
425
+ env,
426
+ });
427
+ const aliasAddState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
428
+ push(
429
+ "bot_add_accepts_ai_prefixed_option_aliases",
430
+ String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_SERVER_BOT_ID || "") === mock.bots[0].id
431
+ && String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_CLIENT || "") === "codex"
432
+ && String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_MODEL || "") === "gpt-5-codex"
433
+ && String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_PERMISSION_MODE || "") === "read_only"
434
+ && String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_REASONING_EFFORT || "") === "low",
435
+ `client=${String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_CLIENT || "")} model=${String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_MODEL || "")}`,
436
+ );
437
+
438
+ await runCLI({
439
+ cliPath,
440
+ args: [
441
+ "bot", "remove",
442
+ "--provider", "telegram",
443
+ "--bot-key", "explicit_test",
444
+ "--non-interactive", "true",
445
+ ],
446
+ env,
447
+ });
448
+
449
+ const migratedEnv = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
450
+ migratedEnv.TELEGRAM_BOT_TOKEN = "legacy-selftest-token";
451
+ fs.writeFileSync(
452
+ telegramEnvPath,
453
+ Object.entries(migratedEnv).map(([key, value]) => `${key}=${String(value ?? "")}`).join("\n") + "\n",
454
+ "utf8",
455
+ );
456
+ await runCLI({
457
+ cliPath,
458
+ args: [
459
+ "bot", "migrate",
460
+ "--provider", "telegram",
461
+ "--bot-key", "legacy_main",
462
+ "--bot-id", mock.bots[0].id,
463
+ "--bot-name", "MonitorSelftestBot",
464
+ "--role-profile", "monitor",
465
+ "--client", "codex",
466
+ "--permission-mode", "read_only",
467
+ "--reasoning-effort", "low",
468
+ "--non-interactive", "true",
469
+ ],
470
+ env,
471
+ });
472
+ const migratedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
473
+ push(
474
+ "bot_migrate_converts_legacy_token_to_named_entry",
475
+ String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "") === "legacy-selftest-token"
476
+ && String(migratedState.TELEGRAM_BOT_TOKEN || "") === "",
477
+ `legacy_named=${String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "")} fallback=${String(migratedState.TELEGRAM_BOT_TOKEN || "")}`,
478
+ );
344
479
  } catch (err) {
345
480
  push("bot_commands_smoke", false, String(err?.message || err));
346
481
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.63",
3
+ "version": "0.2.65",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [