metheus-governance-mcp-cli 0.2.65 → 0.2.67

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
@@ -226,7 +226,12 @@ Behavior:
226
226
 
227
227
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
228
228
  - `bot add` without flags starts a guided question flow: provider -> server bot -> local bot key -> token -> verify -> role/AI binding -> default bot choice.
229
+ - When you choose a server Telegram bot profile such as `RyoAI_bot [monitor]`, the CLI automatically uses that server role as the local `role_profile` and fills blank AI defaults from your local `bot-runner.json` `role_profiles` mapping.
229
230
  - `bot edit` without flags starts a guided numbered flow: provider -> bot entry -> guided edit -> step-by-step field choices.
231
+ - In guided `bot edit`, changing the bound server Telegram bot profile can also auto-fill blank local AI fields from the selected server role.
232
+ - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
233
+ - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
234
+ - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
230
235
  - Telegram supports named local bot entries with:
231
236
  - `SERVER_BOT_ID`
232
237
  - `USERNAME`
@@ -182,6 +182,26 @@ async function promptYesNo(ui, promptText, defaultValue = true) {
182
182
  }
183
183
  }
184
184
 
185
+ async function promptConfirmChoice(
186
+ ui,
187
+ title,
188
+ {
189
+ confirmLabel = "Confirm",
190
+ confirmDescription = "",
191
+ cancelLabel = "Cancel",
192
+ cancelDescription = "",
193
+ defaultValue = "confirm",
194
+ } = {},
195
+ ) {
196
+ const options = [
197
+ { value: "confirm", label: confirmLabel, description: confirmDescription },
198
+ { value: "cancel", label: cancelLabel, description: cancelDescription },
199
+ ];
200
+ const defaultIndex = defaultValue === "cancel" ? 1 : 0;
201
+ const selected = await promptChoice(ui, title, options, { defaultIndex });
202
+ return selected?.value === "confirm";
203
+ }
204
+
185
205
  function formatChoiceLabel(option) {
186
206
  return `${String(option.label || option.value || "").trim()}${option.description ? ` - ${option.description}` : ""}`;
187
207
  }
@@ -493,6 +513,7 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
493
513
  if (!current.roleProfile && serverBot.role) {
494
514
  current.roleProfile = serverBot.role;
495
515
  }
516
+ applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
496
517
  } else if (bindingAction === "clear") {
497
518
  current.serverBotID = "";
498
519
  }
@@ -540,6 +561,7 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
540
561
  current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
541
562
  await promptTelegramRoleProfile(ui, deps, current.roleProfile),
542
563
  );
564
+ applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile, deps));
543
565
  } else if (roleAction === "clear") {
544
566
  current.roleProfile = "";
545
567
  }
@@ -921,6 +943,49 @@ async function promptReasoningEffort(ui, defaultValue = "") {
921
943
  return String(selected?.value || "").trim();
922
944
  }
923
945
 
946
+ function resolveRoleProfileDefaults(roleProfileName, deps) {
947
+ const profileName = String(roleProfileName || "").trim();
948
+ if (!profileName) {
949
+ return {
950
+ client: "",
951
+ model: "",
952
+ permissionMode: "",
953
+ reasoningEffort: "",
954
+ };
955
+ }
956
+ const config = requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true });
957
+ const profile = safeObject(safeObject(config.roleProfiles || {})[profileName]);
958
+ return {
959
+ client: requireDependency(deps, "normalizeLocalAIClientName")(profile.client || "", ""),
960
+ model: String(profile.model || "").trim(),
961
+ permissionMode: requireDependency(deps, "normalizeLocalAIPermissionMode")(
962
+ profile.permission_mode || profile.permissionMode || "",
963
+ "",
964
+ ),
965
+ reasoningEffort: requireDependency(deps, "normalizeLocalAIReasoningEffort")(
966
+ profile.reasoning_effort || profile.reasoningEffort || "",
967
+ "",
968
+ ),
969
+ };
970
+ }
971
+
972
+ function applyRoleProfileDefaults(entry, defaults, { overwrite = false } = {}) {
973
+ const current = safeObject(entry);
974
+ const resolved = safeObject(defaults);
975
+ if (overwrite || !String(current.client || "").trim()) {
976
+ current.client = String(resolved.client || "").trim();
977
+ }
978
+ if (overwrite || !String(current.model || "").trim()) {
979
+ current.model = String(resolved.model || "").trim();
980
+ }
981
+ if (overwrite || !String(current.permissionMode || "").trim()) {
982
+ current.permissionMode = String(resolved.permissionMode || "").trim();
983
+ }
984
+ if (overwrite || !String(current.reasoningEffort || "").trim()) {
985
+ current.reasoningEffort = String(resolved.reasoningEffort || "").trim();
986
+ }
987
+ }
988
+
924
989
  function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
925
990
  return {
926
991
  ok: true,
@@ -1013,23 +1078,26 @@ async function addTelegramBot(ui, flags, deps) {
1013
1078
  ? String(flags["role-profile"] || defaultRoleProfile).trim()
1014
1079
  : await promptTelegramRoleProfile(ui, deps, defaultRoleProfile),
1015
1080
  );
1081
+ const roleProfileDefaults = resolveRoleProfileDefaults(roleProfile, deps);
1016
1082
  const client = requireDependency(deps, "normalizeLocalAIClientName")(
1017
1083
  nonInteractive
1018
- ? getAIClientFlag(flags)
1019
- : await promptAIClient(ui, deps, getAIClientFlag(flags)),
1084
+ ? firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client])
1085
+ : await promptAIClient(ui, deps, firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client])),
1020
1086
  "",
1021
1087
  );
1022
- const model = nonInteractive ? getAIModelFlag(flags) : await promptLine(ui, "AI model", getAIModelFlag(flags));
1088
+ const model = nonInteractive
1089
+ ? firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model])
1090
+ : await promptLine(ui, "AI model", firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model]));
1023
1091
  const permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
1024
1092
  nonInteractive
1025
- ? getAIPermissionModeFlag(flags)
1026
- : await promptPermissionMode(ui, getAIPermissionModeFlag(flags)),
1093
+ ? firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode])
1094
+ : await promptPermissionMode(ui, firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode])),
1027
1095
  "",
1028
1096
  );
1029
1097
  const reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
1030
1098
  nonInteractive
1031
- ? getAIReasoningEffortFlag(flags)
1032
- : await promptReasoningEffort(ui, getAIReasoningEffortFlag(flags)),
1099
+ ? firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort])
1100
+ : await promptReasoningEffort(ui, firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort])),
1033
1101
  "",
1034
1102
  );
1035
1103
 
@@ -1262,7 +1330,13 @@ async function removeTelegramBot(ui, deps) {
1262
1330
  const parsed = { ...state.parsed };
1263
1331
  const selected = await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry to remove");
1264
1332
  if (!selected) return;
1265
- if (!await promptYesNo(ui, `Remove Telegram bot "${selected.key}"?`, false)) {
1333
+ if (!await promptConfirmChoice(ui, `Remove Telegram bot "${selected.key}"?`, {
1334
+ confirmLabel: "Remove bot",
1335
+ confirmDescription: "delete this local Telegram bot entry",
1336
+ cancelLabel: "Cancel",
1337
+ cancelDescription: "keep the current local bot entry",
1338
+ defaultValue: "cancel",
1339
+ })) {
1266
1340
  process.stdout.write("Cancelled.\n");
1267
1341
  return;
1268
1342
  }
@@ -1338,7 +1412,23 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
1338
1412
  }
1339
1413
  }
1340
1414
  }
1341
- if (boolFromRaw(flags.json, false)) {
1415
+ const interactiveJsonChoice = (
1416
+ !boolFromRaw(flags.json, false)
1417
+ && !boolFromRaw(flags["non-interactive"] ?? flags.yes, false)
1418
+ )
1419
+ ? await promptChoice(
1420
+ ui,
1421
+ "Verification output format",
1422
+ [
1423
+ { value: "text", label: "Text output (Recommended)", description: "human-readable summary" },
1424
+ { value: "json", label: "JSON output", description: "machine-readable verification payload" },
1425
+ ],
1426
+ { defaultIndex: 0 },
1427
+ )
1428
+ : null;
1429
+ const outputAsJson = boolFromRaw(flags.json, false) || interactiveJsonChoice?.value === "json";
1430
+
1431
+ if (outputAsJson) {
1342
1432
  process.stdout.write(
1343
1433
  `${JSON.stringify({
1344
1434
  ok: overallOK,
@@ -1594,9 +1684,7 @@ async function runBotVerify(ui, flags, deps) {
1594
1684
  }
1595
1685
 
1596
1686
  async function runBotSetDefault(ui, flags, deps) {
1597
- const provider = String(flags.provider || "").trim()
1598
- ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
1599
- : "telegram";
1687
+ const provider = await selectProvider(ui, flags.provider, deps);
1600
1688
  if (provider !== "telegram") {
1601
1689
  throw new Error("bot set-default currently supports only --provider telegram");
1602
1690
  }
@@ -1609,6 +1697,16 @@ async function runBotSetDefault(ui, flags, deps) {
1609
1697
  if (!selected) {
1610
1698
  throw new Error("Telegram bot selector is required for set-default");
1611
1699
  }
1700
+ if (!nonInteractive && !await promptConfirmChoice(ui, `Set "${selected.key}" as TELEGRAM_DEFAULT_BOT_KEY?`, {
1701
+ confirmLabel: "Set default bot",
1702
+ confirmDescription: "make this the default local Telegram bot entry",
1703
+ cancelLabel: "Cancel",
1704
+ cancelDescription: "leave the current default unchanged",
1705
+ defaultValue: "confirm",
1706
+ })) {
1707
+ process.stdout.write("Cancelled.\n");
1708
+ return;
1709
+ }
1612
1710
  parsed.TELEGRAM_DEFAULT_BOT_KEY = selected.key;
1613
1711
  const filePath = writeProviderEnvState("telegram", parsed, deps);
1614
1712
  process.stdout.write(`Set TELEGRAM_DEFAULT_BOT_KEY=${selected.key} in ${filePath}\n`);
@@ -45,6 +45,45 @@ function readJSON(rawText) {
45
45
  return JSON.parse(String(rawText || "").trim() || "{}");
46
46
  }
47
47
 
48
+ function readTrailingJSON(rawText) {
49
+ const text = String(rawText || "");
50
+ const start = text.indexOf("{");
51
+ if (start < 0) {
52
+ return readJSON(text);
53
+ }
54
+ let depth = 0;
55
+ let inString = false;
56
+ let escaping = false;
57
+ for (let index = start; index < text.length; index += 1) {
58
+ const char = text[index];
59
+ if (inString) {
60
+ if (escaping) {
61
+ escaping = false;
62
+ } else if (char === "\\") {
63
+ escaping = true;
64
+ } else if (char === "\"") {
65
+ inString = false;
66
+ }
67
+ continue;
68
+ }
69
+ if (char === "\"") {
70
+ inString = true;
71
+ continue;
72
+ }
73
+ if (char === "{") {
74
+ depth += 1;
75
+ continue;
76
+ }
77
+ if (char === "}") {
78
+ depth -= 1;
79
+ if (depth === 0) {
80
+ return readJSON(text.slice(start, index + 1));
81
+ }
82
+ }
83
+ }
84
+ return readJSON(text.slice(start));
85
+ }
86
+
48
87
  function createMockServer() {
49
88
  const serverBots = [
50
89
  {
@@ -237,11 +276,11 @@ export async function runSelftestBotCommands(push, deps) {
237
276
  "selftest-main-token",
238
277
  "y", // verify now
239
278
  "", // keep verified username
240
- "3", // role profile: monitor
241
- "2", // ai client: codex
242
- "gpt-5-codex",
243
- "2", // permission: read_only
244
- "2", // reasoning: low
279
+ "", // keep role profile default from selected server role
280
+ "", // keep AI client default from role profile
281
+ "", // keep AI model default from role profile
282
+ "", // keep permission default from role profile
283
+ "", // keep reasoning default from role profile
245
284
  "y", // set as default
246
285
  ]),
247
286
  },
@@ -251,10 +290,12 @@ export async function runSelftestBotCommands(push, deps) {
251
290
  "bot_add_guided_creates_named_telegram_entry",
252
291
  String(addState.TELEGRAM_BOT_MAIN_TEST_SERVER_BOT_ID || "") === mock.bots[0].id
253
292
  && String(addState.TELEGRAM_BOT_MAIN_TEST_USERNAME || "").trim().toLowerCase() === "monitorselftestbot"
293
+ && String(addState.TELEGRAM_BOT_MAIN_TEST_ROLE_PROFILE || "") === "monitor"
254
294
  && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "") === "codex"
255
- && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "") === "gpt-5-codex"
295
+ && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_PERMISSION_MODE || "") === "read_only"
296
+ && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_REASONING_EFFORT || "") === "low"
256
297
  && String(addState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
257
- `default=${String(addState.TELEGRAM_DEFAULT_BOT_KEY || "")} client=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
298
+ `default=${String(addState.TELEGRAM_DEFAULT_BOT_KEY || "")} role=${String(addState.TELEGRAM_BOT_MAIN_TEST_ROLE_PROFILE || "")} client=${String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")}`,
258
299
  );
259
300
 
260
301
  const showResult = await runCLI({
@@ -338,6 +379,50 @@ export async function runSelftestBotCommands(push, deps) {
338
379
  `client=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "")} model=${String(editedState.TELEGRAM_BOT_MAIN_TEST_AI_MODEL || "")}`,
339
380
  );
340
381
 
382
+ await runCLI({
383
+ cliPath,
384
+ args: ["bot", "set-default"],
385
+ env: {
386
+ ...env,
387
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
388
+ "1", // provider: telegram
389
+ "1", // bot entry: main_test
390
+ "1", // confirm set default
391
+ ]),
392
+ },
393
+ });
394
+ const guidedDefaultState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
395
+ push(
396
+ "bot_set_default_guided_selects_entry",
397
+ String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
398
+ `default=${String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "")}`,
399
+ );
400
+
401
+ const guidedVerifyResult = await runCLI({
402
+ cliPath,
403
+ args: [
404
+ "bot", "verify",
405
+ "--base-url", baseURL,
406
+ "--timeout-seconds", "5",
407
+ ],
408
+ env: {
409
+ ...env,
410
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
411
+ "1", // provider: telegram
412
+ "1", // bot entry: main_test
413
+ "2", // output format: json
414
+ ]),
415
+ },
416
+ });
417
+ const guidedVerifyPayload = readTrailingJSON(guidedVerifyResult.stdout);
418
+ push(
419
+ "bot_verify_guided_can_emit_json",
420
+ guidedVerifyPayload.ok === true
421
+ && safeObject(guidedVerifyPayload.serverBinding).ok === true
422
+ && String(guidedVerifyPayload.client || "") === "claude",
423
+ `verify=${String(guidedVerifyPayload.ok)} server=${String(safeObject(guidedVerifyPayload.serverBinding).detail || "")}`,
424
+ );
425
+
341
426
  await runCLI({
342
427
  cliPath,
343
428
  args: [
@@ -435,16 +520,49 @@ export async function runSelftestBotCommands(push, deps) {
435
520
  `client=${String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_CLIENT || "")} model=${String(aliasAddState.TELEGRAM_BOT_EXPLICIT_TEST_AI_MODEL || "")}`,
436
521
  );
437
522
 
523
+ const guidedRemoveBeforeList = await runCLI({
524
+ cliPath,
525
+ args: [
526
+ "bot", "list",
527
+ "--provider", "telegram",
528
+ "--json", "true",
529
+ ],
530
+ env,
531
+ });
532
+ const guidedRemoveBeforePayload = ensureArray(readJSON(guidedRemoveBeforeList.stdout));
533
+ const guidedRemoveBeforeEntry = safeObject(guidedRemoveBeforePayload[0]);
534
+ const guidedRemoveEntries = ensureArray(guidedRemoveBeforeEntry.entries);
535
+ const explicitEntryIndex = guidedRemoveEntries.findIndex((entry) => String(safeObject(entry).key || "") === "explicit_test");
536
+
438
537
  await runCLI({
538
+ cliPath,
539
+ args: ["bot", "remove"],
540
+ env: {
541
+ ...env,
542
+ METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
543
+ "1", // provider: telegram
544
+ String(explicitEntryIndex + 1), // bot entry: explicit_test
545
+ "1", // confirm remove
546
+ ]),
547
+ },
548
+ });
549
+ const guidedRemoveList = await runCLI({
439
550
  cliPath,
440
551
  args: [
441
- "bot", "remove",
552
+ "bot", "list",
442
553
  "--provider", "telegram",
443
- "--bot-key", "explicit_test",
444
- "--non-interactive", "true",
554
+ "--json", "true",
445
555
  ],
446
556
  env,
447
557
  });
558
+ const guidedRemovePayload = ensureArray(readJSON(guidedRemoveList.stdout));
559
+ const guidedRemoveEntry = safeObject(guidedRemovePayload[0]);
560
+ push(
561
+ "bot_remove_guided_deletes_selected_entry",
562
+ explicitEntryIndex >= 0
563
+ && ensureArray(guidedRemoveEntry.entries).length === 0,
564
+ `entries=${String(ensureArray(guidedRemoveEntry.entries).length)} selected=${String(explicitEntryIndex + 1)}`,
565
+ );
448
566
 
449
567
  const migratedEnv = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
450
568
  migratedEnv.TELEGRAM_BOT_TOKEN = "legacy-selftest-token";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.65",
3
+ "version": "0.2.67",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [