metheus-governance-mcp-cli 0.2.70 → 0.2.71

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
@@ -231,8 +231,8 @@ Behavior:
231
231
  - For Telegram, the local env key is auto-generated from the matched server bot name or verified username, so you do not have to invent a separate local nickname first.
232
232
  - For Telegram, the CLI tries to match the verified bot identity against the server `me/bots` list first. If the server exposes one logical bot name with multiple roles such as `approval`, `worker`, `review`, and `monitor`, the CLI does not ask you to choose one UUID. It binds by server bot name and keeps the local role/AI fields empty so runtime can use the server bot role for each route.
233
233
  - When the Telegram username matches exactly one server bot role, the CLI still auto-fills the local `role_profile` and blank AI defaults from your local `bot-runner.json` `role_profiles` mapping.
234
- - `bot edit` without flags starts a guided numbered flow: provider -> bot entry -> guided edit -> step-by-step field choices.
235
- - In guided `bot edit`, changing the bound Telegram responsibility can also auto-fill blank local AI fields from the selected server role and skip redundant role/AI prompts.
234
+ - `bot edit` without flags now uses the same sequential flow every time: provider -> bot entry -> username/token review -> AI field choices -> default choice -> save.
235
+ - In the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically. It no longer asks you to pick `approval / worker / review / monitor` or a server bot UUID first.
236
236
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
237
237
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
238
238
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
@@ -147,7 +147,8 @@ function printBotUsage(deps) {
147
147
  ` - bot add without flags uses the shortest practical guided flow: provider -> token -> verify -> optional username fallback -> optional default bot.`,
148
148
  ` - in the normal Telegram path, bot add does not ask for a local bot key or a server role/profile choice.`,
149
149
  ` - server bot name/UUID is the source of truth; local key is auto-derived unless you intentionally override it with --bot-key.`,
150
- ` - bot edit without flags asks for provider, existing entry, then a numbered edit flow.`,
150
+ ` - bot edit without flags asks for provider, existing entry, then walks field-by-field in a numbered flow.`,
151
+ ` - in the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically instead of asking you to pick a server role/profile UUID first.`,
151
152
  ` - bot set-default / bot verify / bot remove also support guided numbered selection when run without flags.`,
152
153
  "",
153
154
  ].join("\n"),
@@ -594,58 +595,9 @@ function buildDerivedTelegramBotKey(parsedEnv, deps, preferredValues, { excludeK
594
595
 
595
596
  async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps) {
596
597
  let serverRoleAutoResolved = "";
597
- const aiFlagOverrides = hasOwnFlag(flags, "client")
598
- || hasOwnFlag(flags, "ai-client")
599
- || hasOwnFlag(flags, "model")
600
- || hasOwnFlag(flags, "ai-model")
601
- || hasOwnFlag(flags, "permission-mode")
602
- || hasOwnFlag(flags, "ai-permission-mode")
603
- || hasOwnFlag(flags, "reasoning-effort")
604
- || hasOwnFlag(flags, "ai-reasoning-effort");
605
- const bindingAction = await promptKeepChangeClear(ui, "Server bot binding", {
606
- allowClear: true,
607
- defaultValue: current.serverBotID ? "keep" : "change",
608
- });
609
- if (bindingAction === "change") {
610
- const serverBot = await chooseServerBot(
611
- ui,
612
- "telegram",
613
- flags["base-url"] || deps.defaultSiteURL,
614
- intFromRaw(flags["timeout-seconds"], 15) || 15,
615
- deps,
616
- {
617
- preferredUsername: current.username,
618
- preferredName: current.username,
619
- },
620
- );
621
- if (serverBot.matchMode === "group") {
622
- current.serverBotID = "";
623
- current.__preferServerIdentity = true;
624
- if (!String(flags["role-profile"] || "").trim()) {
625
- current.roleProfile = "";
626
- }
627
- if (!aiFlagOverrides) {
628
- current.client = "";
629
- current.model = "";
630
- current.permissionMode = "";
631
- current.reasoningEffort = "";
632
- }
633
- serverRoleAutoResolved = "__server_role_group__";
634
- process.stdout.write(
635
- `Matched server Telegram bot "${serverBot.name || current.username || current.key}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Runtime will use the server role, so local role/AI fields can stay empty.\n`,
636
- );
637
- } else {
638
- current.serverBotID = serverBot.botID;
639
- current.__preferServerIdentity = true;
640
- if (!current.roleProfile && serverBot.role) {
641
- current.roleProfile = serverBot.role;
642
- }
643
- serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim();
644
- applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
645
- }
646
- } else if (bindingAction === "clear") {
647
- current.serverBotID = "";
648
- current.__preferServerIdentity = false;
598
+ if (current.serverBotID || current.__preferServerIdentity) {
599
+ current.__preferServerIdentity = true;
600
+ serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
649
601
  }
650
602
 
651
603
  const usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
@@ -683,7 +635,29 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
683
635
  }
684
636
  }
685
637
 
686
- if (!serverRoleAutoResolved) {
638
+ if (!current.serverBotID || current.__preferServerIdentity || usernameAction === "change" || tokenAction === "change") {
639
+ const serverBot = await autoResolveTelegramServerBot(current, flags, deps);
640
+ if (serverBot.matchMode === "group") {
641
+ current.serverBotID = "";
642
+ current.__preferServerIdentity = true;
643
+ current.roleProfile = "";
644
+ serverRoleAutoResolved = "__server_role_group__";
645
+ process.stdout.write(
646
+ `Using server Telegram bot "${serverBot.name || current.username || current.key}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Runtime will use the server role automatically.\n`,
647
+ );
648
+ } else if (String(serverBot.botID || "").trim()) {
649
+ current.serverBotID = String(serverBot.botID || "").trim();
650
+ current.__preferServerIdentity = true;
651
+ current.roleProfile = String(serverBot.role || current.roleProfile || "").trim();
652
+ serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim() || "__server_binding__";
653
+ applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
654
+ } else if (current.serverBotID) {
655
+ current.__preferServerIdentity = true;
656
+ serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
657
+ }
658
+ }
659
+
660
+ if (!current.serverBotID && !current.__preferServerIdentity) {
687
661
  const roleAction = await promptKeepChangeClear(ui, "Role profile", {
688
662
  allowClear: true,
689
663
  defaultValue: current.roleProfile ? "keep" : "change",
@@ -696,70 +670,55 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
696
670
  } else if (roleAction === "clear") {
697
671
  current.roleProfile = "";
698
672
  }
673
+ }
699
674
 
700
- const clientAction = await promptKeepChangeClear(ui, "AI client", {
701
- allowClear: true,
702
- defaultValue: current.client ? "keep" : "change",
703
- });
704
- if (clientAction === "change") {
705
- current.client = requireDependency(deps, "normalizeLocalAIClientName")(
706
- await promptAIClient(ui, deps, current.client),
707
- "",
708
- );
709
- } else if (clientAction === "clear") {
710
- current.client = "";
711
- }
712
-
713
- const modelAction = await promptKeepChangeClear(ui, "AI model", {
714
- allowClear: true,
715
- defaultValue: current.model ? "keep" : "change",
716
- });
717
- if (modelAction === "change") {
718
- current.model = await promptLine(ui, "AI model", current.model);
719
- } else if (modelAction === "clear") {
720
- current.model = "";
721
- }
675
+ const clientAction = await promptKeepChangeClear(ui, "AI client", {
676
+ allowClear: true,
677
+ defaultValue: current.client ? "keep" : "change",
678
+ });
679
+ if (clientAction === "change") {
680
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(
681
+ await promptAIClient(ui, deps, current.client),
682
+ "",
683
+ );
684
+ } else if (clientAction === "clear") {
685
+ current.client = "";
686
+ }
722
687
 
723
- const permissionAction = await promptKeepChangeClear(ui, "AI permission mode", {
724
- allowClear: true,
725
- defaultValue: current.permissionMode ? "keep" : "change",
726
- });
727
- if (permissionAction === "change") {
728
- current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
729
- await promptPermissionMode(ui, current.permissionMode),
730
- "",
731
- );
732
- } else if (permissionAction === "clear") {
733
- current.permissionMode = "";
734
- }
688
+ const modelAction = await promptKeepChangeClear(ui, "AI model", {
689
+ allowClear: true,
690
+ defaultValue: current.model ? "keep" : "change",
691
+ });
692
+ if (modelAction === "change") {
693
+ current.model = await promptLine(ui, "AI model", current.model);
694
+ } else if (modelAction === "clear") {
695
+ current.model = "";
696
+ }
735
697
 
736
- const reasoningAction = await promptKeepChangeClear(ui, "AI reasoning effort", {
737
- allowClear: true,
738
- defaultValue: current.reasoningEffort ? "keep" : "change",
739
- });
740
- if (reasoningAction === "change") {
741
- current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
742
- await promptReasoningEffort(ui, current.reasoningEffort),
743
- "",
744
- );
745
- } else if (reasoningAction === "clear") {
746
- current.reasoningEffort = "";
747
- }
698
+ const permissionAction = await promptKeepChangeClear(ui, "AI permission mode", {
699
+ allowClear: true,
700
+ defaultValue: current.permissionMode ? "keep" : "change",
701
+ });
702
+ if (permissionAction === "change") {
703
+ current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
704
+ await promptPermissionMode(ui, current.permissionMode),
705
+ "",
706
+ );
707
+ } else if (permissionAction === "clear") {
708
+ current.permissionMode = "";
748
709
  }
749
710
 
750
- const botKeyAction = await promptKeepChangeClear(ui, "Local bot key", {
751
- allowClear: false,
752
- defaultValue: "keep",
711
+ const reasoningAction = await promptKeepChangeClear(ui, "AI reasoning effort", {
712
+ allowClear: true,
713
+ defaultValue: current.reasoningEffort ? "keep" : "change",
753
714
  });
754
- if (botKeyAction === "change") {
755
- const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
756
- let nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", current.key), current.key);
757
- const existing = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key).filter((key) => key !== current.key));
758
- while (existing.has(nextKey)) {
759
- process.stdout.write(`Telegram bot key "${nextKey}" already exists.\n`);
760
- nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", `${nextKey}_2`), `${nextKey}_2`);
761
- }
762
- current.key = nextKey;
715
+ if (reasoningAction === "change") {
716
+ current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
717
+ await promptReasoningEffort(ui, current.reasoningEffort),
718
+ "",
719
+ );
720
+ } else if (reasoningAction === "clear") {
721
+ current.reasoningEffort = "";
763
722
  }
764
723
 
765
724
  const defaultChoice = await promptChoice(
@@ -1339,6 +1298,43 @@ async function verifyTelegramTokenCandidate(provider, token, apiBaseURL, timeout
1339
1298
  return verifier(provider, buildTemporaryTelegramEnvConfig({ token, apiBaseURL }), timeoutSeconds);
1340
1299
  }
1341
1300
 
1301
+ async function autoResolveTelegramServerBot(current, flags, deps) {
1302
+ const existingBotID = String(current?.serverBotID || "").trim();
1303
+ const preferredUsername = String(current?.username || "").trim();
1304
+ if (!preferredUsername) {
1305
+ return {
1306
+ botID: existingBotID,
1307
+ role: String(current?.roleProfile || "").trim(),
1308
+ name: "",
1309
+ roles: [],
1310
+ matchMode: existingBotID ? "existing" : "blank",
1311
+ };
1312
+ }
1313
+ try {
1314
+ return await resolveServerBotForNonInteractive(
1315
+ "telegram",
1316
+ {
1317
+ "bot-name": preferredUsername,
1318
+ "base-url": flags["base-url"] || deps.defaultSiteURL,
1319
+ "timeout-seconds": intFromRaw(flags["timeout-seconds"], 15) || 15,
1320
+ },
1321
+ deps,
1322
+ {
1323
+ preferredUsername,
1324
+ preferredName: preferredUsername,
1325
+ },
1326
+ );
1327
+ } catch {
1328
+ return {
1329
+ botID: existingBotID,
1330
+ role: String(current?.roleProfile || "").trim(),
1331
+ name: "",
1332
+ roles: [],
1333
+ matchMode: existingBotID ? "existing" : "blank",
1334
+ };
1335
+ }
1336
+ }
1337
+
1342
1338
  async function addTelegramBot(ui, flags, deps) {
1343
1339
  const state = loadProviderEnvState("telegram", deps);
1344
1340
  const parsed = { ...state.parsed };
@@ -1624,122 +1620,7 @@ async function editTelegramBot(ui, flags, deps) {
1624
1620
  saveTelegramBotEdit(parsed, selected, current, deps);
1625
1621
  return;
1626
1622
  }
1627
- const editMode = await promptChoice(
1628
- ui,
1629
- `How do you want to edit Telegram bot "${current.key}"?`,
1630
- [
1631
- { value: "guided", label: "Guided edit (Recommended)", description: "Step-by-step prompts with numbered choices" },
1632
- { value: "field_menu", label: "Field menu", description: "Choose one field at a time until save" },
1633
- ],
1634
- { defaultIndex: 0 },
1635
- );
1636
- if (editMode?.value === "guided") {
1637
- await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
1638
- return;
1639
- }
1640
- while (true) {
1641
- const choice = await promptChoice(
1642
- ui,
1643
- `Edit Telegram bot "${current.key}"`,
1644
- [
1645
- { value: "server_bot_id", label: "Server bot UUID", description: current.serverBotID || "-" },
1646
- { value: "username", label: "Telegram username", description: current.username ? `@${current.username}` : "-" },
1647
- { value: "token", label: "Telegram token", description: current.token ? maskSecret(current.token) : "-" },
1648
- { value: "role_profile", label: "Role profile", description: current.roleProfile || "-" },
1649
- { value: "client", label: "AI client", description: current.client || "-" },
1650
- { value: "model", label: "AI model", description: current.model || "-" },
1651
- { value: "permission", label: "AI permission mode", description: current.permissionMode || "-" },
1652
- { value: "reasoning", label: "AI reasoning effort", description: current.reasoningEffort || "-" },
1653
- { value: "bot_key", label: "Local bot key", description: current.key },
1654
- { value: "set_default", label: "Set as default bot", description: boolFromRaw(flags.default, false) || String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === current.key ? "yes" : "no" },
1655
- { value: "save", label: "Save changes" },
1656
- ],
1657
- { defaultIndex: 0 },
1658
- );
1659
- if (!choice) return;
1660
- if (choice.value === "save") {
1661
- saveTelegramBotEdit(parsed, selected, current, deps);
1662
- return;
1663
- }
1664
- if (choice.value === "server_bot_id") {
1665
- const serverBot = await chooseServerBot(
1666
- ui,
1667
- "telegram",
1668
- flags["base-url"] || deps.defaultSiteURL,
1669
- intFromRaw(flags["timeout-seconds"], 15) || 15,
1670
- deps,
1671
- {
1672
- preferredUsername: current.username,
1673
- preferredName: current.username,
1674
- },
1675
- );
1676
- if (serverBot.matchMode === "group") {
1677
- current.serverBotID = "";
1678
- process.stdout.write(
1679
- `Matched server Telegram bot "${serverBot.name || current.username || current.key}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Keep role/AI fields empty if you want runtime role-based resolution.\n`,
1680
- );
1681
- } else {
1682
- current.serverBotID = serverBot.botID;
1683
- if (!current.roleProfile && serverBot.role) {
1684
- current.roleProfile = serverBot.role;
1685
- }
1686
- }
1687
- } else if (choice.value === "username") {
1688
- current.username = requireDependency(deps, "normalizeTelegramBotUsername")(
1689
- await promptLine(ui, "Telegram username (without @)", current.username),
1690
- "",
1691
- );
1692
- } else if (choice.value === "token") {
1693
- current.token = await promptRequiredLine(ui, "Telegram bot token", current.token);
1694
- if (await promptYesNo(ui, "Verify updated token now?", true)) {
1695
- const verifyResult = await verifyTelegramTokenCandidate(
1696
- "telegram",
1697
- current.token,
1698
- String(parsed.TELEGRAM_API_BASE_URL || "").trim(),
1699
- intFromRaw(flags["timeout-seconds"], 15) || 15,
1700
- deps,
1701
- );
1702
- process.stdout.write(`Verify: ${verifyResult.ok ? "OK" : "FAIL"}${verifyResult.detail ? ` - ${verifyResult.detail}` : ""}\n`);
1703
- const maybeUsername = extractVerifiedTelegramUsername(verifyResult.detail);
1704
- if (verifyResult.ok && maybeUsername && !current.username) {
1705
- current.username = maybeUsername;
1706
- }
1707
- }
1708
- } else if (choice.value === "role_profile") {
1709
- current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
1710
- await promptTelegramRoleProfile(ui, deps, current.roleProfile),
1711
- );
1712
- } else if (choice.value === "client") {
1713
- current.client = requireDependency(deps, "normalizeLocalAIClientName")(
1714
- await promptAIClient(ui, deps, current.client),
1715
- "",
1716
- );
1717
- } else if (choice.value === "model") {
1718
- current.model = await promptLine(ui, "AI model", current.model);
1719
- } else if (choice.value === "permission") {
1720
- current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
1721
- await promptPermissionMode(ui, current.permissionMode),
1722
- "",
1723
- );
1724
- } else if (choice.value === "reasoning") {
1725
- current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
1726
- await promptReasoningEffort(ui, current.reasoningEffort),
1727
- "",
1728
- );
1729
- } else if (choice.value === "bot_key") {
1730
- const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
1731
- let nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", current.key), current.key);
1732
- const existing = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key).filter((key) => key !== current.key));
1733
- while (existing.has(nextKey)) {
1734
- process.stdout.write(`Telegram bot key "${nextKey}" already exists.\n`);
1735
- nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", `${nextKey}_2`), `${nextKey}_2`);
1736
- }
1737
- current.key = nextKey;
1738
- } else if (choice.value === "set_default") {
1739
- parsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
1740
- process.stdout.write(`Default Telegram bot will be "${current.key}".\n`);
1741
- }
1742
- }
1623
+ await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
1743
1624
  }
1744
1625
 
1745
1626
  async function removeTelegramBot(ui, deps) {
@@ -381,11 +381,8 @@ export async function runSelftestBotCommands(push, deps) {
381
381
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
382
382
  "1", // provider: telegram
383
383
  "1", // bot entry: @monitorselftestbot
384
- "1", // edit mode: guided
385
- "1", // keep server bot binding
386
384
  "1", // keep username
387
385
  "1", // keep token
388
- "1", // keep role profile
389
386
  "2", // change AI client
390
387
  "4", // gemini
391
388
  "2", // change AI model
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.70",
3
+ "version": "0.2.71",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [