metheus-governance-mcp-cli 0.2.97 → 0.2.99

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
@@ -23,7 +23,6 @@ npm install -g metheus-governance-mcp-cli@latest
23
23
 
24
24
  Install creates local provider settings templates here:
25
25
 
26
- - `~/.metheus/telegram.env`
27
26
  - `~/.metheus/telegram-bots/`
28
27
  - `~/.metheus/slack.env`
29
28
  - `~/.metheus/kakaotalk.env`
@@ -32,8 +31,9 @@ Install creates local provider settings templates here:
32
31
  These files are for local provider bot secrets, local transport options, and optional per-bot local AI binding.
33
32
 
34
33
  - Store locally:
35
- - `TELEGRAM_BOT_TOKEN` as a legacy Telegram fallback in `telegram.env`
36
- - one Telegram bot file per entry in `~/.metheus/telegram-bots/<bot-key>.env`
34
+ - Telegram-wide settings in `~/.metheus/telegram-bots/_global.env`
35
+ - `TELEGRAM_BOT_TOKEN` as a legacy Telegram fallback in `_global.env`
36
+ - one Telegram bot file per entry in `~/.metheus/telegram-bots/<server-bot-name>.env`
37
37
  - `SLACK_BOT_TOKEN`
38
38
  - `KAKAOTALK_BOT_TOKEN`
39
39
  - Server-side Metheus stores project chat destination metadata separately:
@@ -41,23 +41,23 @@ These files are for local provider bot secrets, local transport options, and opt
41
41
  - `chat_id` / channel / room identifier
42
42
  - label / active state
43
43
  - Do not put project chat destination identifiers in local env files.
44
- - Telegram-wide settings stay in `telegram.env`; Telegram per-bot secrets and AI fields live in `telegram-bots/*.env`.
44
+ - Telegram-wide settings now live in `telegram-bots/_global.env`; Telegram per-bot secrets and AI fields live in `telegram-bots/*.env`.
45
45
 
46
46
  Example templates:
47
47
 
48
48
  ```env
49
- # ~/.metheus/telegram.env
49
+ # ~/.metheus/telegram-bots/_global.env
50
50
  TELEGRAM_API_BASE_URL=
51
51
  TELEGRAM_AUTO_CLEAR_WEBHOOK=true
52
52
  TELEGRAM_ALLOWED_UPDATES=message,edited_message
53
- TELEGRAM_DEFAULT_BOT_KEY=ryoai
53
+ TELEGRAM_DEFAULT_BOT_KEY=ryoai_bot
54
54
 
55
55
  # Legacy fallback
56
56
  TELEGRAM_BOT_TOKEN=
57
57
  ```
58
58
 
59
59
  ```env
60
- # ~/.metheus/telegram-bots/ryoai.env
60
+ # ~/.metheus/telegram-bots/ryoai_bot.env
61
61
  TELEGRAM_BOT_NAME=ryoai_bot
62
62
  TELEGRAM_BOT_SERVER_BOT_ID=
63
63
  TELEGRAM_BOT_TOKEN=
@@ -164,7 +164,6 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
164
164
 
165
165
  `setup` also ensures local provider templates exist:
166
166
 
167
- - `~/.metheus/telegram.env`
168
167
  - `~/.metheus/telegram-bots/`
169
168
  - `~/.metheus/slack.env`
170
169
  - `~/.metheus/kakaotalk.env`
@@ -172,8 +171,8 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
172
171
 
173
172
  When Telegram local config already exists, bootstrap/setup keeps your secrets but auto-normalizes the layout to the latest split structure:
174
173
 
175
- - Telegram-wide settings stay in `~/.metheus/telegram.env`
176
- - per-bot secrets move to `~/.metheus/telegram-bots/<bot-key>.env`
174
+ - Telegram-wide settings stay in `~/.metheus/telegram-bots/_global.env`
175
+ - per-bot secrets move to `~/.metheus/telegram-bots/<server-bot-name>.env`
177
176
  - stale inline keys such as `TELEGRAM_BOT_<NAME>_BOT_*` are rewritten into generic per-bot keys
178
177
 
179
178
  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.
@@ -183,7 +182,7 @@ Fill provider bot secrets and provider-local transport options locally. Project
183
182
  - which provider/role bot profile to use
184
183
  - which `project_id -> workspace_dir` mapping to apply locally
185
184
  - which role profile maps to which local CLI/model/permission/reasoning policy
186
- - which server bot maps to which local LLM execution profile via `telegram.env` or fallback `bot_bindings`
185
+ - which server bot maps to which local LLM execution profile via `telegram-bots/_global.env`, `telegram-bots/*.env`, or fallback `bot_bindings`
187
186
 
188
187
  Built-in helper command for legacy fallback/testing:
189
188
 
@@ -224,15 +223,15 @@ Direct commands:
224
223
 
225
224
  ```bash
226
225
  metheus-governance-mcp-cli bot list
227
- metheus-governance-mcp-cli bot show --provider telegram --bot-key ryoai
226
+ metheus-governance-mcp-cli bot show --provider telegram --bot-key ryoai_bot
228
227
  metheus-governance-mcp-cli bot add --provider telegram
229
228
  metheus-governance-mcp-cli bot edit
230
229
  metheus-governance-mcp-cli bot edit --provider telegram
231
230
  metheus-governance-mcp-cli bot remove --provider telegram
232
- metheus-governance-mcp-cli bot set-default --provider telegram --bot-key ryoai
233
- metheus-governance-mcp-cli bot migrate --provider telegram --bot-key ryoai
231
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-key ryoai_bot
232
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-key ryoai_bot
234
233
  metheus-governance-mcp-cli bot global --provider telegram
235
- metheus-governance-mcp-cli bot verify --provider telegram --bot-key ryoai
234
+ metheus-governance-mcp-cli bot verify --provider telegram --bot-key ryoai_bot
236
235
  ```
237
236
 
238
237
  Behavior:
@@ -259,7 +258,7 @@ Behavior:
259
258
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
260
259
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
261
260
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
262
- - Telegram stores one bot file per entry under `~/.metheus/telegram-bots/<bot-key>.env` with generic fields:
261
+ - Telegram stores one bot file per entry under `~/.metheus/telegram-bots/<server-bot-name>.env` with generic fields:
263
262
  - `TELEGRAM_BOT_NAME`
264
263
  - `TELEGRAM_BOT_SERVER_BOT_ID`
265
264
  - `TELEGRAM_BOT_TOKEN`
@@ -281,11 +280,11 @@ Non-interactive examples:
281
280
  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
282
281
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --default true
283
282
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --ai-client gpt --ai-model "gpt-5.4" --ai-permission-mode read_only --ai-reasoning-effort low
284
- metheus-governance-mcp-cli bot edit --provider telegram --bot-key ryoai --non-interactive true --ai-client claude --ai-model "Sonnet 4.6r" --ai-permission-mode danger_full_access --ai-reasoning-effort high
285
- metheus-governance-mcp-cli bot set-default --provider telegram --bot-key ryoai --non-interactive true
286
- metheus-governance-mcp-cli bot migrate --provider telegram --bot-key ryoai --server-bot-id <server_bot_uuid> --bot-name <telegram_username> --role-profile monitor --ai-client gpt --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
287
- metheus-governance-mcp-cli bot remove --provider telegram --bot-key ryoai --non-interactive true
288
- metheus-governance-mcp-cli bot verify --provider telegram --bot-key ryoai --json true
283
+ metheus-governance-mcp-cli bot edit --provider telegram --bot-key ryoai_bot --non-interactive true --ai-client claude --ai-model "Sonnet 4.6r" --ai-permission-mode danger_full_access --ai-reasoning-effort high
284
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-key ryoai_bot --non-interactive true
285
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-key ryoai_bot --server-bot-id <server_bot_uuid> --bot-name <telegram_username> --role-profile monitor --ai-client gpt --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
286
+ metheus-governance-mcp-cli bot remove --provider telegram --bot-key ryoai_bot --non-interactive true
287
+ metheus-governance-mcp-cli bot verify --provider telegram --bot-key ryoai_bot --json true
289
288
  ```
290
289
 
291
290
  For direct Telegram adds, the CLI can derive the local entry key from the matched server bot name. You no longer need `--bot-key` or `--username` in the normal server-bound path. Treat `--bot-key` as an advanced override only when you intentionally want a different local suffix.
@@ -398,7 +397,7 @@ Recommended production path:
398
397
  - keep `project_mappings.<project_id>.workspace_dir` aligned to that teammate's actual local project folder
399
398
  - let `ctxpack pull` or project connection refresh the mapping automatically
400
399
  - keep per-role execution policy under `role_profiles`
401
- - keep per-bot LLM binding primarily in `~/.metheus/telegram.env`
400
+ - keep Telegram-wide binding defaults in `~/.metheus/telegram-bots/_global.env`
402
401
  - use `bot_bindings` in `bot-runner.json` only as local fallback/override
403
402
  - runner resolution order is: explicit `route.role_profile` -> provider env bot binding -> `bot_bindings` -> server bot role -> `route.role`
404
403
 
@@ -471,7 +470,7 @@ Notes:
471
470
  - `local-bot-bridge` reads stdin JSON from the runner and can call Codex/Claude/Gemini for you
472
471
  - `route.command` fallback is disabled by default; enable it only temporarily with `METHEUS_ALLOW_LEGACY_RUNNER_COMMAND=1`
473
472
  - today this automation path is implemented for Telegram end-to-end
474
- - prefer `TELEGRAM_API_BASE_URL=` inside `~/.metheus/telegram.env` for per-bot local Telegram API overrides; `METHEUS_TELEGRAM_API_BASE_URL` remains a process-level fallback mainly for mock/regression testing
473
+ - prefer `TELEGRAM_API_BASE_URL=` inside `~/.metheus/telegram-bots/_global.env` for local Telegram API overrides; `METHEUS_TELEGRAM_API_BASE_URL` remains a process-level fallback mainly for mock/regression testing
475
474
  - Slack can use direct local send, but automatic inbound runner flow is not completed yet
476
475
  - KakaoTalk config can be stored now, but direct send/runner flow is not implemented yet
477
476
  - `doctor` now reports provider support for both enabled runner routes and active project chat destinations
package/cli.mjs CHANGED
@@ -154,9 +154,11 @@ const AUTH_STORE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-auth.json
154
154
  const BOT_RUNNER_CONFIG_RELATIVE_PATH = path.join(".metheus", "bot-runner.json");
155
155
  const BOT_RUNNER_STATE_RELATIVE_PATH = path.join(".metheus", "bot-runner-state.json");
156
156
  const BOT_RUNNER_CONFIG_VERSION = 2;
157
+ const TELEGRAM_LEGACY_ENV_RELATIVE_PATH = path.join(".metheus", "telegram.env");
158
+ const TELEGRAM_GLOBAL_ENV_RELATIVE_PATH = path.join(".metheus", "telegram-bots", "_global.env");
157
159
  const PROVIDER_ENV_CONFIG = {
158
160
  telegram: {
159
- relativePath: path.join(".metheus", "telegram.env"),
161
+ relativePath: TELEGRAM_GLOBAL_ENV_RELATIVE_PATH,
160
162
  tokenKey: "TELEGRAM_BOT_TOKEN",
161
163
  label: "Telegram",
162
164
  verifyURL: "https://api.telegram.org",
@@ -736,6 +738,10 @@ function providerEnvFilePath(provider) {
736
738
  return resolveHomeFilePath(providerEnvConfig(provider).relativePath);
737
739
  }
738
740
 
741
+ function telegramLegacyEnvFilePath() {
742
+ return resolveHomeFilePath(TELEGRAM_LEGACY_ENV_RELATIVE_PATH);
743
+ }
744
+
739
745
  function telegramBotEntriesDirPath() {
740
746
  return resolveHomeFilePath(".metheus/telegram-bots");
741
747
  }
@@ -751,7 +757,8 @@ function providerEnvTemplate(provider) {
751
757
  return [
752
758
  "# Metheus local Telegram bot settings",
753
759
  "# Keep this file on your machine only. Do not commit it.",
754
- "# Store only Telegram-wide settings here.",
760
+ "# Store Telegram-wide settings here.",
761
+ "# This global file now lives in ~/.metheus/telegram-bots/_global.env.",
755
762
  "# Per-bot secrets and AI settings live in ~/.metheus/telegram-bots/<bot-key>.env.",
756
763
  "# Project chat destinations must be managed on the Metheus server.",
757
764
  "",
@@ -1037,6 +1044,23 @@ function isCompatibleLegacyLocalBotBridgeCommand(command, roleProfile) {
1037
1044
  }
1038
1045
 
1039
1046
  function ensureProviderEnvTemplate(provider) {
1047
+ if (normalizeBotProvider(provider) === "telegram") {
1048
+ const filePath = providerEnvFilePath(provider);
1049
+ const legacyFilePath = telegramLegacyEnvFilePath();
1050
+ const existedBefore = fs.existsSync(filePath) || fs.existsSync(legacyFilePath);
1051
+ try {
1052
+ const state = readTelegramEnvState();
1053
+ writeTelegramEnvState(state.parsed);
1054
+ return {
1055
+ filePath,
1056
+ created: !existedBefore,
1057
+ existed: existedBefore,
1058
+ normalized: true,
1059
+ };
1060
+ } catch (err) {
1061
+ return { filePath, created: false, existed: false, error: String(err?.message || err) };
1062
+ }
1063
+ }
1040
1064
  const filePath = providerEnvFilePath(provider);
1041
1065
  try {
1042
1066
  if (fs.existsSync(filePath)) {
@@ -2642,7 +2666,6 @@ function normalizeTelegramBotEnvKey(rawValue, fallback = "") {
2642
2666
  .trim()
2643
2667
  .toLowerCase()
2644
2668
  .replace(/[^a-z0-9]+/g, "_")
2645
- .replace(/_bot$/g, "")
2646
2669
  .replace(/^_+|_+$/g, "");
2647
2670
  return normalized || fallback;
2648
2671
  }
@@ -2703,24 +2726,30 @@ function collectTelegramEnvBotEntries(parsedEnv) {
2703
2726
  }
2704
2727
  entries.set(botKey, entry);
2705
2728
  }
2706
- return Array.from(entries.values());
2729
+ return Array.from(entries.values()).map((entryRaw) => {
2730
+ const entry = { ...safeObject(entryRaw) };
2731
+ const preferredKey = normalizeTelegramBotEnvKey(entry.username || "", "");
2732
+ if (preferredKey) {
2733
+ entry.key = preferredKey;
2734
+ }
2735
+ return entry;
2736
+ });
2707
2737
  }
2708
2738
 
2709
2739
  function telegramBotEntryDisplayNameForState(entry) {
2710
2740
  const current = safeObject(entry);
2711
2741
  const username = normalizeTelegramBotUsername(current.username || "");
2712
2742
  if (username) return username;
2713
- const key = normalizeTelegramBotEnvKey(current.key || "", "telegram_bot");
2714
- if (!key) return "telegram_bot";
2715
- return key.endsWith("_bot") ? key : `${key}_bot`;
2743
+ return normalizeTelegramBotEnvKey(current.key || "", "telegram_bot");
2716
2744
  }
2717
2745
 
2718
2746
  function parseTelegramBotEntryFile(filePath) {
2719
2747
  const raw = fs.readFileSync(filePath, "utf8");
2720
2748
  const parsed = parseSimpleEnvText(raw);
2721
2749
  const keyFromFile = normalizeTelegramBotEnvKey(path.basename(filePath, path.extname(filePath)), "telegram_bot");
2750
+ const keyFromName = normalizeTelegramBotEnvKey(parsed.TELEGRAM_BOT_NAME || parsed.TELEGRAM_BOT_USERNAME || "", "");
2722
2751
  return {
2723
- key: keyFromFile,
2752
+ key: keyFromName || keyFromFile,
2724
2753
  username: normalizeTelegramBotUsername(parsed.TELEGRAM_BOT_NAME || parsed.TELEGRAM_BOT_USERNAME || ""),
2725
2754
  serverBotID: String(parsed.TELEGRAM_BOT_SERVER_BOT_ID || "").trim(),
2726
2755
  token: String(parsed.TELEGRAM_BOT_TOKEN || "").trim(),
@@ -2733,11 +2762,33 @@ function parseTelegramBotEntryFile(filePath) {
2733
2762
  };
2734
2763
  }
2735
2764
 
2765
+ function remapTelegramDefaultBotKey(defaultKeyRaw, entries) {
2766
+ const defaultKey = normalizeTelegramBotEnvKey(defaultKeyRaw || "", "");
2767
+ if (!defaultKey) return "";
2768
+ const rows = ensureArray(entries).map((entry) => safeObject(entry));
2769
+ if (rows.some((entry) => String(entry.key || "").trim() === defaultKey)) {
2770
+ return defaultKey;
2771
+ }
2772
+ const directBotMatch = rows.find((entry) => String(entry.key || "").trim() === `${defaultKey}_bot`);
2773
+ if (directBotMatch) {
2774
+ return String(directBotMatch.key || "").trim();
2775
+ }
2776
+ const compactBotMatch = rows.find((entry) => String(entry.key || "").trim() === `${defaultKey}bot`);
2777
+ if (compactBotMatch) {
2778
+ return String(compactBotMatch.key || "").trim();
2779
+ }
2780
+ const byUsername = rows.find((entry) => normalizeTelegramBotUsername(entry.username || "") === defaultKey);
2781
+ if (byUsername) {
2782
+ return String(byUsername.key || "").trim();
2783
+ }
2784
+ return defaultKey;
2785
+ }
2786
+
2736
2787
  function loadTelegramBotEntriesFromFiles() {
2737
2788
  const dirPath = telegramBotEntriesDirPath();
2738
2789
  if (!fs.existsSync(dirPath)) return [];
2739
2790
  return fs.readdirSync(dirPath, { withFileTypes: true })
2740
- .filter((item) => item.isFile() && /\.env$/i.test(item.name))
2791
+ .filter((item) => item.isFile() && /\.env$/i.test(item.name) && item.name.toLowerCase() !== "_global.env")
2741
2792
  .map((item) => path.join(dirPath, item.name))
2742
2793
  .sort((left, right) => left.localeCompare(right))
2743
2794
  .map((filePath) => {
@@ -2775,14 +2826,20 @@ function buildMergedTelegramEnvParsed(globalParsed, entries) {
2775
2826
 
2776
2827
  function readTelegramEnvState() {
2777
2828
  const filePath = providerEnvFilePath("telegram");
2829
+ const legacyFilePath = telegramLegacyEnvFilePath();
2830
+ let sourceFilePath = filePath;
2778
2831
  try {
2779
- if (!fs.existsSync(filePath)) {
2832
+ if (!fs.existsSync(filePath) && fs.existsSync(legacyFilePath)) {
2833
+ sourceFilePath = legacyFilePath;
2834
+ } else if (!fs.existsSync(filePath)) {
2780
2835
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
2781
2836
  fs.writeFileSync(filePath, providerEnvTemplate("telegram"), "utf8");
2782
2837
  }
2783
2838
  } catch (err) {
2784
2839
  return {
2785
2840
  filePath,
2841
+ legacyFilePath,
2842
+ sourceFilePath,
2786
2843
  entriesDirPath: telegramBotEntriesDirPath(),
2787
2844
  parsed: {},
2788
2845
  globalParsed: {},
@@ -2790,7 +2847,7 @@ function readTelegramEnvState() {
2790
2847
  error: String(err?.message || err),
2791
2848
  };
2792
2849
  }
2793
- const raw = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
2850
+ const raw = fs.existsSync(sourceFilePath) ? fs.readFileSync(sourceFilePath, "utf8") : "";
2794
2851
  const globalParsedRaw = parseSimpleEnvText(raw);
2795
2852
  const inlineEntries = collectTelegramEnvBotEntries(globalParsedRaw);
2796
2853
  const fileEntries = loadTelegramBotEntriesFromFiles();
@@ -2804,8 +2861,11 @@ function readTelegramEnvState() {
2804
2861
  }
2805
2862
  });
2806
2863
  const entries = Array.from(mergedEntries.values());
2864
+ globalParsed.TELEGRAM_DEFAULT_BOT_KEY = remapTelegramDefaultBotKey(globalParsed.TELEGRAM_DEFAULT_BOT_KEY || "", entries);
2807
2865
  return {
2808
2866
  filePath,
2867
+ legacyFilePath,
2868
+ sourceFilePath,
2809
2869
  entriesDirPath: telegramBotEntriesDirPath(),
2810
2870
  parsed: buildMergedTelegramEnvParsed(globalParsed, entries),
2811
2871
  globalParsed,
@@ -2822,18 +2882,20 @@ function telegramEntryCommentNameForEnv(entry) {
2822
2882
  const current = safeObject(entry);
2823
2883
  const username = normalizeTelegramBotUsername(current.username || "");
2824
2884
  if (username) return username;
2825
- const key = normalizeTelegramBotEnvKey(current.key || "", "telegram_bot");
2826
- if (!key) return "telegram_bot";
2827
- return key.endsWith("_bot") ? key : `${key}_bot`;
2885
+ return normalizeTelegramBotEnvKey(current.key || "", "telegram_bot");
2828
2886
  }
2829
2887
 
2830
2888
  function renderNormalizedTelegramEnv(parsedEnv) {
2831
2889
  const parsed = safeObject(parsedEnv);
2832
- const defaultBotKey = normalizeTelegramBotEnvKey(parsed.TELEGRAM_DEFAULT_BOT_KEY || "", "");
2890
+ const defaultBotKey = remapTelegramDefaultBotKey(
2891
+ parsed.TELEGRAM_DEFAULT_BOT_KEY || "",
2892
+ collectTelegramEnvBotEntries(parsed),
2893
+ );
2833
2894
  const lines = [
2834
2895
  "# Metheus local Telegram bot settings",
2835
2896
  "# Keep this file on your machine only. Do not commit it.",
2836
- "# Store only Telegram-wide settings here.",
2897
+ "# Store Telegram-wide settings here.",
2898
+ "# This global file lives in ~/.metheus/telegram-bots/_global.env.",
2837
2899
  "# Per-bot secrets and AI settings live in ~/.metheus/telegram-bots/<bot-key>.env.",
2838
2900
  "",
2839
2901
  `TELEGRAM_API_BASE_URL=${formatProviderEnvValue(parsed.TELEGRAM_API_BASE_URL || "")}`,
@@ -2906,6 +2968,7 @@ function writeTelegramEnvState(parsedEnv) {
2906
2968
  }
2907
2969
  });
2908
2970
  const globalFilePath = providerEnvFilePath("telegram");
2971
+ const legacyFilePath = telegramLegacyEnvFilePath();
2909
2972
  fs.mkdirSync(path.dirname(globalFilePath), { recursive: true });
2910
2973
  fs.writeFileSync(globalFilePath, renderNormalizedTelegramEnv(globalParsed), {
2911
2974
  encoding: "utf8",
@@ -2923,13 +2986,16 @@ function writeTelegramEnvState(parsedEnv) {
2923
2986
  });
2924
2987
  });
2925
2988
  fs.readdirSync(entriesDirPath, { withFileTypes: true })
2926
- .filter((item) => item.isFile() && /\.env$/i.test(item.name))
2989
+ .filter((item) => item.isFile() && /\.env$/i.test(item.name) && item.name.toLowerCase() !== "_global.env")
2927
2990
  .forEach((item) => {
2928
2991
  const filePath = path.resolve(path.join(entriesDirPath, item.name));
2929
2992
  if (!activeFiles.has(filePath)) {
2930
2993
  fs.rmSync(filePath, { force: true });
2931
2994
  }
2932
2995
  });
2996
+ if (legacyFilePath !== globalFilePath && fs.existsSync(legacyFilePath)) {
2997
+ fs.rmSync(legacyFilePath, { force: true });
2998
+ }
2933
2999
  return globalFilePath;
2934
3000
  }
2935
3001
 
@@ -2964,7 +3030,7 @@ function resolveTelegramEnvConfig(parsedEnv, filePath, config, selectors = {}) {
2964
3030
  selectors.route?.telegramBotKey,
2965
3031
  selectors.route?.telegram_bot_key,
2966
3032
  ]), "");
2967
- const defaultBotKey = normalizeTelegramBotEnvKey(parsed.TELEGRAM_DEFAULT_BOT_KEY || "", "");
3033
+ const defaultBotKey = remapTelegramDefaultBotKey(parsed.TELEGRAM_DEFAULT_BOT_KEY || "", entries);
2968
3034
  let selected = null;
2969
3035
  if (desiredBotID) {
2970
3036
  selected = entries.find((entry) => entry.serverBotID === desiredBotID) || null;
@@ -5347,7 +5413,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5347
5413
  "telegram_env_v2_default_bot_key_selects_named_token",
5348
5414
  telegramEnvV2Default.ok
5349
5415
  && telegramEnvV2Default.token === "review-token"
5350
- && telegramEnvV2Default.botKey === "review"
5416
+ && telegramEnvV2Default.botKey === "reviewbot"
5351
5417
  && telegramEnvV2Default.autoClearWebhook === false,
5352
5418
  `bot=${String(telegramEnvV2Default.botKey || "(none)")} token=${String(telegramEnvV2Default.token || "(missing)")}`,
5353
5419
  );
@@ -5361,7 +5427,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5361
5427
  "telegram_env_v2_bot_name_override_selects_matching_token",
5362
5428
  telegramEnvV2ByUsername.ok
5363
5429
  && telegramEnvV2ByUsername.token === "ryoai-token"
5364
- && telegramEnvV2ByUsername.botKey === "ryoai"
5430
+ && telegramEnvV2ByUsername.botKey === "ryoai_bot"
5365
5431
  && Array.isArray(telegramEnvV2ByUsername.allowedUpdates)
5366
5432
  && telegramEnvV2ByUsername.allowedUpdates.includes("channel_post"),
5367
5433
  `bot=${String(telegramEnvV2ByUsername.botKey || "(none)")} updates=${String((telegramEnvV2ByUsername.allowedUpdates || []).join(","))}`,
@@ -5374,7 +5440,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5374
5440
  try {
5375
5441
  process.env.USERPROFILE = telegramEnvNormalizeTempHome;
5376
5442
  process.env.HOME = telegramEnvNormalizeTempHome;
5377
- const normalizeTestEnvPath = providerEnvFilePath("telegram");
5443
+ const normalizeTestEnvPath = telegramLegacyEnvFilePath();
5378
5444
  fs.mkdirSync(path.dirname(normalizeTestEnvPath), { recursive: true });
5379
5445
  fs.writeFileSync(
5380
5446
  normalizeTestEnvPath,
@@ -5386,9 +5452,10 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5386
5452
  ].join("\n"),
5387
5453
  "utf8",
5388
5454
  );
5389
- ensureProviderEnvTemplate("telegram");
5390
- normalizedGlobalText = fs.readFileSync(normalizeTestEnvPath, "utf8");
5391
- normalizedBotText = fs.readFileSync(telegramBotEntryFilePath("ryoai"), "utf8");
5455
+ const normalizedState = readTelegramEnvState();
5456
+ writeTelegramEnvState(normalizedState.parsed);
5457
+ normalizedGlobalText = fs.readFileSync(providerEnvFilePath("telegram"), "utf8");
5458
+ normalizedBotText = fs.readFileSync(telegramBotEntryFilePath("ryoai_bot"), "utf8");
5392
5459
  } finally {
5393
5460
  if (previousUserProfile === undefined) delete process.env.USERPROFILE;
5394
5461
  else process.env.USERPROFILE = previousUserProfile;
@@ -5397,7 +5464,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5397
5464
  }
5398
5465
  push(
5399
5466
  "telegram_env_existing_file_auto_normalizes_named_keys",
5400
- normalizedGlobalText.includes("TELEGRAM_DEFAULT_BOT_KEY=ryoai")
5467
+ normalizedGlobalText.includes("TELEGRAM_DEFAULT_BOT_KEY=ryoai_bot")
5401
5468
  && !normalizedGlobalText.includes("TELEGRAM_BOT_RYOAI_BOT_TOKEN")
5402
5469
  && !normalizedGlobalText.includes("TELEGRAM_BOT_RYOAI_TOKEN")
5403
5470
  && normalizedBotText.includes("TELEGRAM_BOT_NAME=ryoai_bot")
@@ -288,8 +288,8 @@ export async function runSelftestBotCommands(push, deps) {
288
288
  const env = buildSpawnEnv(tempHome);
289
289
  const baseURL = `http://127.0.0.1:${mock.port}`;
290
290
  const telegramApiBaseURL = `${baseURL}/telegram`;
291
- const telegramEnvPath = path.join(tempHome, ".metheus", "telegram.env");
292
291
  const telegramBotEntriesDir = path.join(tempHome, ".metheus", "telegram-bots");
292
+ const telegramEnvPath = path.join(telegramBotEntriesDir, "_global.env");
293
293
  const readTelegramGlobals = () => parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
294
294
  const readTelegramBotEntry = (botKey) => {
295
295
  const filePath = path.join(telegramBotEntriesDir, `${String(botKey || "").trim()}.env`);
@@ -398,7 +398,7 @@ export async function runSelftestBotCommands(push, deps) {
398
398
  ]),
399
399
  },
400
400
  });
401
- const groupedState = readTelegramBotEntry("ryoai");
401
+ const groupedState = readTelegramBotEntry("ryoai_bot");
402
402
  push(
403
403
  "bot_add_guided_autoresolves_server_bot_group_without_role_prompt",
404
404
  String(groupedState.TELEGRAM_BOT_NAME || "").toLowerCase() === "ryoai_bot"
@@ -420,7 +420,7 @@ export async function runSelftestBotCommands(push, deps) {
420
420
  ...env,
421
421
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
422
422
  "1", // provider: telegram
423
- "2", // bot entry: ryoai
423
+ "2", // bot entry: ryoai_bot
424
424
  "1", // keep token
425
425
  "2", // grouped role settings: edit one role
426
426
  "3", // select role to edit: worker
@@ -465,7 +465,7 @@ export async function runSelftestBotCommands(push, deps) {
465
465
  args: [
466
466
  "bot", "show",
467
467
  "--provider", "telegram",
468
- "--bot-key", "ryoai",
468
+ "--bot-key", "ryoai_bot",
469
469
  "--base-url", `http://127.0.0.1:${groupedMock.port}`,
470
470
  "--json", "true",
471
471
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.97",
3
+ "version": "0.2.99",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [