metheus-governance-mcp-cli 0.2.92 → 0.2.93

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
@@ -164,6 +164,8 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
164
164
  - `~/.metheus/kakaotalk.env`
165
165
  - `~/.metheus/bot-runner.json`
166
166
 
167
+ When `telegram.env` already exists, bootstrap/setup keeps your secrets but auto-normalizes the file to the latest named-entry layout so stale keys such as `TELEGRAM_BOT_<NAME>_BOT_*` are rewritten to `TELEGRAM_BOT_<NAME>_*`.
168
+
167
169
  Fill provider bot secrets and provider-local transport options locally. Project chat destination identifiers should be managed on the Metheus server as project chat destinations, not as local env values and not inside legacy Chat Hooks/webhooks.
168
170
 
169
171
  `~/.metheus/bot-runner.json` is the local automation profile for:
package/cli.mjs CHANGED
@@ -1053,7 +1053,12 @@ function ensureProviderEnvTemplate(provider) {
1053
1053
  const filePath = providerEnvFilePath(provider);
1054
1054
  try {
1055
1055
  if (fs.existsSync(filePath)) {
1056
- return { filePath, created: false, existed: true };
1056
+ return {
1057
+ filePath,
1058
+ created: false,
1059
+ existed: true,
1060
+ normalized: normalizeExistingProviderEnvFile(provider, filePath),
1061
+ };
1057
1062
  }
1058
1063
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
1059
1064
  fs.writeFileSync(filePath, providerEnvTemplate(provider), "utf8");
@@ -2636,6 +2641,15 @@ function parseSimpleEnvText(rawText) {
2636
2641
  return out;
2637
2642
  }
2638
2643
 
2644
+ function formatProviderEnvValue(rawValue) {
2645
+ const text = String(rawValue ?? "");
2646
+ if (!text) return "";
2647
+ if (/^[A-Za-z0-9_./:@,\-]+$/.test(text)) {
2648
+ return text;
2649
+ }
2650
+ return JSON.stringify(text);
2651
+ }
2652
+
2639
2653
  function normalizeTelegramBotEnvKey(rawValue, fallback = "") {
2640
2654
  const normalized = String(rawValue || "")
2641
2655
  .trim()
@@ -2705,6 +2719,124 @@ function collectTelegramEnvBotEntries(parsedEnv) {
2705
2719
  return Array.from(entries.values());
2706
2720
  }
2707
2721
 
2722
+ function isTelegramEntryEnvKey(rawKey) {
2723
+ return /^TELEGRAM_BOT_([A-Z0-9_]+)_(TOKEN|USERNAME|SERVER_BOT_ID|ROLE_PROFILE|AI_CLIENT|AI_MODEL|AI_PERMISSION_MODE|AI_REASONING_EFFORT)$/i
2724
+ .test(String(rawKey || "").trim());
2725
+ }
2726
+
2727
+ function telegramEntryCommentNameForEnv(entry) {
2728
+ const current = safeObject(entry);
2729
+ const username = normalizeTelegramBotUsername(current.username || "");
2730
+ if (username) return username;
2731
+ const key = normalizeTelegramBotEnvKey(current.key || "", "telegram_bot");
2732
+ if (!key) return "telegram_bot";
2733
+ return key.endsWith("_bot") ? key : `${key}_bot`;
2734
+ }
2735
+
2736
+ function renderNormalizedTelegramEnv(parsedEnv) {
2737
+ const parsed = safeObject(parsedEnv);
2738
+ const entries = collectTelegramEnvBotEntries(parsed)
2739
+ .filter((entry) => (
2740
+ entry.token
2741
+ || entry.username
2742
+ || entry.serverBotID
2743
+ || entry.roleProfile
2744
+ || entry.client
2745
+ || entry.model
2746
+ || entry.permissionMode
2747
+ || entry.reasoningEffort
2748
+ ))
2749
+ .sort((left, right) => String(left.key || "").localeCompare(String(right.key || "")));
2750
+ const defaultBotKey = normalizeTelegramBotEnvKey(parsed.TELEGRAM_DEFAULT_BOT_KEY || "", "");
2751
+ if (defaultBotKey) {
2752
+ entries.sort((left, right) => {
2753
+ if (left.key === defaultBotKey) return -1;
2754
+ if (right.key === defaultBotKey) return 1;
2755
+ return String(left.key || "").localeCompare(String(right.key || ""));
2756
+ });
2757
+ }
2758
+ const lines = [
2759
+ "# Metheus local Telegram bot settings",
2760
+ "# Keep this file on your machine only. Do not commit it.",
2761
+ "# Store Telegram bot secrets, AI bindings, and local transport options only.",
2762
+ "",
2763
+ `TELEGRAM_API_BASE_URL=${formatProviderEnvValue(parsed.TELEGRAM_API_BASE_URL || "")}`,
2764
+ `TELEGRAM_AUTO_CLEAR_WEBHOOK=${boolFromRaw(parsed.TELEGRAM_AUTO_CLEAR_WEBHOOK, true) ? "true" : "false"}`,
2765
+ `TELEGRAM_ALLOWED_UPDATES=${formatProviderEnvValue(String(parsed.TELEGRAM_ALLOWED_UPDATES || "").trim() || "message,edited_message")}`,
2766
+ `TELEGRAM_DEFAULT_BOT_KEY=${formatProviderEnvValue(defaultBotKey || "")}`,
2767
+ "",
2768
+ "# Legacy fallback",
2769
+ `TELEGRAM_BOT_TOKEN=${formatProviderEnvValue(parsed.TELEGRAM_BOT_TOKEN || "")}`,
2770
+ "",
2771
+ ];
2772
+ if (!entries.length) {
2773
+ lines.push("# No named Telegram bot entries configured yet", "");
2774
+ } else {
2775
+ entries.forEach((entry) => {
2776
+ const upper = String(entry.key || "").trim().toUpperCase();
2777
+ lines.push(
2778
+ `# Telegram bot entry: ${telegramEntryCommentNameForEnv(entry)}`,
2779
+ `TELEGRAM_BOT_${upper}_SERVER_BOT_ID=${formatProviderEnvValue(entry.serverBotID || "")}`,
2780
+ );
2781
+ if (String(entry.username || "").trim()) {
2782
+ lines.push(`TELEGRAM_BOT_${upper}_USERNAME=${formatProviderEnvValue(entry.username || "")}`);
2783
+ }
2784
+ lines.push(
2785
+ `TELEGRAM_BOT_${upper}_TOKEN=${formatProviderEnvValue(entry.token || "")}`,
2786
+ `TELEGRAM_BOT_${upper}_ROLE_PROFILE=${formatProviderEnvValue(entry.roleProfile || "")}`,
2787
+ `TELEGRAM_BOT_${upper}_AI_CLIENT=${formatProviderEnvValue(entry.client || "")}`,
2788
+ `TELEGRAM_BOT_${upper}_AI_MODEL=${formatProviderEnvValue(entry.model || "")}`,
2789
+ `TELEGRAM_BOT_${upper}_AI_PERMISSION_MODE=${formatProviderEnvValue(entry.permissionMode || "")}`,
2790
+ `TELEGRAM_BOT_${upper}_AI_REASONING_EFFORT=${formatProviderEnvValue(entry.reasoningEffort || "")}`,
2791
+ "",
2792
+ );
2793
+ });
2794
+ }
2795
+ const knownKeys = new Set([
2796
+ "TELEGRAM_API_BASE_URL",
2797
+ "TELEGRAM_AUTO_CLEAR_WEBHOOK",
2798
+ "TELEGRAM_ALLOWED_UPDATES",
2799
+ "TELEGRAM_DEFAULT_BOT_KEY",
2800
+ "TELEGRAM_BOT_TOKEN",
2801
+ ]);
2802
+ entries.forEach((entry) => {
2803
+ const upper = String(entry.key || "").trim().toUpperCase();
2804
+ knownKeys.add(`TELEGRAM_BOT_${upper}_SERVER_BOT_ID`);
2805
+ knownKeys.add(`TELEGRAM_BOT_${upper}_USERNAME`);
2806
+ knownKeys.add(`TELEGRAM_BOT_${upper}_TOKEN`);
2807
+ knownKeys.add(`TELEGRAM_BOT_${upper}_ROLE_PROFILE`);
2808
+ knownKeys.add(`TELEGRAM_BOT_${upper}_AI_CLIENT`);
2809
+ knownKeys.add(`TELEGRAM_BOT_${upper}_AI_MODEL`);
2810
+ knownKeys.add(`TELEGRAM_BOT_${upper}_AI_PERMISSION_MODE`);
2811
+ knownKeys.add(`TELEGRAM_BOT_${upper}_AI_REASONING_EFFORT`);
2812
+ });
2813
+ const extras = Object.keys(parsed)
2814
+ .filter((key) => !knownKeys.has(key) && !isTelegramEntryEnvKey(key))
2815
+ .sort((left, right) => left.localeCompare(right));
2816
+ if (extras.length) {
2817
+ lines.push("# Additional preserved keys");
2818
+ extras.forEach((key) => {
2819
+ lines.push(`${key}=${formatProviderEnvValue(parsed[key])}`);
2820
+ });
2821
+ lines.push("");
2822
+ }
2823
+ return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
2824
+ }
2825
+
2826
+ function normalizeExistingProviderEnvFile(provider, filePath) {
2827
+ if (normalizeBotProvider(provider) !== "telegram") return false;
2828
+ const raw = fs.readFileSync(filePath, "utf8");
2829
+ const rendered = renderNormalizedTelegramEnv(parseSimpleEnvText(raw));
2830
+ if (raw.replace(/\r\n/g, "\n") === rendered.replace(/\r\n/g, "\n")) {
2831
+ return false;
2832
+ }
2833
+ fs.writeFileSync(filePath, rendered, {
2834
+ encoding: "utf8",
2835
+ mode: 0o600,
2836
+ });
2837
+ return true;
2838
+ }
2839
+
2708
2840
  function resolveTelegramEnvConfig(parsedEnv, filePath, config, selectors = {}) {
2709
2841
  const parsed = safeObject(parsedEnv);
2710
2842
  const legacyToken = String(parsed.TELEGRAM_BOT_TOKEN || "").trim();
@@ -5114,6 +5246,20 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5114
5246
  && telegramEnvV2ByUsername.allowedUpdates.includes("channel_post"),
5115
5247
  `bot=${String(telegramEnvV2ByUsername.botKey || "(none)")} updates=${String((telegramEnvV2ByUsername.allowedUpdates || []).join(","))}`,
5116
5248
  );
5249
+ const telegramEnvNormalizedText = renderNormalizedTelegramEnv(parseSimpleEnvText(`
5250
+ TELEGRAM_DEFAULT_BOT_KEY=ryoai_bot
5251
+ TELEGRAM_BOT_RYOAI_BOT_SERVER_BOT_ID=bot-ryoai
5252
+ TELEGRAM_BOT_RYOAI_BOT_TOKEN=ryoai-token
5253
+ `));
5254
+ push(
5255
+ "telegram_env_existing_file_auto_normalizes_named_keys",
5256
+ telegramEnvNormalizedText.includes("TELEGRAM_DEFAULT_BOT_KEY=ryoai")
5257
+ && telegramEnvNormalizedText.includes("TELEGRAM_BOT_RYOAI_SERVER_BOT_ID=bot-ryoai")
5258
+ && telegramEnvNormalizedText.includes("TELEGRAM_BOT_RYOAI_TOKEN=ryoai-token")
5259
+ && telegramEnvNormalizedText.includes("# Telegram bot entry: ryoai_bot")
5260
+ && !telegramEnvNormalizedText.includes("TELEGRAM_BOT_RYOAI_BOT_TOKEN"),
5261
+ telegramEnvNormalizedText,
5262
+ );
5117
5263
  const telegramSupport = getProviderSupport("telegram");
5118
5264
  push(
5119
5265
  "provider_support_telegram_full_runner",
@@ -441,6 +441,15 @@ function telegramEntryEnvKeys(botKey) {
441
441
  };
442
442
  }
443
443
 
444
+ function telegramEntryCommentName(entry) {
445
+ const current = safeObject(entry);
446
+ const username = String(current.username || "").trim();
447
+ if (username) return username;
448
+ const key = String(current.key || "").trim();
449
+ if (!key) return "telegram_bot";
450
+ return key.endsWith("_bot") ? key : `${key}_bot`;
451
+ }
452
+
444
453
  function persistTelegramUsername(entry) {
445
454
  const current = safeObject(entry);
446
455
  if (current.__preferServerIdentity) return "";
@@ -491,6 +500,11 @@ function telegramKnownKeys(parsedEnv, deps) {
491
500
  return keys;
492
501
  }
493
502
 
503
+ function isTelegramEntryEnvKey(rawKey) {
504
+ return /^TELEGRAM_BOT_([A-Z0-9_]+)_(TOKEN|USERNAME|SERVER_BOT_ID|ROLE_PROFILE|AI_CLIENT|AI_MODEL|AI_PERMISSION_MODE|AI_REASONING_EFFORT)$/i
505
+ .test(String(rawKey || "").trim());
506
+ }
507
+
494
508
  function renderTelegramEnv(parsedEnv, deps) {
495
509
  const parsed = safeObject(parsedEnv);
496
510
  const collectEntries = requireDependency(deps, "collectTelegramEnvBotEntries");
@@ -535,7 +549,7 @@ function renderTelegramEnv(parsedEnv, deps) {
535
549
  entries.forEach((entry) => {
536
550
  const keys = telegramEntryEnvKeys(entry.key);
537
551
  const entryLines = [
538
- `# Telegram bot entry: ${entry.key}`,
552
+ `# Telegram bot entry: ${telegramEntryCommentName(entry)}`,
539
553
  `${keys.serverBotID}=${formatEnvValue(entry.serverBotID || "")}`,
540
554
  ];
541
555
  if (String(entry.username || "").trim()) {
@@ -555,7 +569,7 @@ function renderTelegramEnv(parsedEnv, deps) {
555
569
  }
556
570
  const knownKeys = telegramKnownKeys(parsed, deps);
557
571
  const extras = Object.keys(parsed)
558
- .filter((key) => !knownKeys.has(key))
572
+ .filter((key) => !knownKeys.has(key) && !isTelegramEntryEnvKey(key))
559
573
  .sort((left, right) => left.localeCompare(right));
560
574
  if (extras.length) {
561
575
  lines.push("# Additional preserved keys");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.92",
3
+ "version": "0.2.93",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [