metheus-governance-mcp-cli 0.2.67 → 0.2.69

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
@@ -225,10 +225,11 @@ metheus-governance-mcp-cli bot verify --provider telegram --bot-key main
225
225
  Behavior:
226
226
 
227
227
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
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.
228
+ - `bot add` without flags starts a guided question flow: provider -> bot responsibility -> local bot key -> token -> verify.
229
+ - For Telegram, the CLI asks for the server-side bot responsibility first and automatically resolves the matching server bot profile when the role is unique.
230
+ - When you choose a Telegram responsibility such as `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.
230
231
  - `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
+ - 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.
232
233
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
233
234
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
234
235
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
@@ -381,6 +382,12 @@ Role profile fields:
381
382
  - `permission_mode`: `read_only`, `workspace_write`, `danger_full_access`
382
383
  - `reasoning_effort`: `low`, `medium`, `high`
383
384
 
385
+ Recommended role mapping:
386
+ - `monitor`: `read_only` + `low`
387
+ - `review`: `read_only` + `medium`
388
+ - `worker`: `danger_full_access` + `medium`
389
+ - `approval`: `danger_full_access` + `high`
390
+
384
391
  Role profile note:
385
392
  - Claude maps `reasoning_effort` to `--effort`.
386
393
  - Codex maps `reasoning_effort` to `-c model_reasoning_effort="..."`.
package/cli.mjs CHANGED
@@ -809,13 +809,13 @@ function defaultBotRunnerRoleProfiles() {
809
809
  worker: {
810
810
  client: "codex",
811
811
  model: "",
812
- permission_mode: "workspace_write",
812
+ permission_mode: "danger_full_access",
813
813
  reasoning_effort: "medium",
814
814
  },
815
815
  approval: {
816
816
  client: "gemini",
817
817
  model: "",
818
- permission_mode: "read_only",
818
+ permission_mode: "danger_full_access",
819
819
  reasoning_effort: "high",
820
820
  },
821
821
  };
@@ -497,6 +497,7 @@ function saveTelegramBotEdit(parsed, selected, current, deps) {
497
497
  }
498
498
 
499
499
  async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps) {
500
+ let serverRoleAutoResolved = "";
500
501
  const bindingAction = await promptKeepChangeClear(ui, "Server bot binding", {
501
502
  allowClear: true,
502
503
  defaultValue: current.serverBotID ? "keep" : "change",
@@ -513,6 +514,7 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
513
514
  if (!current.roleProfile && serverBot.role) {
514
515
  current.roleProfile = serverBot.role;
515
516
  }
517
+ serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim();
516
518
  applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
517
519
  } else if (bindingAction === "clear") {
518
520
  current.serverBotID = "";
@@ -553,66 +555,68 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
553
555
  }
554
556
  }
555
557
 
556
- const roleAction = await promptKeepChangeClear(ui, "Role profile", {
557
- allowClear: true,
558
- defaultValue: current.roleProfile ? "keep" : "change",
559
- });
560
- if (roleAction === "change") {
561
- current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
562
- await promptTelegramRoleProfile(ui, deps, current.roleProfile),
563
- );
564
- applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile, deps));
565
- } else if (roleAction === "clear") {
566
- current.roleProfile = "";
567
- }
558
+ if (!serverRoleAutoResolved) {
559
+ const roleAction = await promptKeepChangeClear(ui, "Role profile", {
560
+ allowClear: true,
561
+ defaultValue: current.roleProfile ? "keep" : "change",
562
+ });
563
+ if (roleAction === "change") {
564
+ current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
565
+ await promptTelegramRoleProfile(ui, deps, current.roleProfile),
566
+ );
567
+ applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile, deps));
568
+ } else if (roleAction === "clear") {
569
+ current.roleProfile = "";
570
+ }
568
571
 
569
- const clientAction = await promptKeepChangeClear(ui, "AI client", {
570
- allowClear: true,
571
- defaultValue: current.client ? "keep" : "change",
572
- });
573
- if (clientAction === "change") {
574
- current.client = requireDependency(deps, "normalizeLocalAIClientName")(
575
- await promptAIClient(ui, deps, current.client),
576
- "",
577
- );
578
- } else if (clientAction === "clear") {
579
- current.client = "";
580
- }
572
+ const clientAction = await promptKeepChangeClear(ui, "AI client", {
573
+ allowClear: true,
574
+ defaultValue: current.client ? "keep" : "change",
575
+ });
576
+ if (clientAction === "change") {
577
+ current.client = requireDependency(deps, "normalizeLocalAIClientName")(
578
+ await promptAIClient(ui, deps, current.client),
579
+ "",
580
+ );
581
+ } else if (clientAction === "clear") {
582
+ current.client = "";
583
+ }
581
584
 
582
- const modelAction = await promptKeepChangeClear(ui, "AI model", {
583
- allowClear: true,
584
- defaultValue: current.model ? "keep" : "change",
585
- });
586
- if (modelAction === "change") {
587
- current.model = await promptLine(ui, "AI model", current.model);
588
- } else if (modelAction === "clear") {
589
- current.model = "";
590
- }
585
+ const modelAction = await promptKeepChangeClear(ui, "AI model", {
586
+ allowClear: true,
587
+ defaultValue: current.model ? "keep" : "change",
588
+ });
589
+ if (modelAction === "change") {
590
+ current.model = await promptLine(ui, "AI model", current.model);
591
+ } else if (modelAction === "clear") {
592
+ current.model = "";
593
+ }
591
594
 
592
- const permissionAction = await promptKeepChangeClear(ui, "AI permission mode", {
593
- allowClear: true,
594
- defaultValue: current.permissionMode ? "keep" : "change",
595
- });
596
- if (permissionAction === "change") {
597
- current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
598
- await promptPermissionMode(ui, current.permissionMode),
599
- "",
600
- );
601
- } else if (permissionAction === "clear") {
602
- current.permissionMode = "";
603
- }
595
+ const permissionAction = await promptKeepChangeClear(ui, "AI permission mode", {
596
+ allowClear: true,
597
+ defaultValue: current.permissionMode ? "keep" : "change",
598
+ });
599
+ if (permissionAction === "change") {
600
+ current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
601
+ await promptPermissionMode(ui, current.permissionMode),
602
+ "",
603
+ );
604
+ } else if (permissionAction === "clear") {
605
+ current.permissionMode = "";
606
+ }
604
607
 
605
- const reasoningAction = await promptKeepChangeClear(ui, "AI reasoning effort", {
606
- allowClear: true,
607
- defaultValue: current.reasoningEffort ? "keep" : "change",
608
- });
609
- if (reasoningAction === "change") {
610
- current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
611
- await promptReasoningEffort(ui, current.reasoningEffort),
612
- "",
613
- );
614
- } else if (reasoningAction === "clear") {
615
- current.reasoningEffort = "";
608
+ const reasoningAction = await promptKeepChangeClear(ui, "AI reasoning effort", {
609
+ allowClear: true,
610
+ defaultValue: current.reasoningEffort ? "keep" : "change",
611
+ });
612
+ if (reasoningAction === "change") {
613
+ current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
614
+ await promptReasoningEffort(ui, current.reasoningEffort),
615
+ "",
616
+ );
617
+ } else if (reasoningAction === "clear") {
618
+ current.reasoningEffort = "";
619
+ }
616
620
  }
617
621
 
618
622
  const botKeyAction = await promptKeepChangeClear(ui, "Local bot key", {
@@ -843,7 +847,11 @@ async function resolveTelegramEntryForShow(ui, parsedEnv, flags, deps) {
843
847
  async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps) {
844
848
  const lookup = requireDependency(deps, "listServerBots");
845
849
  const result = await lookup({ provider, baseURL, timeoutSeconds });
846
- const bots = ensureArray(result?.bots);
850
+ const bots = ensureArray(result?.bots).map((bot) => ({
851
+ id: String(bot?.id || "").trim(),
852
+ role: String(bot?.role || bot?.bot_role || "").trim(),
853
+ name: String(bot?.name || "").trim(),
854
+ })).filter((bot) => bot.id);
847
855
  if (!bots.length) {
848
856
  if (result?.error) {
849
857
  process.stdout.write(`Server bot profiles unavailable: ${result.error}\n`);
@@ -855,11 +863,90 @@ async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps) {
855
863
  name: "",
856
864
  };
857
865
  }
866
+ if (bots.length === 1) {
867
+ const match = bots[0] || {};
868
+ return {
869
+ botID: String(match.id || "").trim(),
870
+ role: String(match.role || "").trim(),
871
+ name: String(match.name || "").trim(),
872
+ };
873
+ }
874
+ const preferredRoleOrder = ["monitor", "review", "worker", "approval"];
875
+ const roleGroups = new Map();
876
+ bots.forEach((bot) => {
877
+ const roleKey = String(bot.role || "").trim() || "(no role)";
878
+ if (!roleGroups.has(roleKey)) {
879
+ roleGroups.set(roleKey, []);
880
+ }
881
+ roleGroups.get(roleKey).push(bot);
882
+ });
883
+ const sortedRoles = [...roleGroups.keys()].sort((left, right) => {
884
+ const leftIndex = preferredRoleOrder.indexOf(left);
885
+ const rightIndex = preferredRoleOrder.indexOf(right);
886
+ if (leftIndex >= 0 && rightIndex >= 0) return leftIndex - rightIndex;
887
+ if (leftIndex >= 0) return -1;
888
+ if (rightIndex >= 0) return 1;
889
+ return left.localeCompare(right);
890
+ });
891
+ const roleChoice = await promptChoice(
892
+ ui,
893
+ `Select ${providerLabel(provider, deps)} bot responsibility`,
894
+ [
895
+ ...sortedRoles.map((role) => {
896
+ const matches = ensureArray(roleGroups.get(role));
897
+ if (matches.length === 1) {
898
+ return {
899
+ value: `role:${role}`,
900
+ label: role,
901
+ description: `${matches[0].name || "(unnamed)"} -> ${matches[0].id}`,
902
+ };
903
+ }
904
+ return {
905
+ value: `group:${role}`,
906
+ label: role,
907
+ description: `${matches.length} server bot profiles`,
908
+ };
909
+ }),
910
+ {
911
+ value: "__manual__",
912
+ label: "Manual entry",
913
+ description: "type the server bot UUID yourself",
914
+ },
915
+ {
916
+ value: "__blank__",
917
+ label: "Leave empty",
918
+ description: "skip server bot UUID binding",
919
+ },
920
+ ],
921
+ { defaultIndex: 0 },
922
+ );
923
+ if (!roleChoice || roleChoice.value === "__blank__") {
924
+ return { botID: "", role: "", name: "" };
925
+ }
926
+ if (roleChoice.value === "__manual__") {
927
+ const manualID = await promptLine(ui, "Server bot UUID", "");
928
+ return {
929
+ botID: manualID,
930
+ role: "",
931
+ name: "",
932
+ };
933
+ }
934
+ if (String(roleChoice.value || "").startsWith("role:")) {
935
+ const selectedRole = String(roleChoice.value || "").slice(5);
936
+ const match = ensureArray(roleGroups.get(selectedRole))[0] || {};
937
+ return {
938
+ botID: String(match.id || "").trim(),
939
+ role: String(match.role || "").trim(),
940
+ name: String(match.name || "").trim(),
941
+ };
942
+ }
943
+ const selectedRole = String(roleChoice.value || "").slice(6);
944
+ const matches = ensureArray(roleGroups.get(selectedRole));
858
945
  const choice = await promptChoice(
859
946
  ui,
860
- `Select server ${providerLabel(provider, deps)} bot profile`,
947
+ `Select server ${providerLabel(provider, deps)} bot profile for role "${selectedRole}"`,
861
948
  [
862
- ...bots.map((bot) => ({
949
+ ...matches.map((bot) => ({
863
950
  value: bot.id,
864
951
  label: `${bot.name || "(unnamed)"} [${bot.role || "-"}]`,
865
952
  description: bot.id,
@@ -884,11 +971,11 @@ async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps) {
884
971
  const manualID = await promptLine(ui, "Server bot UUID", "");
885
972
  return {
886
973
  botID: manualID,
887
- role: "",
974
+ role: selectedRole,
888
975
  name: "",
889
976
  };
890
977
  }
891
- const match = bots.find((bot) => bot.id === choice.value) || {};
978
+ const match = matches.find((bot) => bot.id === choice.value) || {};
892
979
  return {
893
980
  botID: String(match.id || "").trim(),
894
981
  role: String(match.role || "").trim(),
@@ -1070,34 +1157,52 @@ async function addTelegramBot(ui, flags, deps) {
1070
1157
  const username = requireDependency(deps, "normalizeTelegramBotUsername")(
1071
1158
  nonInteractive
1072
1159
  ? String(flags.username || usernameDefault).trim()
1073
- : await promptLine(ui, "Telegram username (without @)", usernameDefault),
1160
+ : (usernameDefault || await promptLine(ui, "Telegram username (without @)", usernameDefault)),
1074
1161
  );
1075
1162
  const defaultRoleProfile = firstNonEmptyString([flags["role-profile"], serverBot.role, ""]);
1163
+ const roleProfileAutoResolved = !nonInteractive && !String(flags["role-profile"] || "").trim() && Boolean(defaultRoleProfile);
1076
1164
  const roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
1077
1165
  nonInteractive
1078
1166
  ? String(flags["role-profile"] || defaultRoleProfile).trim()
1079
- : await promptTelegramRoleProfile(ui, deps, defaultRoleProfile),
1167
+ : (roleProfileAutoResolved ? defaultRoleProfile : await promptTelegramRoleProfile(ui, deps, defaultRoleProfile)),
1080
1168
  );
1081
1169
  const roleProfileDefaults = resolveRoleProfileDefaults(roleProfile, deps);
1170
+ const aiFlagOverrides = hasOwnFlag(flags, "client")
1171
+ || hasOwnFlag(flags, "ai-client")
1172
+ || hasOwnFlag(flags, "model")
1173
+ || hasOwnFlag(flags, "ai-model")
1174
+ || hasOwnFlag(flags, "permission-mode")
1175
+ || hasOwnFlag(flags, "ai-permission-mode")
1176
+ || hasOwnFlag(flags, "reasoning-effort")
1177
+ || hasOwnFlag(flags, "ai-reasoning-effort");
1178
+ const autoApplyRoleDefaults = !nonInteractive && roleProfileAutoResolved && !aiFlagOverrides;
1082
1179
  const client = requireDependency(deps, "normalizeLocalAIClientName")(
1083
1180
  nonInteractive
1084
1181
  ? firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client])
1085
- : await promptAIClient(ui, deps, firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client])),
1182
+ : (autoApplyRoleDefaults
1183
+ ? firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client])
1184
+ : await promptAIClient(ui, deps, firstNonEmptyString([getAIClientFlag(flags), roleProfileDefaults.client]))),
1086
1185
  "",
1087
1186
  );
1088
1187
  const model = nonInteractive
1089
1188
  ? firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model])
1090
- : await promptLine(ui, "AI model", firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model]));
1189
+ : (autoApplyRoleDefaults
1190
+ ? firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model])
1191
+ : await promptLine(ui, "AI model", firstNonEmptyString([getAIModelFlag(flags), roleProfileDefaults.model])));
1091
1192
  const permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
1092
1193
  nonInteractive
1093
1194
  ? firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode])
1094
- : await promptPermissionMode(ui, firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode])),
1195
+ : (autoApplyRoleDefaults
1196
+ ? firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode])
1197
+ : await promptPermissionMode(ui, firstNonEmptyString([getAIPermissionModeFlag(flags), roleProfileDefaults.permissionMode]))),
1095
1198
  "",
1096
1199
  );
1097
1200
  const reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
1098
1201
  nonInteractive
1099
1202
  ? firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort])
1100
- : await promptReasoningEffort(ui, firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort])),
1203
+ : (autoApplyRoleDefaults
1204
+ ? firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort])
1205
+ : await promptReasoningEffort(ui, firstNonEmptyString([getAIReasoningEffortFlag(flags), roleProfileDefaults.reasoningEffort]))),
1101
1206
  "",
1102
1207
  );
1103
1208
 
@@ -1113,9 +1218,10 @@ async function addTelegramBot(ui, flags, deps) {
1113
1218
  reasoningEffort,
1114
1219
  });
1115
1220
 
1221
+ const hasCurrentDefault = Boolean(String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim());
1116
1222
  if (
1117
1223
  boolFromRaw(flags.default, false)
1118
- || (!nonInteractive && await promptYesNo(ui, "Set this bot as TELEGRAM_DEFAULT_BOT_KEY?", !String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim()))
1224
+ || (!nonInteractive && !hasCurrentDefault && await promptYesNo(ui, "Set this bot as TELEGRAM_DEFAULT_BOT_KEY?", true))
1119
1225
  ) {
1120
1226
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = botKey;
1121
1227
  }
@@ -271,17 +271,9 @@ export async function runSelftestBotCommands(push, deps) {
271
271
  ...env,
272
272
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
273
273
  "1", // provider: telegram
274
- "1", // server bot: first listed bot
275
274
  "main_test",
276
275
  "selftest-main-token",
277
276
  "y", // verify now
278
- "", // keep verified username
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
284
- "y", // set as default
285
277
  ]),
286
278
  },
287
279
  });
@@ -294,7 +286,7 @@ export async function runSelftestBotCommands(push, deps) {
294
286
  && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_CLIENT || "") === "codex"
295
287
  && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_PERMISSION_MODE || "") === "read_only"
296
288
  && String(addState.TELEGRAM_BOT_MAIN_TEST_AI_REASONING_EFFORT || "") === "low"
297
- && String(addState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main_test",
289
+ && String(addState.TELEGRAM_DEFAULT_BOT_KEY || "") === "main",
298
290
  `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 || "")}`,
299
291
  );
300
292
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.67",
3
+ "version": "0.2.69",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [
package/postinstall.mjs CHANGED
@@ -80,6 +80,24 @@ function botRunnerTemplate() {
80
80
  permission_mode: "read_only",
81
81
  reasoning_effort: "low",
82
82
  },
83
+ review: {
84
+ client: "claude",
85
+ model: "",
86
+ permission_mode: "read_only",
87
+ reasoning_effort: "medium",
88
+ },
89
+ worker: {
90
+ client: "codex",
91
+ model: "",
92
+ permission_mode: "danger_full_access",
93
+ reasoning_effort: "medium",
94
+ },
95
+ approval: {
96
+ client: "gemini",
97
+ model: "",
98
+ permission_mode: "danger_full_access",
99
+ reasoning_effort: "high",
100
+ },
83
101
  },
84
102
  bot_bindings: {
85
103
  "<binding_key>": {