metheus-governance-mcp-cli 0.2.63 → 0.2.64

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
@@ -215,6 +215,8 @@ metheus-governance-mcp-cli bot show --provider telegram --bot-key main
215
215
  metheus-governance-mcp-cli bot add --provider telegram
216
216
  metheus-governance-mcp-cli bot edit --provider telegram
217
217
  metheus-governance-mcp-cli bot remove --provider telegram
218
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main
219
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-key main
218
220
  metheus-governance-mcp-cli bot global --provider telegram
219
221
  metheus-governance-mcp-cli bot verify --provider telegram --bot-key main
220
222
  ```
@@ -235,6 +237,8 @@ Behavior:
235
237
  - `bot verify` checks the configured local token and prints the current AI binding summary.
236
238
  - `bot show` prints one local bot entry in detail.
237
239
  - `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
240
+ - `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
241
+ - `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
238
242
 
239
243
  Non-interactive examples:
240
244
 
@@ -242,6 +246,8 @@ Non-interactive examples:
242
246
  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
243
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
244
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
249
+ 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
245
251
  metheus-governance-mcp-cli bot remove --provider telegram --bot-key main --non-interactive true
246
252
  metheus-governance-mcp-cli bot verify --provider telegram --bot-key main --json true
247
253
  ```
@@ -276,6 +282,9 @@ Checks:
276
282
  - role profile -> local CLI availability
277
283
  - route dry-run / trigger-policy safety warnings
278
284
  - local provider token presence for active project destinations
285
+ - enabled runner route -> local bot env binding resolution
286
+ - enabled runner route -> server bot UUID cross-check
287
+ - legacy `TELEGRAM_BOT_TOKEN` fallback still in use
279
288
  - codex/claude/gemini/antigravity/cursor registration state
280
289
  - gateway `tools/list` reachability
281
290
  - `project.summary` access
package/cli.mjs CHANGED
@@ -240,6 +240,8 @@ function printUsage() {
240
240
  ` ${cmd} bot add [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>]`,
241
241
  ` ${cmd} bot edit [--provider <telegram|slack|kakaotalk>] [--base-url <url>] [--timeout-seconds <n>]`,
242
242
  ` ${cmd} bot remove [--provider <telegram|slack|kakaotalk>]`,
243
+ ` ${cmd} bot set-default --provider telegram [--bot-key <key>]`,
244
+ ` ${cmd} bot migrate --provider telegram [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>]`,
243
245
  ` ${cmd} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-key <key>] [--timeout-seconds <n>] [--json <true|false>]`,
244
246
  ` ${cmd} bot global --provider telegram [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <key>]`,
245
247
  ` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>] [--strict <true|false>]`,
@@ -3527,6 +3529,8 @@ async function runDoctor(flags) {
3527
3529
  includeDrafts: true,
3528
3530
  }, { defaultBaseURL: DEFAULT_BASE_URL });
3529
3531
  const rows = [];
3532
+ let runnerConfig = null;
3533
+ let enabledRoutes = [];
3530
3534
  const providerTemplates = ensureAllProviderEnvTemplates();
3531
3535
  const runnerTemplate = ensureBotRunnerConfigTemplate();
3532
3536
  for (const provider of PROVIDER_ENV_ORDER) {
@@ -3541,9 +3545,9 @@ async function runDoctor(flags) {
3541
3545
  if (runnerTemplate.error) {
3542
3546
  addDoctorCheck(rows, "warn", "local bot runner config", `${runnerTemplate.error} (${runnerTemplate.filePath})`);
3543
3547
  } else {
3544
- const runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
3548
+ runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
3545
3549
  const runnerState = loadBotRunnerState();
3546
- const enabledRoutes = ensureArray(runnerConfig.routes).filter((routeRaw) => safeObject(routeRaw).enabled);
3550
+ enabledRoutes = ensureArray(runnerConfig.routes).filter((routeRaw) => safeObject(routeRaw).enabled);
3547
3551
  if (!enabledRoutes.length) {
3548
3552
  addDoctorCheck(rows, "warn", "local bot runner config", `template ready (${runnerTemplate.filePath}), no enabled routes`);
3549
3553
  } else {
@@ -3662,6 +3666,82 @@ async function runDoctor(flags) {
3662
3666
  deps: buildDoctorChecksDeps(),
3663
3667
  });
3664
3668
  }
3669
+
3670
+ if (runnerConfig && enabledRoutes.length > 0) {
3671
+ let serverBots = null;
3672
+ try {
3673
+ serverBots = await listUserBotsForRunner({
3674
+ siteBaseURL: normalizeSiteBaseURL(context.baseURL),
3675
+ token,
3676
+ timeoutSeconds,
3677
+ }, buildRunnerDataDeps());
3678
+ } catch {
3679
+ serverBots = null;
3680
+ }
3681
+ for (const routeRaw of enabledRoutes) {
3682
+ const route = normalizeRunnerRoute(routeRaw);
3683
+ const routeLabel = route.name || route.projectID || route.provider;
3684
+ const envConfig = loadProviderEnvConfig(route.provider, {
3685
+ botID: route.botID,
3686
+ botName: route.botName,
3687
+ route,
3688
+ });
3689
+ if (!envConfig.ok) {
3690
+ addDoctorCheck(
3691
+ rows,
3692
+ "fail",
3693
+ `runner route ${routeLabel} binding`,
3694
+ `${providerEnvConfig(route.provider).label} local bot env is unresolved (${envConfig.error})`,
3695
+ );
3696
+ continue;
3697
+ }
3698
+ const detailParts = [];
3699
+ if (envConfig.botKey) detailParts.push(`bot=${envConfig.botKey}`);
3700
+ if (envConfig.botUsername) detailParts.push(`@${envConfig.botUsername}`);
3701
+ if (envConfig.serverBotID) detailParts.push(`server_bot_id=${envConfig.serverBotID}`);
3702
+ if (envConfig.client) detailParts.push(`ai=${envConfig.client}`);
3703
+ addDoctorCheck(
3704
+ rows,
3705
+ "ok",
3706
+ `runner route ${routeLabel} binding`,
3707
+ detailParts.join(", ") || "resolved",
3708
+ );
3709
+ if (route.botID && envConfig.serverBotID && route.botID !== envConfig.serverBotID) {
3710
+ addDoctorCheck(
3711
+ rows,
3712
+ strictMode ? "fail" : "warn",
3713
+ `runner route ${routeLabel} server_bot_id`,
3714
+ `route bot_id ${route.botID} != local env ${envConfig.serverBotID}`,
3715
+ );
3716
+ }
3717
+ if (route.botID && !envConfig.serverBotID) {
3718
+ addDoctorCheck(
3719
+ rows,
3720
+ strictMode ? "fail" : "warn",
3721
+ `runner route ${routeLabel} server_bot_id`,
3722
+ `route bot_id ${route.botID} is set, but local env entry does not carry SERVER_BOT_ID`,
3723
+ );
3724
+ }
3725
+ if (serverBots && route.botID) {
3726
+ const match = ensureArray(serverBots).find((bot) => String(bot.id || "").trim() === String(route.botID || "").trim());
3727
+ addDoctorCheck(
3728
+ rows,
3729
+ match ? "ok" : (strictMode ? "fail" : "warn"),
3730
+ `runner route ${routeLabel} server bot`,
3731
+ match ? `${match.name || "(unnamed)"} [${match.role || "-"}]` : `server bot ${route.botID} not found`,
3732
+ );
3733
+ }
3734
+ }
3735
+ const telegramDefaultConfig = loadProviderEnvConfig("telegram");
3736
+ if (telegramDefaultConfig.ok && !telegramDefaultConfig.botKey && String(telegramDefaultConfig.token || "").trim()) {
3737
+ addDoctorCheck(
3738
+ rows,
3739
+ "warn",
3740
+ "telegram legacy token",
3741
+ "TELEGRAM_BOT_TOKEN fallback is still active; run bot migrate --provider telegram to move to named entries",
3742
+ );
3743
+ }
3744
+ }
3665
3745
  }
3666
3746
 
3667
3747
  for (const cliBin of MCP_CLIENTS) {
@@ -79,6 +79,8 @@ function printBotUsage(deps) {
79
79
  ` ${cliName} bot add [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--yes <true|false>]`,
80
80
  ` ${cliName} bot edit [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--bot-key <key>]`,
81
81
  ` ${cliName} bot remove [--provider <telegram|slack|kakaotalk>] [--non-interactive <true|false>] [--bot-key <key>]`,
82
+ ` ${cliName} bot set-default --provider telegram [--bot-key <key>] [--non-interactive <true|false>]`,
83
+ ` ${cliName} bot migrate --provider telegram [--bot-key <key>] [--bot-id <uuid>] [--bot-name <name>] [--keep-legacy-token <true|false>] [--non-interactive <true|false>]`,
82
84
  ` ${cliName} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-key <key>] [--timeout-seconds <n>] [--json <true|false>]`,
83
85
  ` ${cliName} bot global --provider telegram [--non-interactive <true|false>] [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <key>]`,
84
86
  "",
@@ -201,6 +203,10 @@ function removeTelegramEntry(parsedEnv, botKey) {
201
203
  return parsed;
202
204
  }
203
205
 
206
+ function hasNamedTelegramEntry(parsedEnv, deps) {
207
+ return telegramEntriesForDisplay(parsedEnv, deps).length > 0;
208
+ }
209
+
204
210
  function upsertTelegramEntry(parsedEnv, entry) {
205
211
  const parsed = removeTelegramEntry(parsedEnv, entry.key);
206
212
  const keys = telegramEntryEnvKeys(entry.key);
@@ -1194,6 +1200,8 @@ async function runBotSetup(ui, flags, deps) {
1194
1200
  { value: "add", label: "Add bot" },
1195
1201
  { value: "edit", label: "Edit bot" },
1196
1202
  { value: "remove", label: "Remove bot" },
1203
+ { value: "set_default", label: "Set default bot" },
1204
+ { value: "migrate", label: "Migrate legacy token" },
1197
1205
  { value: "verify", label: "Verify bot" },
1198
1206
  { value: "global", label: "Telegram global settings" },
1199
1207
  ];
@@ -1244,6 +1252,14 @@ async function runBotSetup(ui, flags, deps) {
1244
1252
  }
1245
1253
  return;
1246
1254
  }
1255
+ if (action.value === "set_default" && provider === "telegram") {
1256
+ await runBotSetDefault(ui, flags, deps);
1257
+ return;
1258
+ }
1259
+ if (action.value === "migrate" && provider === "telegram") {
1260
+ await runBotMigrate(ui, flags, deps);
1261
+ return;
1262
+ }
1247
1263
  if (action.value === "verify") {
1248
1264
  await verifyProviderEntry(ui, provider, flags, deps);
1249
1265
  return;
@@ -1344,6 +1360,77 @@ async function runBotVerify(ui, flags, deps) {
1344
1360
  await verifyProviderEntry(ui, provider, flags, deps);
1345
1361
  }
1346
1362
 
1363
+ async function runBotSetDefault(ui, flags, deps) {
1364
+ const provider = String(flags.provider || "").trim()
1365
+ ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
1366
+ : "telegram";
1367
+ if (provider !== "telegram") {
1368
+ throw new Error("bot set-default currently supports only --provider telegram");
1369
+ }
1370
+ const state = loadProviderEnvState("telegram", deps);
1371
+ const parsed = { ...state.parsed };
1372
+ const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
1373
+ const selected = nonInteractive
1374
+ ? findTelegramEntryByFlags(parsed, flags, deps)
1375
+ : await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot");
1376
+ if (!selected) {
1377
+ throw new Error("Telegram bot selector is required for set-default");
1378
+ }
1379
+ parsed.TELEGRAM_DEFAULT_BOT_KEY = selected.key;
1380
+ const filePath = writeProviderEnvState("telegram", parsed, deps);
1381
+ process.stdout.write(`Set TELEGRAM_DEFAULT_BOT_KEY=${selected.key} in ${filePath}\n`);
1382
+ }
1383
+
1384
+ async function runBotMigrate(ui, flags, deps) {
1385
+ const provider = String(flags.provider || "").trim()
1386
+ ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
1387
+ : "telegram";
1388
+ if (provider !== "telegram") {
1389
+ throw new Error("bot migrate currently supports only --provider telegram");
1390
+ }
1391
+ const state = loadProviderEnvState("telegram", deps);
1392
+ const parsed = { ...state.parsed };
1393
+ const legacyToken = String(parsed.TELEGRAM_BOT_TOKEN || "").trim();
1394
+ if (!legacyToken) {
1395
+ process.stdout.write("No legacy TELEGRAM_BOT_TOKEN value is set.\n");
1396
+ return;
1397
+ }
1398
+ const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
1399
+ const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
1400
+ const existingKeys = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key));
1401
+ const defaultKeyHint = normalizeBotKey(flags["bot-key"] || parsed.TELEGRAM_DEFAULT_BOT_KEY || "main", "main");
1402
+ const botKey = normalizeBotKey(
1403
+ nonInteractive
1404
+ ? String(flags["bot-key"] || defaultKeyHint).trim()
1405
+ : await promptRequiredLine(ui, "Local Telegram bot key for migrated entry", defaultKeyHint),
1406
+ defaultKeyHint,
1407
+ );
1408
+ if (existingKeys.has(botKey)) {
1409
+ throw new Error(`Telegram bot key "${botKey}" already exists`);
1410
+ }
1411
+ const nextParsed = upsertTelegramEntry(parsed, {
1412
+ key: botKey,
1413
+ serverBotID: String(flags["server-bot-id"] || flags["bot-id"] || "").trim(),
1414
+ username: requireDependency(deps, "normalizeTelegramBotUsername")(flags["bot-name"] || flags.username || ""),
1415
+ token: legacyToken,
1416
+ 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"] || "", ""),
1421
+ });
1422
+ if (!hasNamedTelegramEntry(parsed, deps) || !String(nextParsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim()) {
1423
+ nextParsed.TELEGRAM_DEFAULT_BOT_KEY = botKey;
1424
+ }
1425
+ if (!boolFromRaw(flags["keep-legacy-token"], false)) {
1426
+ nextParsed.TELEGRAM_BOT_TOKEN = "";
1427
+ }
1428
+ const filePath = writeProviderEnvState("telegram", nextParsed, deps);
1429
+ process.stdout.write(
1430
+ `Migrated TELEGRAM_BOT_TOKEN to named entry "${botKey}" in ${filePath}${boolFromRaw(flags["keep-legacy-token"], false) ? " (legacy token preserved)" : ""}\n`,
1431
+ );
1432
+ }
1433
+
1347
1434
  async function runBotGlobal(ui, flags, deps) {
1348
1435
  const provider = String(flags.provider || "").trim()
1349
1436
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
@@ -1389,6 +1476,14 @@ export async function runBotCommand(argv, deps) {
1389
1476
  await runBotRemove(ui, flags, deps);
1390
1477
  return;
1391
1478
  }
1479
+ if (command === "set-default") {
1480
+ await runBotSetDefault(ui, flags, deps);
1481
+ return;
1482
+ }
1483
+ if (command === "migrate") {
1484
+ await runBotMigrate(ui, flags, deps);
1485
+ return;
1486
+ }
1392
1487
  if (command === "verify") {
1393
1488
  await runBotVerify(ui, flags, deps);
1394
1489
  return;
@@ -1397,7 +1492,7 @@ export async function runBotCommand(argv, deps) {
1397
1492
  await runBotGlobal(ui, flags, deps);
1398
1493
  return;
1399
1494
  }
1400
- throw new Error("bot requires a subcommand: setup | list | show | add | edit | remove | verify | global");
1495
+ throw new Error("bot requires a subcommand: setup | list | show | add | edit | remove | set-default | migrate | verify | global");
1401
1496
  } finally {
1402
1497
  ui.close();
1403
1498
  }
@@ -294,6 +294,23 @@ export async function runSelftestBotCommands(push, deps) {
294
294
  `client=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
295
295
  );
296
296
 
297
+ await runCLI({
298
+ cliPath,
299
+ args: [
300
+ "bot", "set-default",
301
+ "--provider", "telegram",
302
+ "--bot-key", "main_test",
303
+ "--non-interactive", "true",
304
+ ],
305
+ env,
306
+ });
307
+ const defaultState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
308
+ push(
309
+ "bot_set_default_updates_default_bot_key",
310
+ String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
311
+ `default=${String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "")}`,
312
+ );
313
+
297
314
  const verifyResult = await runCLI({
298
315
  cliPath,
299
316
  args: [
@@ -341,6 +358,37 @@ export async function runSelftestBotCommands(push, deps) {
341
358
  ensureArray(telegramEntry.entries).length === 0,
342
359
  `entries=${String(ensureArray(telegramEntry.entries).length)}`,
343
360
  );
361
+
362
+ const migratedEnv = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
363
+ migratedEnv.TELEGRAM_BOT_TOKEN = "legacy-selftest-token";
364
+ fs.writeFileSync(
365
+ telegramEnvPath,
366
+ Object.entries(migratedEnv).map(([key, value]) => `${key}=${String(value ?? "")}`).join("\n") + "\n",
367
+ "utf8",
368
+ );
369
+ await runCLI({
370
+ cliPath,
371
+ args: [
372
+ "bot", "migrate",
373
+ "--provider", "telegram",
374
+ "--bot-key", "legacy_main",
375
+ "--bot-id", mock.bots[0].id,
376
+ "--bot-name", "MonitorSelftestBot",
377
+ "--role-profile", "monitor",
378
+ "--client", "codex",
379
+ "--permission-mode", "read_only",
380
+ "--reasoning-effort", "low",
381
+ "--non-interactive", "true",
382
+ ],
383
+ env,
384
+ });
385
+ const migratedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
386
+ push(
387
+ "bot_migrate_converts_legacy_token_to_named_entry",
388
+ String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "") === "legacy-selftest-token"
389
+ && String(migratedState.TELEGRAM_BOT_TOKEN || "") === "",
390
+ `legacy_named=${String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "")} fallback=${String(migratedState.TELEGRAM_BOT_TOKEN || "")}`,
391
+ );
344
392
  } catch (err) {
345
393
  push("bot_commands_smoke", false, String(err?.message || err));
346
394
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.63",
3
+ "version": "0.2.64",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [