metheus-governance-mcp-cli 0.2.70 → 0.2.72

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,9 @@ 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 -> grouped server-role review when needed -> AI field choices -> default choice -> save.
235
+ - if one server bot name maps to multiple server roles, `bot edit` keeps the Telegram env entry bound to the server identity and lets you review the local `role_profiles` for each detected role instead of forcing one role/profile UUID choice up front.
236
+ - 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
237
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
237
238
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
238
239
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
package/cli.mjs CHANGED
@@ -3362,6 +3362,7 @@ function buildBotCommandDeps() {
3362
3362
  loadProviderEnvConfig,
3363
3363
  verifyLocalProviderToken,
3364
3364
  loadBotRunnerConfig,
3365
+ saveBotRunnerConfig,
3365
3366
  listServerBots: async ({ provider, baseURL, timeoutSeconds }) => {
3366
3367
  const authFlowDeps = buildAuthFlowDeps();
3367
3368
  const resolved = resolveCurrentAccessToken(authFlowDeps);
@@ -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,11 @@ 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
+ let groupedServerRoles = [];
599
+ let groupedServerName = "";
600
+ if (current.serverBotID || current.__preferServerIdentity) {
601
+ current.__preferServerIdentity = true;
602
+ serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
649
603
  }
650
604
 
651
605
  const usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
@@ -683,7 +637,35 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
683
637
  }
684
638
  }
685
639
 
686
- if (!serverRoleAutoResolved) {
640
+ if (!current.serverBotID || current.__preferServerIdentity || usernameAction === "change" || tokenAction === "change") {
641
+ const serverBot = await autoResolveTelegramServerBot(current, flags, deps);
642
+ if (serverBot.matchMode === "group") {
643
+ current.serverBotID = "";
644
+ current.__preferServerIdentity = true;
645
+ current.roleProfile = "";
646
+ current.client = "";
647
+ current.model = "";
648
+ current.permissionMode = "";
649
+ current.reasoningEffort = "";
650
+ serverRoleAutoResolved = "__server_role_group__";
651
+ groupedServerRoles = preferredRoleSort(serverBot.roles);
652
+ groupedServerName = String(serverBot.name || current.username || current.key).trim();
653
+ process.stdout.write(
654
+ `Using server Telegram bot "${groupedServerName}" with roles: ${groupedServerRoles.join(", ")}. Runtime will use the server role automatically.\n`,
655
+ );
656
+ } else if (String(serverBot.botID || "").trim()) {
657
+ current.serverBotID = String(serverBot.botID || "").trim();
658
+ current.__preferServerIdentity = true;
659
+ current.roleProfile = String(serverBot.role || current.roleProfile || "").trim();
660
+ serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim() || "__server_binding__";
661
+ applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
662
+ } else if (current.serverBotID) {
663
+ current.__preferServerIdentity = true;
664
+ serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
665
+ }
666
+ }
667
+
668
+ if (!current.serverBotID && !current.__preferServerIdentity) {
687
669
  const roleAction = await promptKeepChangeClear(ui, "Role profile", {
688
670
  allowClear: true,
689
671
  defaultValue: current.roleProfile ? "keep" : "change",
@@ -696,7 +678,18 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
696
678
  } else if (roleAction === "clear") {
697
679
  current.roleProfile = "";
698
680
  }
681
+ }
699
682
 
683
+ if (serverRoleAutoResolved === "__server_role_group__") {
684
+ await maybePromptGroupedServerRoleProfiles(
685
+ ui,
686
+ {
687
+ name: groupedServerName,
688
+ roles: groupedServerRoles,
689
+ },
690
+ deps,
691
+ );
692
+ } else {
700
693
  const clientAction = await promptKeepChangeClear(ui, "AI client", {
701
694
  allowClear: true,
702
695
  defaultValue: current.client ? "keep" : "change",
@@ -747,21 +740,6 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
747
740
  }
748
741
  }
749
742
 
750
- const botKeyAction = await promptKeepChangeClear(ui, "Local bot key", {
751
- allowClear: false,
752
- defaultValue: "keep",
753
- });
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;
763
- }
764
-
765
743
  const defaultChoice = await promptChoice(
766
744
  ui,
767
745
  "Default Telegram bot setting",
@@ -1326,6 +1304,144 @@ function applyRoleProfileDefaults(entry, defaults, { overwrite = false } = {}) {
1326
1304
  }
1327
1305
  }
1328
1306
 
1307
+ function preferredRoleSort(roles) {
1308
+ const order = ["monitor", "review", "worker", "approval"];
1309
+ return ensureArray(roles).slice().sort((left, right) => {
1310
+ const leftText = String(left || "").trim();
1311
+ const rightText = String(right || "").trim();
1312
+ const leftIndex = order.indexOf(leftText);
1313
+ const rightIndex = order.indexOf(rightText);
1314
+ if (leftIndex >= 0 && rightIndex >= 0) return leftIndex - rightIndex;
1315
+ if (leftIndex >= 0) return -1;
1316
+ if (rightIndex >= 0) return 1;
1317
+ return leftText.localeCompare(rightText);
1318
+ });
1319
+ }
1320
+
1321
+ function currentRoleProfileState(roleName, deps) {
1322
+ const normalizedRole = requireDependency(deps, "normalizeRunnerRoleProfileName")(roleName || "");
1323
+ const defaults = resolveRoleProfileDefaults(normalizedRole, deps);
1324
+ const config = requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true });
1325
+ const profile = safeObject(safeObject(config.roleProfiles || {})[normalizedRole]);
1326
+ return {
1327
+ role: normalizedRole,
1328
+ client: requireDependency(deps, "normalizeLocalAIClientName")(profile.client || defaults.client || "", ""),
1329
+ model: String(profile.model || defaults.model || "").trim(),
1330
+ permissionMode: requireDependency(deps, "normalizeLocalAIPermissionMode")(
1331
+ profile.permission_mode || profile.permissionMode || defaults.permissionMode || "",
1332
+ "",
1333
+ ),
1334
+ reasoningEffort: requireDependency(deps, "normalizeLocalAIReasoningEffort")(
1335
+ profile.reasoning_effort || profile.reasoningEffort || defaults.reasoningEffort || "",
1336
+ "",
1337
+ ),
1338
+ };
1339
+ }
1340
+
1341
+ function formatRoleProfileSummary(profile) {
1342
+ const current = safeObject(profile);
1343
+ return [
1344
+ current.client ? `client:${current.client}` : "client:(blank)",
1345
+ current.model ? `model:${current.model}` : "model:(blank)",
1346
+ current.permissionMode ? `permission:${current.permissionMode}` : "permission:(blank)",
1347
+ current.reasoningEffort ? `reasoning:${current.reasoningEffort}` : "reasoning:(blank)",
1348
+ ].join(" | ");
1349
+ }
1350
+
1351
+ function persistRoleProfileState(profile, deps) {
1352
+ const current = safeObject(profile);
1353
+ const role = requireDependency(deps, "normalizeRunnerRoleProfileName")(current.role || "");
1354
+ if (!role) return null;
1355
+ const config = requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true });
1356
+ const nextRoleProfiles = {
1357
+ ...safeObject(config.roleProfiles || {}),
1358
+ [role]: {
1359
+ client: String(current.client || "").trim(),
1360
+ model: String(current.model || "").trim(),
1361
+ permission_mode: String(current.permissionMode || "").trim(),
1362
+ reasoning_effort: String(current.reasoningEffort || "").trim(),
1363
+ },
1364
+ };
1365
+ const nextConfig = {
1366
+ ...config,
1367
+ roleProfiles: nextRoleProfiles,
1368
+ };
1369
+ return requireDependency(deps, "saveBotRunnerConfig")(nextConfig, config.filePath);
1370
+ }
1371
+
1372
+ async function promptRoleExecutionProfile(ui, roleName, deps) {
1373
+ const current = currentRoleProfileState(roleName, deps);
1374
+ const action = await promptChoice(
1375
+ ui,
1376
+ `Role "${current.role}" local execution profile`,
1377
+ [
1378
+ {
1379
+ value: "keep",
1380
+ label: "Keep current settings",
1381
+ description: formatRoleProfileSummary(current),
1382
+ },
1383
+ {
1384
+ value: "edit",
1385
+ label: "Edit settings",
1386
+ description: "change AI client, model, permission, and reasoning",
1387
+ },
1388
+ ],
1389
+ { defaultIndex: 0 },
1390
+ );
1391
+ if (action?.value !== "edit") {
1392
+ return { changed: false, filePath: "" };
1393
+ }
1394
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(
1395
+ await promptAIClient(ui, deps, current.client),
1396
+ "",
1397
+ );
1398
+ current.model = await promptLine(ui, `AI model for role "${current.role}"`, current.model);
1399
+ current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
1400
+ await promptPermissionMode(ui, current.permissionMode),
1401
+ "",
1402
+ );
1403
+ current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
1404
+ await promptReasoningEffort(ui, current.reasoningEffort),
1405
+ "",
1406
+ );
1407
+ const filePath = persistRoleProfileState(current, deps);
1408
+ if (filePath) {
1409
+ process.stdout.write(`Saved local execution profile for role "${current.role}" to ${filePath}\n`);
1410
+ }
1411
+ return { changed: true, filePath };
1412
+ }
1413
+
1414
+ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
1415
+ const roles = preferredRoleSort(ensureArray(serverBot?.roles).filter(Boolean));
1416
+ if (roles.length <= 1) {
1417
+ return false;
1418
+ }
1419
+ const editChoice = await promptChoice(
1420
+ ui,
1421
+ `Server bot "${String(serverBot?.name || "").trim() || "telegram"}" uses multiple roles. What should happen to the local execution settings?`,
1422
+ [
1423
+ {
1424
+ value: "keep",
1425
+ label: "Keep current role settings",
1426
+ description: "use the existing role_profiles defaults as-is",
1427
+ },
1428
+ {
1429
+ value: "review",
1430
+ label: "Review role settings",
1431
+ description: "check each role and change AI client/model/permission/reasoning when needed",
1432
+ },
1433
+ ],
1434
+ { defaultIndex: 0 },
1435
+ );
1436
+ if (editChoice?.value !== "review") {
1437
+ return false;
1438
+ }
1439
+ for (const role of roles) {
1440
+ await promptRoleExecutionProfile(ui, role, deps);
1441
+ }
1442
+ return true;
1443
+ }
1444
+
1329
1445
  function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
1330
1446
  return {
1331
1447
  ok: true,
@@ -1339,6 +1455,46 @@ async function verifyTelegramTokenCandidate(provider, token, apiBaseURL, timeout
1339
1455
  return verifier(provider, buildTemporaryTelegramEnvConfig({ token, apiBaseURL }), timeoutSeconds);
1340
1456
  }
1341
1457
 
1458
+ async function autoResolveTelegramServerBot(current, flags, deps) {
1459
+ const existingBotID = String(current?.serverBotID || "").trim();
1460
+ const preferredIdentity = firstNonEmptyString([
1461
+ current?.username,
1462
+ current?.key,
1463
+ ]);
1464
+ if (!preferredIdentity) {
1465
+ return {
1466
+ botID: existingBotID,
1467
+ role: String(current?.roleProfile || "").trim(),
1468
+ name: "",
1469
+ roles: [],
1470
+ matchMode: existingBotID ? "existing" : "blank",
1471
+ };
1472
+ }
1473
+ try {
1474
+ return await resolveServerBotForNonInteractive(
1475
+ "telegram",
1476
+ {
1477
+ "bot-name": preferredIdentity,
1478
+ "base-url": flags["base-url"] || deps.defaultSiteURL,
1479
+ "timeout-seconds": intFromRaw(flags["timeout-seconds"], 15) || 15,
1480
+ },
1481
+ deps,
1482
+ {
1483
+ preferredUsername: preferredIdentity,
1484
+ preferredName: preferredIdentity,
1485
+ },
1486
+ );
1487
+ } catch {
1488
+ return {
1489
+ botID: existingBotID,
1490
+ role: String(current?.roleProfile || "").trim(),
1491
+ name: "",
1492
+ roles: [],
1493
+ matchMode: existingBotID ? "existing" : "blank",
1494
+ };
1495
+ }
1496
+ }
1497
+
1342
1498
  async function addTelegramBot(ui, flags, deps) {
1343
1499
  const state = loadProviderEnvState("telegram", deps);
1344
1500
  const parsed = { ...state.parsed };
@@ -1624,122 +1780,7 @@ async function editTelegramBot(ui, flags, deps) {
1624
1780
  saveTelegramBotEdit(parsed, selected, current, deps);
1625
1781
  return;
1626
1782
  }
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
- }
1783
+ await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
1743
1784
  }
1744
1785
 
1745
1786
  async function removeTelegramBot(ui, deps) {
@@ -341,6 +341,51 @@ export async function runSelftestBotCommands(push, deps) {
341
341
  && String(groupedState.TELEGRAM_BOT_RYOAI_BOT_AI_PERMISSION_MODE || "") === "",
342
342
  `username=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_USERNAME || "")} server_bot_id=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_SERVER_BOT_ID || "")} role=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_ROLE_PROFILE || "")}`,
343
343
  );
344
+
345
+ await runCLI({
346
+ cliPath,
347
+ args: [
348
+ "bot", "edit",
349
+ "--base-url", `http://127.0.0.1:${groupedMock.port}`,
350
+ "--timeout-seconds", "5",
351
+ ],
352
+ env: {
353
+ ...env,
354
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
355
+ "1", // provider: telegram
356
+ "2", // bot entry: ryoai_bot
357
+ "1", // keep username
358
+ "1", // keep token
359
+ "2", // review grouped server role settings
360
+ "1", // monitor: keep current role profile settings
361
+ "1", // review: keep current role profile settings
362
+ "2", // worker: edit settings
363
+ "3", // worker AI client: claude
364
+ "worker-sonnet-4",
365
+ "4", // worker permission: danger_full_access
366
+ "4", // worker reasoning: high
367
+ "2", // approval: edit settings
368
+ "4", // approval AI client: gemini
369
+ "approval-pro-2",
370
+ "4", // approval permission: danger_full_access
371
+ "4", // approval reasoning: high
372
+ "1", // keep current default setting
373
+ "y", // save
374
+ ]),
375
+ },
376
+ });
377
+ const groupedRunnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
378
+ const groupedRunnerConfig = readJSON(fs.readFileSync(groupedRunnerConfigPath, "utf8"));
379
+ push(
380
+ "bot_edit_grouped_server_roles_updates_role_profiles",
381
+ String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).client || "") === "claude"
382
+ && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).model || "") === "worker-sonnet-4"
383
+ && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).permission_mode || "") === "danger_full_access"
384
+ && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).reasoning_effort || "") === "high"
385
+ && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).client || "") === "gemini"
386
+ && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).model || "") === "approval-pro-2",
387
+ `worker=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval))}`,
388
+ );
344
389
  } finally {
345
390
  await groupedMock.close();
346
391
  await runCLI({
@@ -381,11 +426,8 @@ export async function runSelftestBotCommands(push, deps) {
381
426
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
382
427
  "1", // provider: telegram
383
428
  "1", // bot entry: @monitorselftestbot
384
- "1", // edit mode: guided
385
- "1", // keep server bot binding
386
429
  "1", // keep username
387
430
  "1", // keep token
388
- "1", // keep role profile
389
431
  "2", // change AI client
390
432
  "4", // gemini
391
433
  "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.72",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [