metheus-governance-mcp-cli 0.2.77 → 0.2.79

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.
@@ -711,22 +711,32 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
711
711
  let serverRoleAutoResolved = "";
712
712
  let groupedServerRoles = [];
713
713
  let groupedServerName = "";
714
- if (current.serverBotID || current.__preferServerIdentity) {
715
- current.__preferServerIdentity = true;
716
- serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
714
+ let serverManagedIdentity = Boolean(current.serverBotID || current.__preferServerIdentity);
715
+ if (!serverManagedIdentity) {
716
+ const initialServerBot = await autoResolveTelegramServerBot(current, flags, deps);
717
+ if (String(initialServerBot.botID || "").trim() || initialServerBot.matchMode === "group") {
718
+ serverManagedIdentity = true;
719
+ }
717
720
  }
718
-
719
- const usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
720
- allowClear: true,
721
- defaultValue: current.username ? "keep" : "change",
722
- });
723
- if (usernameAction === "change") {
724
- current.username = requireDependency(deps, "normalizeTelegramBotUsername")(
725
- await promptRequiredLine(ui, "Telegram username (without @)", current.username),
726
- "",
727
- );
728
- } else if (usernameAction === "clear") {
721
+ let usernameAction = "keep";
722
+ if (serverManagedIdentity) {
723
+ current.__preferServerIdentity = true;
729
724
  current.username = "";
725
+ serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
726
+ process.stdout.write("Telegram username is managed from server bot info and cannot be edited here.\n");
727
+ } else {
728
+ usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
729
+ allowClear: true,
730
+ defaultValue: current.username ? "keep" : "change",
731
+ });
732
+ if (usernameAction === "change") {
733
+ current.username = requireDependency(deps, "normalizeTelegramBotUsername")(
734
+ await promptRequiredLine(ui, "Telegram username (without @)", current.username),
735
+ "",
736
+ );
737
+ } else if (usernameAction === "clear") {
738
+ current.username = "";
739
+ }
730
740
  }
731
741
 
732
742
  const tokenAction = await promptKeepChangeClear(ui, "Telegram token", {
@@ -1113,19 +1123,16 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
1113
1123
  process.stdout.write(` token: ${token ? maskSecret(token) : "(not configured)"}\n`);
1114
1124
  }
1115
1125
 
1116
- async function chooseTelegramEntry(ui, parsedEnv, deps, title = "Select Telegram bot entry") {
1126
+ async function chooseTelegramEntry(ui, parsedEnv, deps, title = "Select Telegram bot entry", flags = {}) {
1117
1127
  const entries = telegramEntriesForDisplay(parsedEnv, deps);
1118
1128
  if (!entries.length) {
1119
1129
  throw new Error("no Telegram bot entries are configured");
1120
1130
  }
1131
+ const displayRows = await buildTelegramEntrySelectionRows(entries, flags, deps);
1121
1132
  const selected = await promptChoice(
1122
1133
  ui,
1123
1134
  title,
1124
- entries.map((entry) => ({
1125
- value: entry.key,
1126
- label: `${telegramEntryDisplayName(entry)}${entry.isDefault ? " [default]" : ""}`,
1127
- description: telegramEntryDisplayDescription(entry),
1128
- })),
1135
+ displayRows,
1129
1136
  { defaultIndex: 0 },
1130
1137
  );
1131
1138
  return entries.find((entry) => entry.key === selected.value) || entries[0];
@@ -1154,7 +1161,7 @@ async function resolveTelegramEntryForShow(ui, parsedEnv, flags, deps) {
1154
1161
  if (nonInteractive) {
1155
1162
  throw new Error("Telegram bot selector is required when multiple entries exist; pass --bot-key, --bot-id, or --bot-name");
1156
1163
  }
1157
- return chooseTelegramEntry(ui, parsedEnv, deps, "Select Telegram bot entry");
1164
+ return chooseTelegramEntry(ui, parsedEnv, deps, "Select Telegram bot entry", flags);
1158
1165
  }
1159
1166
 
1160
1167
  async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps, options = {}) {
@@ -1715,34 +1722,11 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
1715
1722
  return true;
1716
1723
  }
1717
1724
 
1718
- async function resolveTelegramServerBindingDetails(envConfig, flags, deps) {
1725
+ function resolveTelegramServerBindingDetailsFromBots(envConfig, bots, deps) {
1719
1726
  const current = safeObject(envConfig);
1720
1727
  if (!String(current.serverBotID || "").trim() && !String(current.botUsername || "").trim() && !String(current.botKey || "").trim()) {
1721
1728
  return null;
1722
1729
  }
1723
- const lookup = await requireDependency(deps, "listServerBots")({
1724
- provider: "telegram",
1725
- baseURL: flags["base-url"] || deps.defaultSiteURL,
1726
- timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
1727
- });
1728
- if (!lookup?.ok) {
1729
- return {
1730
- ok: false,
1731
- mode: "lookup_error",
1732
- matchedBy: "",
1733
- name: "",
1734
- role: "",
1735
- roles: [],
1736
- effectiveRoleProfile: null,
1737
- effectiveRoleProfiles: {},
1738
- detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
1739
- };
1740
- }
1741
- const bots = ensureArray(lookup.bots).map((bot) => ({
1742
- id: String(bot?.id || "").trim(),
1743
- role: String(bot?.role || bot?.bot_role || "").trim(),
1744
- name: String(bot?.name || "").trim(),
1745
- })).filter((bot) => bot.id);
1746
1730
  const resolveGroupedPayload = (matches, matchedBy) => {
1747
1731
  const roles = preferredRoleSort(summarizeServerBotRoles(matches));
1748
1732
  return {
@@ -1827,6 +1811,88 @@ async function resolveTelegramServerBindingDetails(envConfig, flags, deps) {
1827
1811
  };
1828
1812
  }
1829
1813
 
1814
+ async function resolveTelegramServerBindingDetails(envConfig, flags, deps) {
1815
+ const lookup = await requireDependency(deps, "listServerBots")({
1816
+ provider: "telegram",
1817
+ baseURL: flags["base-url"] || deps.defaultSiteURL,
1818
+ timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
1819
+ });
1820
+ if (!lookup?.ok) {
1821
+ return {
1822
+ ok: false,
1823
+ mode: "lookup_error",
1824
+ matchedBy: "",
1825
+ name: "",
1826
+ role: "",
1827
+ roles: [],
1828
+ effectiveRoleProfile: null,
1829
+ effectiveRoleProfiles: {},
1830
+ detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
1831
+ };
1832
+ }
1833
+ const bots = ensureArray(lookup.bots).map((bot) => ({
1834
+ id: String(bot?.id || "").trim(),
1835
+ role: String(bot?.role || bot?.bot_role || "").trim(),
1836
+ name: String(bot?.name || "").trim(),
1837
+ })).filter((bot) => bot.id);
1838
+ return resolveTelegramServerBindingDetailsFromBots(envConfig, bots, deps);
1839
+ }
1840
+
1841
+ async function buildTelegramEntrySelectionRows(entries, flags, deps) {
1842
+ const listServerBots = requireDependency(deps, "listServerBots");
1843
+ let bots = [];
1844
+ try {
1845
+ const lookup = await listServerBots({
1846
+ provider: "telegram",
1847
+ baseURL: flags["base-url"] || deps.defaultSiteURL,
1848
+ timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
1849
+ });
1850
+ bots = ensureArray(lookup?.bots).map((bot) => ({
1851
+ id: String(bot?.id || "").trim(),
1852
+ role: String(bot?.role || bot?.bot_role || "").trim(),
1853
+ name: String(bot?.name || "").trim(),
1854
+ })).filter((bot) => bot.id);
1855
+ } catch {
1856
+ bots = [];
1857
+ }
1858
+ return ensureArray(entries).map((entry) => {
1859
+ const current = safeObject(entry);
1860
+ const binding = bots.length
1861
+ ? resolveTelegramServerBindingDetailsFromBots(
1862
+ {
1863
+ serverBotID: current.serverBotID,
1864
+ botUsername: current.username,
1865
+ botKey: current.key,
1866
+ roleProfile: current.roleProfile,
1867
+ },
1868
+ bots,
1869
+ deps,
1870
+ )
1871
+ : null;
1872
+ const serverName = String(binding?.name || "").trim();
1873
+ const singleRole = String(binding?.role || "").trim();
1874
+ const groupedRoles = ensureArray(binding?.roles).filter(Boolean);
1875
+ let label = telegramEntryDisplayName(entry);
1876
+ if (serverName && singleRole) {
1877
+ label = `${serverName} [${singleRole}]`;
1878
+ } else if (serverName && groupedRoles.length) {
1879
+ label = `${serverName} [${groupedRoles.join(", ")}]`;
1880
+ } else if (serverName) {
1881
+ label = serverName;
1882
+ }
1883
+ const descriptionParts = [
1884
+ current.key ? `local:${current.key}` : "",
1885
+ current.serverBotID ? `server:${current.serverBotID}` : "",
1886
+ current.client ? `AI:${current.client}` : "",
1887
+ ].filter(Boolean);
1888
+ return {
1889
+ value: current.key,
1890
+ label: `${label}${current.isDefault ? " [default]" : ""}`,
1891
+ description: descriptionParts.join(" "),
1892
+ };
1893
+ });
1894
+ }
1895
+
1830
1896
  function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
1831
1897
  return {
1832
1898
  ok: true,
@@ -2115,7 +2181,7 @@ async function editTelegramGlobalSettings(ui, flags, deps) {
2115
2181
  process.stdout.write("No Telegram bot entries exist yet.\n");
2116
2182
  return;
2117
2183
  }
2118
- const selected = await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot");
2184
+ const selected = await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot", flags);
2119
2185
  parsed.TELEGRAM_DEFAULT_BOT_KEY = selected.key;
2120
2186
  }
2121
2187
  const filePath = writeProviderEnvState("telegram", parsed, deps);
@@ -2128,7 +2194,7 @@ async function editTelegramBot(ui, flags, deps) {
2128
2194
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
2129
2195
  const selected = nonInteractive
2130
2196
  ? findTelegramEntryByFlags(parsed, flags, deps)
2131
- : await chooseTelegramEntry(ui, parsed, deps);
2197
+ : await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry", flags);
2132
2198
  if (!selected) {
2133
2199
  throw new Error("Telegram bot selector is required for non-interactive edit");
2134
2200
  }
@@ -2168,10 +2234,10 @@ async function editTelegramBot(ui, flags, deps) {
2168
2234
  await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
2169
2235
  }
2170
2236
 
2171
- async function removeTelegramBot(ui, deps) {
2237
+ async function removeTelegramBot(ui, flags, deps) {
2172
2238
  const state = loadProviderEnvState("telegram", deps);
2173
2239
  const parsed = { ...state.parsed };
2174
- const selected = await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry to remove");
2240
+ const selected = await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry to remove", flags);
2175
2241
  if (!selected) return;
2176
2242
  if (!await promptConfirmChoice(ui, `Remove Telegram bot "${selected.key}"?`, {
2177
2243
  confirmLabel: "Remove bot",
@@ -2206,7 +2272,7 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
2206
2272
  botName: requestedBotName,
2207
2273
  };
2208
2274
  } else {
2209
- const selected = await chooseTelegramEntry(ui, state.parsed, deps, "Select Telegram bot entry to verify");
2275
+ const selected = await chooseTelegramEntry(ui, state.parsed, deps, "Select Telegram bot entry to verify", flags);
2210
2276
  selectors = {
2211
2277
  botKey: selected.key,
2212
2278
  botID: selected.serverBotID,
@@ -2546,7 +2612,7 @@ async function runBotRemove(ui, flags, deps) {
2546
2612
  process.stdout.write(`Removed Telegram bot "${selected.key}" from ${filePath}\n`);
2547
2613
  return;
2548
2614
  }
2549
- await removeTelegramBot(ui, deps);
2615
+ await removeTelegramBot(ui, flags, deps);
2550
2616
  return;
2551
2617
  }
2552
2618
  await removeTokenOnlyProvider(ui, provider, flags, deps);
@@ -2573,7 +2639,7 @@ async function runBotSetDefault(ui, flags, deps) {
2573
2639
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
2574
2640
  const selected = nonInteractive
2575
2641
  ? findTelegramEntryByFlags(parsed, flags, deps)
2576
- : await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot");
2642
+ : await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot", flags);
2577
2643
  if (!selected) {
2578
2644
  throw new Error("Telegram bot selector is required for set-default");
2579
2645
  }
@@ -347,7 +347,7 @@ export async function runSelftestBotCommands(push, deps) {
347
347
  `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 || "")}`,
348
348
  );
349
349
 
350
- await runCLI({
350
+ const groupedEditResult = await runCLI({
351
351
  cliPath,
352
352
  args: [
353
353
  "bot", "edit",
@@ -359,7 +359,6 @@ export async function runSelftestBotCommands(push, deps) {
359
359
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
360
360
  "1", // provider: telegram
361
361
  "2", // bot entry: ryoai_bot
362
- "1", // keep username
363
362
  "1", // keep token
364
363
  "2", // grouped role settings: edit one role
365
364
  "3", // select role to edit: worker
@@ -381,6 +380,11 @@ export async function runSelftestBotCommands(push, deps) {
381
380
  ]),
382
381
  },
383
382
  });
383
+ push(
384
+ "bot_edit_grouped_server_roles_skips_local_username_prompt",
385
+ !String(groupedEditResult.stdout || "").includes("Telegram username (without @)"),
386
+ String(groupedEditResult.stdout || "").split(/\r?\n/).filter((line) => line.includes("Telegram username")).join(" | ") || "username prompt skipped",
387
+ );
384
388
  const groupedRunnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
385
389
  const groupedRunnerConfig = readJSON(fs.readFileSync(groupedRunnerConfigPath, "utf8"));
386
390
  push(
@@ -487,7 +491,7 @@ export async function runSelftestBotCommands(push, deps) {
487
491
  `routes=${JSON.stringify(safeObject(showWithRoutesPayload.routeLinks))}`,
488
492
  );
489
493
 
490
- await runCLI({
494
+ const guidedEditResult = await runCLI({
491
495
  cliPath,
492
496
  args: ["bot", "edit"],
493
497
  env: {
@@ -495,7 +499,6 @@ export async function runSelftestBotCommands(push, deps) {
495
499
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
496
500
  "1", // provider: telegram
497
501
  "1", // bot entry: @monitorselftestbot
498
- "1", // keep username
499
502
  "1", // keep token
500
503
  "2", // change AI client
501
504
  "4", // gemini
@@ -510,6 +513,11 @@ export async function runSelftestBotCommands(push, deps) {
510
513
  ]),
511
514
  },
512
515
  });
516
+ push(
517
+ "bot_edit_single_server_binding_skips_local_username_prompt",
518
+ !String(guidedEditResult.stdout || "").includes("Telegram username (without @)"),
519
+ String(guidedEditResult.stdout || "").split(/\r?\n/).filter((line) => line.includes("Telegram username")).join(" | ") || "username prompt skipped",
520
+ );
513
521
  const guidedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
514
522
  push(
515
523
  "bot_edit_guided_prompts_update_ai_binding_fields",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.77",
3
+ "version": "0.2.79",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [