metheus-governance-mcp-cli 0.2.64 → 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,6 +213,7 @@ 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
218
219
  metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main
@@ -224,6 +225,8 @@ metheus-governance-mcp-cli bot verify --provider telegram --bot-key main
224
225
  Behavior:
225
226
 
226
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.
227
230
  - Telegram supports named local bot entries with:
228
231
  - `SERVER_BOT_ID`
229
232
  - `USERNAME`
@@ -244,10 +247,10 @@ Non-interactive examples:
244
247
 
245
248
  ```bash
246
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
247
- 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
248
- 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
249
252
  metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main --non-interactive true
250
- metheus-governance-mcp-cli bot migrate --provider telegram --bot-key main --bot-id <server_bot_uuid> --bot-name <telegram_username> --role-profile monitor --client codex --permission-mode read_only --reasoning-effort low --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
251
254
  metheus-governance-mcp-cli bot remove --provider telegram --bot-key main --non-interactive true
252
255
  metheus-governance-mcp-cli bot verify --provider telegram --bot-key main --json true
253
256
  ```
@@ -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,8 +105,8 @@ 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>]`,
82
111
  ` ${cliName} bot set-default --provider telegram [--bot-key <key>] [--non-interactive <true|false>]`,
83
112
  ` ${cliName} bot migrate --provider telegram [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>] [--keep-legacy-token <true|false>] [--non-interactive <true|false>]`,
@@ -89,6 +118,27 @@ function printBotUsage(deps) {
89
118
  }
90
119
 
91
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
+ }
92
142
  return {
93
143
  ask(promptText) {
94
144
  return new Promise((resolve) => {
@@ -163,6 +213,19 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
163
213
  }
164
214
  }
165
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
+
166
229
  function loadProviderEnvState(provider, deps) {
167
230
  const ensureTemplate = requireDependency(deps, "ensureProviderEnvTemplate");
168
231
  const filePathResolver = requireDependency(deps, "providerEnvFilePath");
@@ -399,6 +462,175 @@ function findTelegramEntryByFlags(parsedEnv, flags, deps) {
399
462
  return null;
400
463
  }
401
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
+
402
634
  async function selectProvider(ui, initialProvider, deps) {
403
635
  const normalizeProvider = requireDependency(deps, "normalizeBotProvider");
404
636
  const provider = String(initialProvider || "").trim();
@@ -708,7 +940,7 @@ async function addTelegramBot(ui, flags, deps) {
708
940
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
709
941
  const serverBot = nonInteractive
710
942
  ? {
711
- botID: String(flags["server-bot-id"] || flags["bot-id"] || "").trim(),
943
+ botID: getServerBotIDFlag(flags),
712
944
  role: String(flags.role || "").trim(),
713
945
  name: "",
714
946
  }
@@ -783,27 +1015,27 @@ async function addTelegramBot(ui, flags, deps) {
783
1015
  );
784
1016
  const client = requireDependency(deps, "normalizeLocalAIClientName")(
785
1017
  nonInteractive
786
- ? String(flags.client || "").trim()
787
- : await promptAIClient(ui, deps, flags.client || ""),
1018
+ ? getAIClientFlag(flags)
1019
+ : await promptAIClient(ui, deps, getAIClientFlag(flags)),
788
1020
  "",
789
1021
  );
790
- 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));
791
1023
  const permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
792
1024
  nonInteractive
793
- ? String(flags["permission-mode"] || "").trim()
794
- : await promptPermissionMode(ui, String(flags["permission-mode"] || "").trim()),
1025
+ ? getAIPermissionModeFlag(flags)
1026
+ : await promptPermissionMode(ui, getAIPermissionModeFlag(flags)),
795
1027
  "",
796
1028
  );
797
1029
  const reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
798
1030
  nonInteractive
799
- ? String(flags["reasoning-effort"] || "").trim()
800
- : await promptReasoningEffort(ui, String(flags["reasoning-effort"] || "").trim()),
1031
+ ? getAIReasoningEffortFlag(flags)
1032
+ : await promptReasoningEffort(ui, getAIReasoningEffortFlag(flags)),
801
1033
  "",
802
1034
  );
803
1035
 
804
1036
  const nextParsed = upsertTelegramEntry(parsed, {
805
1037
  key: botKey,
806
- serverBotID: String(flags["server-bot-id"] || serverBot.botID || "").trim(),
1038
+ serverBotID: String(getServerBotIDFlag(flags) || serverBot.botID || "").trim(),
807
1039
  username,
808
1040
  token,
809
1041
  roleProfile,
@@ -888,8 +1120,8 @@ async function editTelegramBot(ui, flags, deps) {
888
1120
  }
889
1121
  let current = { ...selected };
890
1122
  if (nonInteractive) {
891
- if (flags["server-bot-id"] || flags["bot-id"]) {
892
- current.serverBotID = String(flags["server-bot-id"] || flags["bot-id"] || "").trim();
1123
+ if (getServerBotIDFlag(flags)) {
1124
+ current.serverBotID = getServerBotIDFlag(flags);
893
1125
  }
894
1126
  if (Object.prototype.hasOwnProperty.call(flags, "username")) {
895
1127
  current.username = requireDependency(deps, "normalizeTelegramBotUsername")(flags.username || "");
@@ -900,25 +1132,35 @@ async function editTelegramBot(ui, flags, deps) {
900
1132
  if (Object.prototype.hasOwnProperty.call(flags, "role-profile")) {
901
1133
  current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(flags["role-profile"] || "");
902
1134
  }
903
- if (Object.prototype.hasOwnProperty.call(flags, "client")) {
904
- current.client = requireDependency(deps, "normalizeLocalAIClientName")(flags.client || "", "");
1135
+ if (hasOwnFlag(flags, "client") || hasOwnFlag(flags, "ai-client")) {
1136
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(getAIClientFlag(flags), "");
905
1137
  }
906
- if (Object.prototype.hasOwnProperty.call(flags, "model")) {
907
- current.model = String(flags.model || "").trim();
1138
+ if (hasOwnFlag(flags, "model") || hasOwnFlag(flags, "ai-model")) {
1139
+ current.model = getAIModelFlag(flags);
908
1140
  }
909
- if (Object.prototype.hasOwnProperty.call(flags, "permission-mode")) {
910
- 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), "");
911
1143
  }
912
- if (Object.prototype.hasOwnProperty.call(flags, "reasoning-effort")) {
913
- 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), "");
914
1146
  }
915
1147
  if (boolFromRaw(flags.default, false)) {
916
1148
  parsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
917
1149
  }
918
- let nextParsed = parsed;
919
- nextParsed = upsertTelegramEntry(nextParsed, current);
920
- const filePath = writeProviderEnvState("telegram", nextParsed, deps);
921
- 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);
922
1164
  return;
923
1165
  }
924
1166
  while (true) {
@@ -942,16 +1184,7 @@ async function editTelegramBot(ui, flags, deps) {
942
1184
  );
943
1185
  if (!choice) return;
944
1186
  if (choice.value === "save") {
945
- let nextParsed = parsed;
946
- if (current.key !== selected.key) {
947
- nextParsed = removeTelegramEntry(nextParsed, selected.key);
948
- }
949
- nextParsed = upsertTelegramEntry(nextParsed, current);
950
- if (String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key) {
951
- nextParsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
952
- }
953
- const filePath = writeProviderEnvState("telegram", nextParsed, deps);
954
- process.stdout.write(`Saved Telegram bot entry "${current.key}" to ${filePath}\n`);
1187
+ saveTelegramBotEdit(parsed, selected, current, deps);
955
1188
  return;
956
1189
  }
957
1190
  if (choice.value === "server_bot_id") {
@@ -1410,14 +1643,14 @@ async function runBotMigrate(ui, flags, deps) {
1410
1643
  }
1411
1644
  const nextParsed = upsertTelegramEntry(parsed, {
1412
1645
  key: botKey,
1413
- serverBotID: String(flags["server-bot-id"] || flags["bot-id"] || "").trim(),
1646
+ serverBotID: getServerBotIDFlag(flags),
1414
1647
  username: requireDependency(deps, "normalizeTelegramBotUsername")(flags["bot-name"] || flags.username || ""),
1415
1648
  token: legacyToken,
1416
1649
  roleProfile: requireDependency(deps, "normalizeRunnerRoleProfileName")(flags["role-profile"] || ""),
1417
- client: requireDependency(deps, "normalizeLocalAIClientName")(flags.client || "", ""),
1418
- model: String(flags.model || "").trim(),
1419
- permissionMode: requireDependency(deps, "normalizeLocalAIPermissionMode")(flags["permission-mode"] || "", ""),
1420
- reasoningEffort: requireDependency(deps, "normalizeLocalAIReasoningEffort")(flags["reasoning-effort"] || "", ""),
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), ""),
1421
1654
  });
1422
1655
  if (!hasNamedTelegramEntry(parsed, deps) || !String(nextParsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim()) {
1423
1656
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = botKey;
@@ -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: [
@@ -359,6 +403,49 @@ export async function runSelftestBotCommands(push, deps) {
359
403
  `entries=${String(ensureArray(telegramEntry.entries).length)}`,
360
404
  );
361
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
+
362
449
  const migratedEnv = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
363
450
  migratedEnv.TELEGRAM_BOT_TOKEN = "legacy-selftest-token";
364
451
  fs.writeFileSync(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.64",
3
+ "version": "0.2.65",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [