metheus-governance-mcp-cli 0.2.72 → 0.2.73

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
@@ -247,8 +247,8 @@ Behavior:
247
247
  - `AI_PERMISSION_MODE`
248
248
  - `AI_REASONING_EFFORT`
249
249
  - Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
250
- - `bot verify` checks the configured local token and prints the current AI binding summary.
251
- - `bot show` prints one local bot entry in detail.
250
+ - `bot verify` checks the configured local token, cross-checks the server bot binding, and prints the effective runtime role profile summary that the runner will use.
251
+ - `bot show` prints one local bot entry in detail, including grouped role summaries when one server bot name expands to multiple roles.
252
252
  - `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
253
253
  - `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
254
254
  - `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
@@ -259,7 +259,7 @@ Non-interactive examples:
259
259
  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
260
260
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --default true
261
261
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --ai-client codex --ai-model gpt-5-codex --ai-permission-mode read_only --ai-reasoning-effort low
262
- 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
262
+ metheus-governance-mcp-cli bot edit --provider telegram --bot-key ryoai_bot --non-interactive true --ai-client claude --ai-model claude-sonnet-4 --ai-permission-mode danger_full_access --ai-reasoning-effort high
263
263
  metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main --non-interactive true
264
264
  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
265
265
  metheus-governance-mcp-cli bot remove --provider telegram --bot-key main --non-interactive true
@@ -268,6 +268,8 @@ metheus-governance-mcp-cli bot verify --provider telegram --bot-key main --json
268
268
 
269
269
  For direct Telegram adds, the CLI can derive the local entry key from the matched server bot name. You no longer need `--bot-key` or `--username` in the normal server-bound path. Treat `--bot-key` as an advanced override only when you intentionally want a different local suffix.
270
270
 
271
+ For direct Telegram edits, `--bot-key` still identifies which saved local entry to update. If one server bot name expands to multiple roles such as `approval / worker / review / monitor`, prefer the guided `bot edit` flow so you can keep the current grouped settings, edit one role only, or walk every role in sequence instead of forcing one entry-level AI override.
272
+
271
273
  Current support status:
272
274
 
273
275
  - Telegram: full local bot entry management, token verification, bot-to-AI binding, inbound runner support
@@ -809,6 +809,16 @@ function renderBotListPayload(provider, state, deps) {
809
809
  };
810
810
  }
811
811
 
812
+ function formatRoleProfileOutputLine(profile) {
813
+ const current = safeObject(profile);
814
+ return [
815
+ current.client ? `client=${current.client}` : "client=(blank)",
816
+ current.model ? `model=${current.model}` : "model=(blank)",
817
+ current.permissionMode ? `permission=${current.permissionMode}` : "permission=(blank)",
818
+ current.reasoningEffort ? `reasoning=${current.reasoningEffort}` : "reasoning=(blank)",
819
+ ].join(" | ");
820
+ }
821
+
812
822
  function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
813
823
  if (provider === "telegram") {
814
824
  const selectedEntry = entry || null;
@@ -829,6 +839,7 @@ function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
829
839
  permissionMode: selectedEntry.permissionMode,
830
840
  reasoningEffort: selectedEntry.reasoningEffort,
831
841
  } : null,
842
+ serverBinding: safeObject(extras.serverBinding),
832
843
  ...safeObject(extras),
833
844
  };
834
845
  }
@@ -883,6 +894,7 @@ function printBotList(provider, state, deps) {
883
894
  function printBotShow(provider, state, entry, deps, extras = {}) {
884
895
  if (provider === "telegram") {
885
896
  const selectedEntry = entry || {};
897
+ const serverBinding = safeObject(extras.serverBinding);
886
898
  process.stdout.write(`${providerLabel(provider, deps)} bot\n`);
887
899
  process.stdout.write(` file: ${state.filePath}\n`);
888
900
  process.stdout.write(` name: ${telegramEntryDisplayName(selectedEntry)}\n`);
@@ -896,8 +908,21 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
896
908
  process.stdout.write(` ai_model: ${selectedEntry.model || "-"}\n`);
897
909
  process.stdout.write(` permission_mode: ${selectedEntry.permissionMode || "-"}\n`);
898
910
  process.stdout.write(` reasoning_effort: ${selectedEntry.reasoningEffort || "-"}\n`);
899
- if (extras.serverBinding) {
900
- process.stdout.write(` server_binding: ${extras.serverBinding.ok ? "OK" : "FAIL"}${extras.serverBinding.detail ? ` (${extras.serverBinding.detail})` : ""}\n`);
911
+ if (Object.keys(serverBinding).length) {
912
+ process.stdout.write(` server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}\n`);
913
+ process.stdout.write(` server_binding_mode: ${serverBinding.mode || "-"}\n`);
914
+ process.stdout.write(` server_bot_name: ${serverBinding.name || "-"}\n`);
915
+ process.stdout.write(` server_bot_id: ${serverBinding.serverBotID || selectedEntry.serverBotID || "-" }\n`);
916
+ if (serverBinding.mode === "group") {
917
+ process.stdout.write(` server_roles: ${ensureArray(serverBinding.roles).join(", ") || "-"}\n`);
918
+ const groupedProfiles = safeObject(serverBinding.effectiveRoleProfiles);
919
+ Object.keys(groupedProfiles).forEach((role) => {
920
+ process.stdout.write(` runtime_role_profile[${role}]: ${formatRoleProfileOutputLine(groupedProfiles[role])}\n`);
921
+ });
922
+ } else if (serverBinding.effectiveRoleProfile) {
923
+ process.stdout.write(` server_role: ${serverBinding.role || "-"}\n`);
924
+ process.stdout.write(` runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}\n`);
925
+ }
901
926
  }
902
927
  return;
903
928
  }
@@ -1348,6 +1373,31 @@ function formatRoleProfileSummary(profile) {
1348
1373
  ].join(" | ");
1349
1374
  }
1350
1375
 
1376
+ function serializeRoleProfile(profile) {
1377
+ const current = safeObject(profile);
1378
+ return {
1379
+ role: String(current.role || "").trim(),
1380
+ client: String(current.client || "").trim(),
1381
+ model: String(current.model || "").trim(),
1382
+ permissionMode: String(current.permissionMode || "").trim(),
1383
+ reasoningEffort: String(current.reasoningEffort || "").trim(),
1384
+ };
1385
+ }
1386
+
1387
+ function buildRoleProfileOutput(roleName, deps) {
1388
+ return serializeRoleProfile(currentRoleProfileState(roleName, deps));
1389
+ }
1390
+
1391
+ function buildGroupedRoleProfileOutput(roles, deps) {
1392
+ const output = {};
1393
+ preferredRoleSort(roles).forEach((role) => {
1394
+ const normalizedRole = String(role || "").trim();
1395
+ if (!normalizedRole) return;
1396
+ output[normalizedRole] = buildRoleProfileOutput(normalizedRole, deps);
1397
+ });
1398
+ return output;
1399
+ }
1400
+
1351
1401
  function persistRoleProfileState(profile, deps) {
1352
1402
  const current = safeObject(profile);
1353
1403
  const role = requireDependency(deps, "normalizeRunnerRoleProfileName")(current.role || "");
@@ -1416,9 +1466,13 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
1416
1466
  if (roles.length <= 1) {
1417
1467
  return false;
1418
1468
  }
1469
+ process.stdout.write(`Current grouped role execution profiles for "${String(serverBot?.name || "").trim() || "telegram"}":\n`);
1470
+ roles.forEach((role) => {
1471
+ process.stdout.write(` - ${role}: ${formatRoleProfileSummary(currentRoleProfileState(role, deps))}\n`);
1472
+ });
1419
1473
  const editChoice = await promptChoice(
1420
1474
  ui,
1421
- `Server bot "${String(serverBot?.name || "").trim() || "telegram"}" uses multiple roles. What should happen to the local execution settings?`,
1475
+ "Grouped role settings",
1422
1476
  [
1423
1477
  {
1424
1478
  value: "keep",
@@ -1426,22 +1480,173 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
1426
1480
  description: "use the existing role_profiles defaults as-is",
1427
1481
  },
1428
1482
  {
1429
- value: "review",
1430
- label: "Review role settings",
1431
- description: "check each role and change AI client/model/permission/reasoning when needed",
1483
+ value: "one",
1484
+ label: "Edit one role",
1485
+ description: "change only the role that needs an update",
1486
+ },
1487
+ {
1488
+ value: "all",
1489
+ label: "Edit all roles",
1490
+ description: "review each role in sequence",
1432
1491
  },
1433
1492
  ],
1434
1493
  { defaultIndex: 0 },
1435
1494
  );
1436
- if (editChoice?.value !== "review") {
1495
+ if (editChoice?.value === "keep") {
1437
1496
  return false;
1438
1497
  }
1439
- for (const role of roles) {
1440
- await promptRoleExecutionProfile(ui, role, deps);
1498
+ if (editChoice?.value === "all") {
1499
+ for (const role of roles) {
1500
+ await promptRoleExecutionProfile(ui, role, deps);
1501
+ }
1502
+ return true;
1503
+ }
1504
+ const remaining = new Set(roles);
1505
+ while (remaining.size) {
1506
+ const roleChoice = await promptChoice(
1507
+ ui,
1508
+ "Select role to edit",
1509
+ [
1510
+ ...Array.from(remaining).map((role) => ({
1511
+ value: role,
1512
+ label: role,
1513
+ description: formatRoleProfileSummary(currentRoleProfileState(role, deps)),
1514
+ })),
1515
+ {
1516
+ value: "__done__",
1517
+ label: "Done",
1518
+ description: "finish grouped role editing",
1519
+ },
1520
+ ],
1521
+ { defaultIndex: 0 },
1522
+ );
1523
+ if (!roleChoice || roleChoice.value === "__done__") {
1524
+ break;
1525
+ }
1526
+ await promptRoleExecutionProfile(ui, roleChoice.value, deps);
1527
+ remaining.delete(roleChoice.value);
1528
+ if (!remaining.size) {
1529
+ break;
1530
+ }
1531
+ if (!await promptYesNo(ui, "Edit another role?", false)) {
1532
+ break;
1533
+ }
1441
1534
  }
1442
1535
  return true;
1443
1536
  }
1444
1537
 
1538
+ async function resolveTelegramServerBindingDetails(envConfig, flags, deps) {
1539
+ const current = safeObject(envConfig);
1540
+ if (!String(current.serverBotID || "").trim() && !String(current.botUsername || "").trim() && !String(current.botKey || "").trim()) {
1541
+ return null;
1542
+ }
1543
+ const lookup = await requireDependency(deps, "listServerBots")({
1544
+ provider: "telegram",
1545
+ baseURL: flags["base-url"] || deps.defaultSiteURL,
1546
+ timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
1547
+ });
1548
+ if (!lookup?.ok) {
1549
+ return {
1550
+ ok: false,
1551
+ mode: "lookup_error",
1552
+ matchedBy: "",
1553
+ name: "",
1554
+ role: "",
1555
+ roles: [],
1556
+ effectiveRoleProfile: null,
1557
+ effectiveRoleProfiles: {},
1558
+ detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
1559
+ };
1560
+ }
1561
+ const bots = ensureArray(lookup.bots).map((bot) => ({
1562
+ id: String(bot?.id || "").trim(),
1563
+ role: String(bot?.role || bot?.bot_role || "").trim(),
1564
+ name: String(bot?.name || "").trim(),
1565
+ })).filter((bot) => bot.id);
1566
+ const resolveGroupedPayload = (matches, matchedBy) => {
1567
+ const roles = preferredRoleSort(summarizeServerBotRoles(matches));
1568
+ return {
1569
+ ok: true,
1570
+ mode: "group",
1571
+ matchedBy,
1572
+ name: String(matches[0]?.name || "").trim(),
1573
+ role: "",
1574
+ roles,
1575
+ serverBotID: String(current.serverBotID || "").trim(),
1576
+ effectiveRoleProfile: null,
1577
+ effectiveRoleProfiles: buildGroupedRoleProfileOutput(roles, deps),
1578
+ detail: `${String(matches[0]?.name || "").trim() || "(unnamed)"} roles: ${roles.join(", ") || "-"}`,
1579
+ };
1580
+ };
1581
+ if (String(current.serverBotID || "").trim()) {
1582
+ const match = bots.find((bot) => bot.id === String(current.serverBotID || "").trim());
1583
+ if (!match) {
1584
+ return {
1585
+ ok: false,
1586
+ mode: "missing",
1587
+ matchedBy: "server_bot_id",
1588
+ name: "",
1589
+ role: "",
1590
+ roles: [],
1591
+ serverBotID: String(current.serverBotID || "").trim(),
1592
+ effectiveRoleProfile: null,
1593
+ effectiveRoleProfiles: {},
1594
+ detail: `server bot ${current.serverBotID} not found`,
1595
+ };
1596
+ }
1597
+ const sameNameMatches = bots.filter((bot) => normalizeServerBotIdentityText(bot.name) === normalizeServerBotIdentityText(match.name));
1598
+ if (sameNameMatches.length > 1) {
1599
+ return resolveGroupedPayload(sameNameMatches, "server_bot_id");
1600
+ }
1601
+ const effectiveRoleProfile = buildRoleProfileOutput(match.role || current.roleProfile || "", deps);
1602
+ return {
1603
+ ok: true,
1604
+ mode: "single",
1605
+ matchedBy: "server_bot_id",
1606
+ name: match.name,
1607
+ role: match.role,
1608
+ roles: summarizeServerBotRoles([match]),
1609
+ serverBotID: match.id,
1610
+ effectiveRoleProfile,
1611
+ effectiveRoleProfiles: {},
1612
+ detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
1613
+ };
1614
+ }
1615
+ const normalizedServerIdentity = normalizeServerBotIdentityText(current.botUsername || current.botKey);
1616
+ const matches = bots.filter((bot) => normalizeServerBotIdentityText(bot.name) === normalizedServerIdentity);
1617
+ if (!matches.length) {
1618
+ return {
1619
+ ok: false,
1620
+ mode: "missing",
1621
+ matchedBy: "server_bot_name",
1622
+ name: "",
1623
+ role: "",
1624
+ roles: [],
1625
+ serverBotID: "",
1626
+ effectiveRoleProfile: null,
1627
+ effectiveRoleProfiles: {},
1628
+ detail: `no server bot matched ${current.botUsername ? `@${current.botUsername}` : current.botKey}`,
1629
+ };
1630
+ }
1631
+ if (matches.length > 1) {
1632
+ return resolveGroupedPayload(matches, "server_bot_name");
1633
+ }
1634
+ const match = matches[0] || {};
1635
+ const effectiveRoleProfile = buildRoleProfileOutput(match.role || current.roleProfile || "", deps);
1636
+ return {
1637
+ ok: true,
1638
+ mode: "single",
1639
+ matchedBy: "server_bot_name",
1640
+ name: String(match.name || "").trim(),
1641
+ role: String(match.role || "").trim(),
1642
+ roles: summarizeServerBotRoles([match]),
1643
+ serverBotID: String(match.id || "").trim(),
1644
+ effectiveRoleProfile,
1645
+ effectiveRoleProfiles: {},
1646
+ detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
1647
+ };
1648
+ }
1649
+
1445
1650
  function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
1446
1651
  return {
1447
1652
  ok: true,
@@ -1840,53 +2045,12 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
1840
2045
  envConfig,
1841
2046
  intFromRaw(flags["timeout-seconds"], 15) || 15,
1842
2047
  );
1843
- let serverBinding = null;
1844
2048
  let overallOK = Boolean(result.ok);
1845
- if (provider === "telegram" && (String(envConfig.serverBotID || "").trim() || String(envConfig.botUsername || "").trim() || String(envConfig.botKey || "").trim())) {
1846
- const lookup = await requireDependency(deps, "listServerBots")({
1847
- provider,
1848
- baseURL: flags["base-url"] || deps.defaultSiteURL,
1849
- timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
1850
- });
1851
- if (!lookup?.ok) {
1852
- serverBinding = {
1853
- ok: false,
1854
- detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
1855
- };
2049
+ let serverBinding = null;
2050
+ if (provider === "telegram") {
2051
+ serverBinding = await resolveTelegramServerBindingDetails(envConfig, flags, deps);
2052
+ if (serverBinding && !serverBinding.ok) {
1856
2053
  overallOK = false;
1857
- } else {
1858
- const normalizedServerIdentity = normalizeServerBotIdentityText(envConfig.botUsername || envConfig.botKey);
1859
- if (String(envConfig.serverBotID || "").trim()) {
1860
- const match = ensureArray(lookup.bots).find((bot) => String(bot.id || "").trim() === String(envConfig.serverBotID || "").trim());
1861
- if (!match) {
1862
- serverBinding = {
1863
- ok: false,
1864
- detail: `server bot ${envConfig.serverBotID} not found`,
1865
- };
1866
- overallOK = false;
1867
- } else {
1868
- serverBinding = {
1869
- ok: true,
1870
- detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
1871
- };
1872
- }
1873
- } else {
1874
- const matches = ensureArray(lookup.bots).filter(
1875
- (bot) => normalizeServerBotIdentityText(bot?.name) === normalizedServerIdentity,
1876
- );
1877
- if (!matches.length) {
1878
- serverBinding = {
1879
- ok: false,
1880
- detail: `no server bot matched ${envConfig.botUsername ? `@${envConfig.botUsername}` : envConfig.botKey}`,
1881
- };
1882
- overallOK = false;
1883
- } else {
1884
- serverBinding = {
1885
- ok: true,
1886
- detail: `${String(matches[0]?.name || "").trim() || "(unnamed)"} roles: ${summarizeServerBotRoles(matches).join(", ") || "-"}`,
1887
- };
1888
- }
1889
- }
1890
2054
  }
1891
2055
  }
1892
2056
  const interactiveJsonChoice = (
@@ -1923,21 +2087,33 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
1923
2087
  }, null, 2)}\n`,
1924
2088
  );
1925
2089
  } else {
1926
- process.stdout.write(
1927
- [
1928
- `${providerLabel(provider, deps)} verify: ${overallOK ? "OK" : "FAIL"}`,
1929
- `file: ${envConfig.filePath}`,
1930
- provider === "telegram" ? `bot_key: ${envConfig.botKey || "-"}` : "",
1931
- provider === "telegram" ? `server_bot_id: ${envConfig.serverBotID || "-"}` : "",
1932
- provider === "telegram" ? `role_profile: ${envConfig.roleProfile || "-"}` : "",
1933
- provider === "telegram" ? `ai_client: ${envConfig.client || "-"}` : "",
1934
- provider === "telegram" ? `ai_model: ${envConfig.model || "-"}` : "",
1935
- provider === "telegram" ? `permission_mode: ${envConfig.permissionMode || "-"}` : "",
1936
- provider === "telegram" ? `reasoning_effort: ${envConfig.reasoningEffort || "-"}` : "",
1937
- `detail: ${result.detail || "-"}`,
1938
- serverBinding ? `server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}` : "",
1939
- ].filter(Boolean).join("\n") + "\n",
1940
- );
2090
+ const lines = [
2091
+ `${providerLabel(provider, deps)} verify: ${overallOK ? "OK" : "FAIL"}`,
2092
+ `file: ${envConfig.filePath}`,
2093
+ provider === "telegram" ? `bot_key: ${envConfig.botKey || "-"}` : "",
2094
+ provider === "telegram" ? `server_bot_id: ${envConfig.serverBotID || "-"}` : "",
2095
+ provider === "telegram" ? `role_profile: ${envConfig.roleProfile || "-"}` : "",
2096
+ provider === "telegram" ? `ai_client: ${envConfig.client || "-"}` : "",
2097
+ provider === "telegram" ? `ai_model: ${envConfig.model || "-"}` : "",
2098
+ provider === "telegram" ? `permission_mode: ${envConfig.permissionMode || "-"}` : "",
2099
+ provider === "telegram" ? `reasoning_effort: ${envConfig.reasoningEffort || "-"}` : "",
2100
+ `detail: ${result.detail || "-"}`,
2101
+ serverBinding ? `server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}` : "",
2102
+ ].filter(Boolean);
2103
+ if (provider === "telegram" && serverBinding) {
2104
+ lines.push(`server_binding_mode: ${serverBinding.mode || "-"}`);
2105
+ lines.push(`server_bot_name: ${serverBinding.name || "-"}`);
2106
+ lines.push(`server_roles: ${ensureArray(serverBinding.roles).join(", ") || "-"}`);
2107
+ if (serverBinding.mode === "group") {
2108
+ const groupedProfiles = safeObject(serverBinding.effectiveRoleProfiles);
2109
+ Object.keys(groupedProfiles).forEach((role) => {
2110
+ lines.push(`runtime_role_profile[${role}]: ${formatRoleProfileOutputLine(groupedProfiles[role])}`);
2111
+ });
2112
+ } else if (serverBinding.effectiveRoleProfile) {
2113
+ lines.push(`runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}`);
2114
+ }
2115
+ }
2116
+ process.stdout.write(`${lines.join("\n")}\n`);
1941
2117
  }
1942
2118
  if (!overallOK) {
1943
2119
  process.exitCode = 1;
@@ -2096,12 +2272,21 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
2096
2272
  const state = loadProviderEnvState(provider, deps);
2097
2273
  if (provider === "telegram") {
2098
2274
  const entry = await resolveTelegramEntryForShow(ui, state.parsed, flags, deps);
2099
- const payload = buildBotShowPayload(provider, state, entry, deps);
2275
+ const serverBinding = await resolveTelegramServerBindingDetails(
2276
+ requireDependency(deps, "loadProviderEnvConfig")(provider, {
2277
+ botKey: entry.key,
2278
+ botID: entry.serverBotID,
2279
+ botName: entry.username || entry.key,
2280
+ }),
2281
+ flags,
2282
+ deps,
2283
+ );
2284
+ const payload = buildBotShowPayload(provider, state, entry, deps, { serverBinding });
2100
2285
  if (boolFromRaw(flags.json, false)) {
2101
2286
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
2102
2287
  return;
2103
2288
  }
2104
- printBotShow(provider, state, entry, deps);
2289
+ printBotShow(provider, state, entry, deps, { serverBinding });
2105
2290
  return;
2106
2291
  }
2107
2292
  const payload = buildBotShowPayload(provider, state, null, deps);
@@ -356,19 +356,21 @@ export async function runSelftestBotCommands(push, deps) {
356
356
  "2", // bot entry: ryoai_bot
357
357
  "1", // keep username
358
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
359
+ "2", // grouped role settings: edit one role
360
+ "3", // select role to edit: worker
362
361
  "2", // worker: edit settings
363
362
  "3", // worker AI client: claude
364
363
  "worker-sonnet-4",
365
364
  "4", // worker permission: danger_full_access
366
365
  "4", // worker reasoning: high
366
+ "y", // edit another role
367
+ "3", // select role to edit: approval
367
368
  "2", // approval: edit settings
368
369
  "4", // approval AI client: gemini
369
370
  "approval-pro-2",
370
371
  "4", // approval permission: danger_full_access
371
372
  "4", // approval reasoning: high
373
+ "n", // stop editing roles
372
374
  "1", // keep current default setting
373
375
  "y", // save
374
376
  ]),
@@ -386,6 +388,26 @@ export async function runSelftestBotCommands(push, deps) {
386
388
  && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).model || "") === "approval-pro-2",
387
389
  `worker=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval))}`,
388
390
  );
391
+
392
+ const groupedShowResult = await runCLI({
393
+ cliPath,
394
+ args: [
395
+ "bot", "show",
396
+ "--provider", "telegram",
397
+ "--bot-key", "ryoai_bot",
398
+ "--base-url", `http://127.0.0.1:${groupedMock.port}`,
399
+ "--json", "true",
400
+ ],
401
+ env,
402
+ });
403
+ const groupedShowPayload = readJSON(groupedShowResult.stdout);
404
+ push(
405
+ "bot_show_reports_grouped_server_roles",
406
+ safeObject(groupedShowPayload.serverBinding).mode === "group"
407
+ && safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client === "claude"
408
+ && safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client === "gemini",
409
+ `mode=${String(safeObject(groupedShowPayload.serverBinding).mode || "")} worker=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client || "")} approval=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client || "")}`,
410
+ );
389
411
  } finally {
390
412
  await groupedMock.close();
391
413
  await runCLI({
@@ -406,6 +428,7 @@ export async function runSelftestBotCommands(push, deps) {
406
428
  "bot", "show",
407
429
  "--provider", "telegram",
408
430
  "--bot-key", "monitorselftestbot",
431
+ "--base-url", baseURL,
409
432
  "--json", "true",
410
433
  ],
411
434
  env,
@@ -414,8 +437,9 @@ export async function runSelftestBotCommands(push, deps) {
414
437
  push(
415
438
  "bot_show_returns_selected_telegram_entry",
416
439
  safeObject(showPayload.entry).key === "monitorselftestbot"
417
- && safeObject(showPayload.entry).client === "codex",
418
- `key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")}`,
440
+ && safeObject(showPayload.entry).client === "codex"
441
+ && safeObject(showPayload.serverBinding).mode === "single",
442
+ `key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")} mode=${String(safeObject(showPayload.serverBinding).mode || "")}`,
419
443
  );
420
444
 
421
445
  await runCLI({
@@ -436,7 +460,6 @@ export async function runSelftestBotCommands(push, deps) {
436
460
  "3", // workspace_write
437
461
  "2", // change reasoning effort
438
462
  "3", // medium
439
- "1", // keep bot key
440
463
  "1", // keep default setting
441
464
  "y", // save
442
465
  ]),
@@ -518,8 +541,9 @@ export async function runSelftestBotCommands(push, deps) {
518
541
  "bot_verify_guided_can_emit_json",
519
542
  guidedVerifyPayload.ok === true
520
543
  && safeObject(guidedVerifyPayload.serverBinding).ok === true
544
+ && safeObject(guidedVerifyPayload.serverBinding).mode === "single"
521
545
  && String(guidedVerifyPayload.client || "") === "claude",
522
- `verify=${String(guidedVerifyPayload.ok)} server=${String(safeObject(guidedVerifyPayload.serverBinding).detail || "")}`,
546
+ `verify=${String(guidedVerifyPayload.ok)} mode=${String(safeObject(guidedVerifyPayload.serverBinding).mode || "")} server=${String(safeObject(guidedVerifyPayload.serverBinding).detail || "")}`,
523
547
  );
524
548
 
525
549
  await runCLI({
@@ -556,8 +580,9 @@ export async function runSelftestBotCommands(push, deps) {
556
580
  "bot_verify_cross_checks_server_bot_binding",
557
581
  verifyPayload.ok === true
558
582
  && safeObject(verifyPayload.serverBinding).ok === true
583
+ && safeObject(verifyPayload.serverBinding).mode === "single"
559
584
  && String(verifyPayload.client || "") === "claude",
560
- `verify=${String(verifyPayload.ok)} server=${String(safeObject(verifyPayload.serverBinding).detail || "")}`,
585
+ `verify=${String(verifyPayload.ok)} mode=${String(safeObject(verifyPayload.serverBinding).mode || "")} server=${String(safeObject(verifyPayload.serverBinding).detail || "")}`,
561
586
  );
562
587
 
563
588
  await runCLI({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.72",
3
+ "version": "0.2.73",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [