metheus-governance-mcp-cli 0.2.92 → 0.2.94

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
@@ -24,6 +24,7 @@ npm install -g metheus-governance-mcp-cli@latest
24
24
  Install creates local provider settings templates here:
25
25
 
26
26
  - `~/.metheus/telegram.env`
27
+ - `~/.metheus/telegram-bots/`
27
28
  - `~/.metheus/slack.env`
28
29
  - `~/.metheus/kakaotalk.env`
29
30
  - `~/.metheus/bot-runner.json`
@@ -31,7 +32,8 @@ Install creates local provider settings templates here:
31
32
  These files are for local provider bot secrets, local transport options, and optional per-bot local AI binding.
32
33
 
33
34
  - Store locally:
34
- - `TELEGRAM_BOT_TOKEN` or named Telegram bot mappings such as `TELEGRAM_BOT_RYOAI_TOKEN`
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`
35
37
  - `SLACK_BOT_TOKEN`
36
38
  - `KAKAOTALK_BOT_TOKEN`
37
39
  - Server-side Metheus stores project chat destination metadata separately:
@@ -39,7 +41,7 @@ These files are for local provider bot secrets, local transport options, and opt
39
41
  - `chat_id` / channel / room identifier
40
42
  - label / active state
41
43
  - Do not put project chat destination identifiers in local env files.
42
- - Telegram env can also carry per-bot local AI binding fields.
44
+ - Telegram-wide settings stay in `telegram.env`; Telegram per-bot secrets and AI fields live in `telegram-bots/*.env`.
43
45
 
44
46
  Example templates:
45
47
 
@@ -52,18 +54,21 @@ TELEGRAM_DEFAULT_BOT_KEY=ryoai
52
54
 
53
55
  # Legacy fallback
54
56
  TELEGRAM_BOT_TOKEN=
57
+ ```
55
58
 
56
- # Preferred named bot mapping
57
- TELEGRAM_BOT_RYOAI_SERVER_BOT_ID=
58
- # Optional fallback only when server bot binding is unavailable
59
- # TELEGRAM_BOT_RYOAI_USERNAME=<bot_username>
60
- TELEGRAM_BOT_RYOAI_TOKEN=
61
- TELEGRAM_BOT_RYOAI_ROLE_PROFILE=monitor
62
- TELEGRAM_BOT_RYOAI_AI_CLIENT=gpt
63
- TELEGRAM_BOT_RYOAI_AI_MODEL=gpt-5.4
64
- TELEGRAM_BOT_RYOAI_AI_PERMISSION_MODE=read_only
65
- TELEGRAM_BOT_RYOAI_AI_REASONING_EFFORT=low
59
+ ```env
60
+ # ~/.metheus/telegram-bots/ryoai.env
61
+ TELEGRAM_BOT_NAME=ryoai_bot
62
+ TELEGRAM_BOT_SERVER_BOT_ID=
63
+ TELEGRAM_BOT_TOKEN=
64
+ TELEGRAM_BOT_ROLE_PROFILE=
65
+ TELEGRAM_BOT_AI_CLIENT=
66
+ TELEGRAM_BOT_AI_MODEL=
67
+ TELEGRAM_BOT_AI_PERMISSION_MODE=
68
+ TELEGRAM_BOT_AI_REASONING_EFFORT=
69
+ ```
66
70
 
71
+ ```env
67
72
  # ~/.metheus/slack.env
68
73
  SLACK_BOT_TOKEN=
69
74
 
@@ -160,10 +165,17 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
160
165
  `setup` also ensures local provider templates exist:
161
166
 
162
167
  - `~/.metheus/telegram.env`
168
+ - `~/.metheus/telegram-bots/`
163
169
  - `~/.metheus/slack.env`
164
170
  - `~/.metheus/kakaotalk.env`
165
171
  - `~/.metheus/bot-runner.json`
166
172
 
173
+ When Telegram local config already exists, bootstrap/setup keeps your secrets but auto-normalizes the layout to the latest split structure:
174
+
175
+ - Telegram-wide settings stay in `~/.metheus/telegram.env`
176
+ - per-bot secrets move to `~/.metheus/telegram-bots/<bot-key>.env`
177
+ - stale inline keys such as `TELEGRAM_BOT_<NAME>_BOT_*` are rewritten into generic per-bot keys
178
+
167
179
  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
180
 
169
181
  `~/.metheus/bot-runner.json` is the local automation profile for:
@@ -228,7 +240,7 @@ Behavior:
228
240
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
229
241
  - `bot add` without flags now uses the shortest practical guided flow: provider -> token -> verify -> optional save-anyway only when verify fails -> optional username fallback only when verify cannot discover it -> optional AI model selection when the resolved defaults leave it blank -> optional default bot.
230
242
  - In the normal Telegram path, `bot add` does not ask for a local bot key, a server bot UUID, or an approval / worker / review / monitor choice.
231
- - For Telegram, the local env key is auto-generated from the matched server bot name or verified username, so you do not have to invent a separate local nickname first.
243
+ - For Telegram, the local bot file stem is auto-generated from the matched server bot name or verified username, so you do not have to invent a separate local nickname first.
232
244
  - For Telegram, the CLI tries to match the verified bot identity against the server `me/bots` list first. If the server exposes one logical bot name with multiple roles such as `approval`, `worker`, `review`, and `monitor`, the CLI does not ask you to choose one UUID. It binds by server bot name and keeps the local role/AI fields empty so runtime can use the server bot role for each route.
233
245
  - When the Telegram username matches exactly one server bot role, the CLI still auto-fills the local `role_profile` and blank AI defaults from your local `bot-runner.json` `role_profiles` mapping.
234
246
  - `bot edit` without flags now uses the same sequential flow every time: provider -> bot entry -> username/token review -> grouped server-role review when needed -> AI field choices -> default choice -> save.
@@ -247,15 +259,15 @@ Behavior:
247
259
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
248
260
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
249
261
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
250
- - Telegram supports named local bot entries with:
251
- - `SERVER_BOT_ID`
252
- - `USERNAME` (optional fallback only when server binding is unavailable)
253
- - `TOKEN`
254
- - `ROLE_PROFILE`
255
- - `AI_CLIENT`
256
- - `AI_MODEL`
257
- - `AI_PERMISSION_MODE`
258
- - `AI_REASONING_EFFORT`
262
+ - Telegram stores one bot file per entry under `~/.metheus/telegram-bots/<bot-key>.env` with generic fields:
263
+ - `TELEGRAM_BOT_NAME`
264
+ - `TELEGRAM_BOT_SERVER_BOT_ID`
265
+ - `TELEGRAM_BOT_TOKEN`
266
+ - `TELEGRAM_BOT_ROLE_PROFILE`
267
+ - `TELEGRAM_BOT_AI_CLIENT`
268
+ - `TELEGRAM_BOT_AI_MODEL`
269
+ - `TELEGRAM_BOT_AI_PERMISSION_MODE`
270
+ - `TELEGRAM_BOT_AI_REASONING_EFFORT`
259
271
  - Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
260
272
  - `bot verify` checks the configured local token, cross-checks the server bot binding, prints the effective runtime role profile summary that the runner will use, and shows which local runner routes currently point at this bot entry.
261
273
  - `bot show` prints one local bot entry in detail, including grouped role summaries when one server bot name expands to multiple roles and any linked local runner routes.
package/cli.mjs CHANGED
@@ -736,46 +736,33 @@ function providerEnvFilePath(provider) {
736
736
  return resolveHomeFilePath(providerEnvConfig(provider).relativePath);
737
737
  }
738
738
 
739
+ function telegramBotEntriesDirPath() {
740
+ return resolveHomeFilePath(".metheus/telegram-bots");
741
+ }
742
+
743
+ function telegramBotEntryFilePath(botKey) {
744
+ const normalizedKey = normalizeTelegramBotEnvKey(botKey || "", "telegram_bot") || "telegram_bot";
745
+ return path.join(telegramBotEntriesDirPath(), `${normalizedKey}.env`);
746
+ }
747
+
739
748
  function providerEnvTemplate(provider) {
740
749
  const config = providerEnvConfig(provider);
741
750
  if (normalizeBotProvider(provider) === "telegram") {
742
751
  return [
743
752
  "# Metheus local Telegram bot settings",
744
753
  "# Keep this file on your machine only. Do not commit it.",
745
- "# Store Telegram bot secrets and local transport options only.",
754
+ "# Store only Telegram-wide settings here.",
755
+ "# Per-bot secrets and AI settings live in ~/.metheus/telegram-bots/<bot-key>.env.",
746
756
  "# Project chat destinations must be managed on the Metheus server.",
747
- "#",
748
- "# Legacy single-bot fallback:",
749
- "# TELEGRAM_BOT_TOKEN=<bot token>",
750
- "#",
751
- "# Preferred v2 multi-bot mapping:",
752
- "# TELEGRAM_DEFAULT_BOT_KEY=ryoai",
753
- "# TELEGRAM_BOT_RYOAI_SERVER_BOT_ID=<server bot uuid>",
754
- "# TELEGRAM_BOT_RYOAI_USERNAME=<optional fallback username when server binding is unavailable>",
755
- "# TELEGRAM_BOT_RYOAI_TOKEN=<bot token>",
756
- "# TELEGRAM_BOT_RYOAI_ROLE_PROFILE=monitor",
757
- "# TELEGRAM_BOT_RYOAI_AI_CLIENT=gpt",
758
- "# TELEGRAM_BOT_RYOAI_AI_MODEL=gpt-5.4",
759
- "# TELEGRAM_BOT_RYOAI_AI_PERMISSION_MODE=read_only",
760
- "# TELEGRAM_BOT_RYOAI_AI_REASONING_EFFORT=low",
761
757
  "",
762
758
  "TELEGRAM_API_BASE_URL=",
763
759
  "TELEGRAM_AUTO_CLEAR_WEBHOOK=true",
764
760
  "TELEGRAM_ALLOWED_UPDATES=message,edited_message",
765
- "TELEGRAM_DEFAULT_BOT_KEY=ryoai",
761
+ "TELEGRAM_DEFAULT_BOT_KEY=",
766
762
  "",
767
763
  "# Legacy fallback (still supported)",
768
764
  "TELEGRAM_BOT_TOKEN=",
769
765
  "",
770
- "# Preferred named bot entry",
771
- "TELEGRAM_BOT_RYOAI_SERVER_BOT_ID=",
772
- "TELEGRAM_BOT_RYOAI_TOKEN=",
773
- "TELEGRAM_BOT_RYOAI_ROLE_PROFILE=",
774
- "TELEGRAM_BOT_RYOAI_AI_CLIENT=",
775
- "TELEGRAM_BOT_RYOAI_AI_MODEL=",
776
- "TELEGRAM_BOT_RYOAI_AI_PERMISSION_MODE=",
777
- "TELEGRAM_BOT_RYOAI_AI_REASONING_EFFORT=",
778
- "",
779
766
  ].join("\n");
780
767
  }
781
768
  return [
@@ -1053,7 +1040,12 @@ function ensureProviderEnvTemplate(provider) {
1053
1040
  const filePath = providerEnvFilePath(provider);
1054
1041
  try {
1055
1042
  if (fs.existsSync(filePath)) {
1056
- return { filePath, created: false, existed: true };
1043
+ return {
1044
+ filePath,
1045
+ created: false,
1046
+ existed: true,
1047
+ normalized: normalizeExistingProviderEnvFile(provider, filePath),
1048
+ };
1057
1049
  }
1058
1050
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
1059
1051
  fs.writeFileSync(filePath, providerEnvTemplate(provider), "utf8");
@@ -2636,6 +2628,15 @@ function parseSimpleEnvText(rawText) {
2636
2628
  return out;
2637
2629
  }
2638
2630
 
2631
+ function formatProviderEnvValue(rawValue) {
2632
+ const text = String(rawValue ?? "");
2633
+ if (!text) return "";
2634
+ if (/^[A-Za-z0-9_./:@,\-]+$/.test(text)) {
2635
+ return text;
2636
+ }
2637
+ return JSON.stringify(text);
2638
+ }
2639
+
2639
2640
  function normalizeTelegramBotEnvKey(rawValue, fallback = "") {
2640
2641
  const normalized = String(rawValue || "")
2641
2642
  .trim()
@@ -2705,6 +2706,240 @@ function collectTelegramEnvBotEntries(parsedEnv) {
2705
2706
  return Array.from(entries.values());
2706
2707
  }
2707
2708
 
2709
+ function telegramBotEntryDisplayNameForState(entry) {
2710
+ const current = safeObject(entry);
2711
+ const username = normalizeTelegramBotUsername(current.username || "");
2712
+ 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`;
2716
+ }
2717
+
2718
+ function parseTelegramBotEntryFile(filePath) {
2719
+ const raw = fs.readFileSync(filePath, "utf8");
2720
+ const parsed = parseSimpleEnvText(raw);
2721
+ const keyFromFile = normalizeTelegramBotEnvKey(path.basename(filePath, path.extname(filePath)), "telegram_bot");
2722
+ return {
2723
+ key: keyFromFile,
2724
+ username: normalizeTelegramBotUsername(parsed.TELEGRAM_BOT_NAME || parsed.TELEGRAM_BOT_USERNAME || ""),
2725
+ serverBotID: String(parsed.TELEGRAM_BOT_SERVER_BOT_ID || "").trim(),
2726
+ token: String(parsed.TELEGRAM_BOT_TOKEN || "").trim(),
2727
+ roleProfile: normalizeRunnerRoleProfileName(parsed.TELEGRAM_BOT_ROLE_PROFILE || ""),
2728
+ client: normalizeLocalAIClientName(parsed.TELEGRAM_BOT_AI_CLIENT || "", ""),
2729
+ model: String(parsed.TELEGRAM_BOT_AI_MODEL || "").trim(),
2730
+ permissionMode: normalizeLocalAIPermissionMode(parsed.TELEGRAM_BOT_AI_PERMISSION_MODE || "", ""),
2731
+ reasoningEffort: normalizeLocalAIReasoningEffort(parsed.TELEGRAM_BOT_AI_REASONING_EFFORT || "", ""),
2732
+ entryFilePath: filePath,
2733
+ };
2734
+ }
2735
+
2736
+ function loadTelegramBotEntriesFromFiles() {
2737
+ const dirPath = telegramBotEntriesDirPath();
2738
+ if (!fs.existsSync(dirPath)) return [];
2739
+ return fs.readdirSync(dirPath, { withFileTypes: true })
2740
+ .filter((item) => item.isFile() && /\.env$/i.test(item.name))
2741
+ .map((item) => path.join(dirPath, item.name))
2742
+ .sort((left, right) => left.localeCompare(right))
2743
+ .map((filePath) => {
2744
+ try {
2745
+ return parseTelegramBotEntryFile(filePath);
2746
+ } catch {
2747
+ return null;
2748
+ }
2749
+ })
2750
+ .filter(Boolean);
2751
+ }
2752
+
2753
+ function buildMergedTelegramEnvParsed(globalParsed, entries) {
2754
+ const merged = { ...safeObject(globalParsed) };
2755
+ Object.keys(merged).forEach((key) => {
2756
+ if (isTelegramEntryEnvKey(key)) {
2757
+ delete merged[key];
2758
+ }
2759
+ });
2760
+ ensureArray(entries).forEach((entryRaw) => {
2761
+ const entry = safeObject(entryRaw);
2762
+ const upper = String(entry.key || "").trim().toUpperCase();
2763
+ if (!upper) return;
2764
+ merged[`TELEGRAM_BOT_${upper}_SERVER_BOT_ID`] = String(entry.serverBotID || "").trim();
2765
+ merged[`TELEGRAM_BOT_${upper}_USERNAME`] = normalizeTelegramBotUsername(entry.username || "");
2766
+ merged[`TELEGRAM_BOT_${upper}_TOKEN`] = String(entry.token || "").trim();
2767
+ merged[`TELEGRAM_BOT_${upper}_ROLE_PROFILE`] = String(entry.roleProfile || "").trim();
2768
+ merged[`TELEGRAM_BOT_${upper}_AI_CLIENT`] = String(entry.client || "").trim();
2769
+ merged[`TELEGRAM_BOT_${upper}_AI_MODEL`] = String(entry.model || "").trim();
2770
+ merged[`TELEGRAM_BOT_${upper}_AI_PERMISSION_MODE`] = String(entry.permissionMode || "").trim();
2771
+ merged[`TELEGRAM_BOT_${upper}_AI_REASONING_EFFORT`] = String(entry.reasoningEffort || "").trim();
2772
+ });
2773
+ return merged;
2774
+ }
2775
+
2776
+ function readTelegramEnvState() {
2777
+ const filePath = providerEnvFilePath("telegram");
2778
+ try {
2779
+ if (!fs.existsSync(filePath)) {
2780
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
2781
+ fs.writeFileSync(filePath, providerEnvTemplate("telegram"), "utf8");
2782
+ }
2783
+ } catch (err) {
2784
+ return {
2785
+ filePath,
2786
+ entriesDirPath: telegramBotEntriesDirPath(),
2787
+ parsed: {},
2788
+ globalParsed: {},
2789
+ entries: [],
2790
+ error: String(err?.message || err),
2791
+ };
2792
+ }
2793
+ const raw = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
2794
+ const globalParsedRaw = parseSimpleEnvText(raw);
2795
+ const inlineEntries = collectTelegramEnvBotEntries(globalParsedRaw);
2796
+ const fileEntries = loadTelegramBotEntriesFromFiles();
2797
+ const mergedEntries = new Map();
2798
+ inlineEntries.forEach((entry) => mergedEntries.set(entry.key, { ...entry }));
2799
+ fileEntries.forEach((entry) => mergedEntries.set(entry.key, { ...entry }));
2800
+ const globalParsed = { ...globalParsedRaw };
2801
+ Object.keys(globalParsed).forEach((key) => {
2802
+ if (isTelegramEntryEnvKey(key)) {
2803
+ delete globalParsed[key];
2804
+ }
2805
+ });
2806
+ const entries = Array.from(mergedEntries.values());
2807
+ return {
2808
+ filePath,
2809
+ entriesDirPath: telegramBotEntriesDirPath(),
2810
+ parsed: buildMergedTelegramEnvParsed(globalParsed, entries),
2811
+ globalParsed,
2812
+ entries,
2813
+ };
2814
+ }
2815
+
2816
+ function isTelegramEntryEnvKey(rawKey) {
2817
+ return /^TELEGRAM_BOT_([A-Z0-9_]+)_(TOKEN|USERNAME|SERVER_BOT_ID|ROLE_PROFILE|AI_CLIENT|AI_MODEL|AI_PERMISSION_MODE|AI_REASONING_EFFORT)$/i
2818
+ .test(String(rawKey || "").trim());
2819
+ }
2820
+
2821
+ function telegramEntryCommentNameForEnv(entry) {
2822
+ const current = safeObject(entry);
2823
+ const username = normalizeTelegramBotUsername(current.username || "");
2824
+ 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`;
2828
+ }
2829
+
2830
+ function renderNormalizedTelegramEnv(parsedEnv) {
2831
+ const parsed = safeObject(parsedEnv);
2832
+ const defaultBotKey = normalizeTelegramBotEnvKey(parsed.TELEGRAM_DEFAULT_BOT_KEY || "", "");
2833
+ const lines = [
2834
+ "# Metheus local Telegram bot settings",
2835
+ "# Keep this file on your machine only. Do not commit it.",
2836
+ "# Store only Telegram-wide settings here.",
2837
+ "# Per-bot secrets and AI settings live in ~/.metheus/telegram-bots/<bot-key>.env.",
2838
+ "",
2839
+ `TELEGRAM_API_BASE_URL=${formatProviderEnvValue(parsed.TELEGRAM_API_BASE_URL || "")}`,
2840
+ `TELEGRAM_AUTO_CLEAR_WEBHOOK=${boolFromRaw(parsed.TELEGRAM_AUTO_CLEAR_WEBHOOK, true) ? "true" : "false"}`,
2841
+ `TELEGRAM_ALLOWED_UPDATES=${formatProviderEnvValue(String(parsed.TELEGRAM_ALLOWED_UPDATES || "").trim() || "message,edited_message")}`,
2842
+ `TELEGRAM_DEFAULT_BOT_KEY=${formatProviderEnvValue(defaultBotKey || "")}`,
2843
+ "",
2844
+ "# Legacy fallback",
2845
+ `TELEGRAM_BOT_TOKEN=${formatProviderEnvValue(parsed.TELEGRAM_BOT_TOKEN || "")}`,
2846
+ "",
2847
+ ];
2848
+ const knownKeys = new Set([
2849
+ "TELEGRAM_API_BASE_URL",
2850
+ "TELEGRAM_AUTO_CLEAR_WEBHOOK",
2851
+ "TELEGRAM_ALLOWED_UPDATES",
2852
+ "TELEGRAM_DEFAULT_BOT_KEY",
2853
+ "TELEGRAM_BOT_TOKEN",
2854
+ ]);
2855
+ const extras = Object.keys(parsed)
2856
+ .filter((key) => !knownKeys.has(key) && !isTelegramEntryEnvKey(key))
2857
+ .sort((left, right) => left.localeCompare(right));
2858
+ if (extras.length) {
2859
+ lines.push("# Additional preserved keys");
2860
+ extras.forEach((key) => {
2861
+ lines.push(`${key}=${formatProviderEnvValue(parsed[key])}`);
2862
+ });
2863
+ lines.push("");
2864
+ }
2865
+ return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
2866
+ }
2867
+
2868
+ function renderTelegramBotEntryEnv(entryRaw) {
2869
+ const entry = safeObject(entryRaw);
2870
+ const botName = telegramEntryCommentNameForEnv(entry);
2871
+ const lines = [
2872
+ "# Metheus local Telegram bot entry",
2873
+ `# Bot: ${botName}`,
2874
+ "",
2875
+ `TELEGRAM_BOT_NAME=${formatProviderEnvValue(botName)}`,
2876
+ `TELEGRAM_BOT_SERVER_BOT_ID=${formatProviderEnvValue(entry.serverBotID || "")}`,
2877
+ `TELEGRAM_BOT_TOKEN=${formatProviderEnvValue(entry.token || "")}`,
2878
+ `TELEGRAM_BOT_ROLE_PROFILE=${formatProviderEnvValue(entry.roleProfile || "")}`,
2879
+ `TELEGRAM_BOT_AI_CLIENT=${formatProviderEnvValue(entry.client || "")}`,
2880
+ `TELEGRAM_BOT_AI_MODEL=${formatProviderEnvValue(entry.model || "")}`,
2881
+ `TELEGRAM_BOT_AI_PERMISSION_MODE=${formatProviderEnvValue(entry.permissionMode || "")}`,
2882
+ `TELEGRAM_BOT_AI_REASONING_EFFORT=${formatProviderEnvValue(entry.reasoningEffort || "")}`,
2883
+ "",
2884
+ ];
2885
+ return `${lines.join("\n").trimEnd()}\n`;
2886
+ }
2887
+
2888
+ function writeTelegramEnvState(parsedEnv) {
2889
+ const parsed = safeObject(parsedEnv);
2890
+ const entries = collectTelegramEnvBotEntries(parsed)
2891
+ .filter((entry) => (
2892
+ entry.token
2893
+ || entry.username
2894
+ || entry.serverBotID
2895
+ || entry.roleProfile
2896
+ || entry.client
2897
+ || entry.model
2898
+ || entry.permissionMode
2899
+ || entry.reasoningEffort
2900
+ ))
2901
+ .sort((left, right) => String(left.key || "").localeCompare(String(right.key || "")));
2902
+ const globalParsed = { ...parsed };
2903
+ Object.keys(globalParsed).forEach((key) => {
2904
+ if (isTelegramEntryEnvKey(key)) {
2905
+ delete globalParsed[key];
2906
+ }
2907
+ });
2908
+ const globalFilePath = providerEnvFilePath("telegram");
2909
+ fs.mkdirSync(path.dirname(globalFilePath), { recursive: true });
2910
+ fs.writeFileSync(globalFilePath, renderNormalizedTelegramEnv(globalParsed), {
2911
+ encoding: "utf8",
2912
+ mode: 0o600,
2913
+ });
2914
+ const entriesDirPath = telegramBotEntriesDirPath();
2915
+ fs.mkdirSync(entriesDirPath, { recursive: true });
2916
+ const activeFiles = new Set();
2917
+ entries.forEach((entry) => {
2918
+ const entryFilePath = telegramBotEntryFilePath(entry.key);
2919
+ activeFiles.add(path.resolve(entryFilePath));
2920
+ fs.writeFileSync(entryFilePath, renderTelegramBotEntryEnv(entry), {
2921
+ encoding: "utf8",
2922
+ mode: 0o600,
2923
+ });
2924
+ });
2925
+ fs.readdirSync(entriesDirPath, { withFileTypes: true })
2926
+ .filter((item) => item.isFile() && /\.env$/i.test(item.name))
2927
+ .forEach((item) => {
2928
+ const filePath = path.resolve(path.join(entriesDirPath, item.name));
2929
+ if (!activeFiles.has(filePath)) {
2930
+ fs.rmSync(filePath, { force: true });
2931
+ }
2932
+ });
2933
+ return globalFilePath;
2934
+ }
2935
+
2936
+ function normalizeExistingProviderEnvFile(provider, filePath) {
2937
+ if (normalizeBotProvider(provider) !== "telegram") return false;
2938
+ const state = readTelegramEnvState();
2939
+ writeTelegramEnvState(state.parsed);
2940
+ return true;
2941
+ }
2942
+
2708
2943
  function resolveTelegramEnvConfig(parsedEnv, filePath, config, selectors = {}) {
2709
2944
  const parsed = safeObject(parsedEnv);
2710
2945
  const legacyToken = String(parsed.TELEGRAM_BOT_TOKEN || "").trim();
@@ -2835,6 +3070,20 @@ function resolveTelegramEnvConfig(parsedEnv, filePath, config, selectors = {}) {
2835
3070
  function loadProviderEnvConfig(provider, selectors = {}) {
2836
3071
  const normalizedProvider = normalizeBotProvider(provider);
2837
3072
  const config = providerEnvConfig(normalizedProvider);
3073
+ if (normalizedProvider === "telegram") {
3074
+ const state = readTelegramEnvState();
3075
+ if (state.error) {
3076
+ return {
3077
+ ok: false,
3078
+ provider: normalizedProvider,
3079
+ providerLabel: config.label,
3080
+ filePath: state.filePath,
3081
+ error: state.error,
3082
+ token: "",
3083
+ };
3084
+ }
3085
+ return resolveTelegramEnvConfig(state.parsed, state.filePath, config, selectors);
3086
+ }
2838
3087
  const ensured = ensureProviderEnvTemplate(normalizedProvider);
2839
3088
  const filePath = ensured.filePath;
2840
3089
  if (ensured.error) {
@@ -2850,9 +3099,6 @@ function loadProviderEnvConfig(provider, selectors = {}) {
2850
3099
  try {
2851
3100
  const raw = fs.readFileSync(filePath, "utf8");
2852
3101
  const parsed = parseSimpleEnvText(raw);
2853
- if (normalizedProvider === "telegram") {
2854
- return resolveTelegramEnvConfig(parsed, filePath, config, selectors);
2855
- }
2856
3102
  const token = String(parsed[config.tokenKey] || "").trim();
2857
3103
  if (!token) {
2858
3104
  return {
@@ -3352,10 +3598,14 @@ function buildBotCommandDeps() {
3352
3598
  providerEnvOrder: PROVIDER_ENV_ORDER,
3353
3599
  providerEnvConfig,
3354
3600
  providerEnvFilePath,
3601
+ telegramBotEntriesDirPath,
3602
+ telegramBotEntryFilePath,
3355
3603
  ensureProviderEnvTemplate,
3356
3604
  parseCommandAndFlags,
3357
3605
  parseSimpleEnvText,
3358
3606
  collectTelegramEnvBotEntries,
3607
+ readTelegramEnvState,
3608
+ writeTelegramEnvState,
3359
3609
  normalizeBotProvider,
3360
3610
  normalizeTelegramBotEnvKey,
3361
3611
  normalizeTelegramBotUsername,
@@ -5114,6 +5364,45 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5114
5364
  && telegramEnvV2ByUsername.allowedUpdates.includes("channel_post"),
5115
5365
  `bot=${String(telegramEnvV2ByUsername.botKey || "(none)")} updates=${String((telegramEnvV2ByUsername.allowedUpdates || []).join(","))}`,
5116
5366
  );
5367
+ const telegramEnvNormalizeTempHome = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-telegram-env-normalize-"));
5368
+ const previousUserProfile = process.env.USERPROFILE;
5369
+ const previousHome = process.env.HOME;
5370
+ let normalizedGlobalText = "";
5371
+ let normalizedBotText = "";
5372
+ try {
5373
+ process.env.USERPROFILE = telegramEnvNormalizeTempHome;
5374
+ process.env.HOME = telegramEnvNormalizeTempHome;
5375
+ const normalizeTestEnvPath = providerEnvFilePath("telegram");
5376
+ fs.mkdirSync(path.dirname(normalizeTestEnvPath), { recursive: true });
5377
+ fs.writeFileSync(
5378
+ normalizeTestEnvPath,
5379
+ [
5380
+ "TELEGRAM_DEFAULT_BOT_KEY=ryoai_bot",
5381
+ "TELEGRAM_BOT_RYOAI_BOT_SERVER_BOT_ID=bot-ryoai",
5382
+ "TELEGRAM_BOT_RYOAI_BOT_TOKEN=ryoai-token",
5383
+ "",
5384
+ ].join("\n"),
5385
+ "utf8",
5386
+ );
5387
+ ensureProviderEnvTemplate("telegram");
5388
+ normalizedGlobalText = fs.readFileSync(normalizeTestEnvPath, "utf8");
5389
+ normalizedBotText = fs.readFileSync(telegramBotEntryFilePath("ryoai"), "utf8");
5390
+ } finally {
5391
+ if (previousUserProfile === undefined) delete process.env.USERPROFILE;
5392
+ else process.env.USERPROFILE = previousUserProfile;
5393
+ if (previousHome === undefined) delete process.env.HOME;
5394
+ else process.env.HOME = previousHome;
5395
+ }
5396
+ push(
5397
+ "telegram_env_existing_file_auto_normalizes_named_keys",
5398
+ normalizedGlobalText.includes("TELEGRAM_DEFAULT_BOT_KEY=ryoai")
5399
+ && !normalizedGlobalText.includes("TELEGRAM_BOT_RYOAI_BOT_TOKEN")
5400
+ && !normalizedGlobalText.includes("TELEGRAM_BOT_RYOAI_TOKEN")
5401
+ && normalizedBotText.includes("TELEGRAM_BOT_NAME=ryoai_bot")
5402
+ && normalizedBotText.includes("TELEGRAM_BOT_SERVER_BOT_ID=bot-ryoai")
5403
+ && normalizedBotText.includes("TELEGRAM_BOT_TOKEN=ryoai-token"),
5404
+ `${normalizedGlobalText}\n---\n${normalizedBotText}`,
5405
+ );
5117
5406
  const telegramSupport = getProviderSupport("telegram");
5118
5407
  push(
5119
5408
  "provider_support_telegram_full_runner",
@@ -411,6 +411,17 @@ async function promptKeepChangeClear(ui, title, { allowClear = true, defaultValu
411
411
  }
412
412
 
413
413
  function loadProviderEnvState(provider, deps) {
414
+ if (provider === "telegram" && typeof deps?.readTelegramEnvState === "function") {
415
+ const state = deps.readTelegramEnvState();
416
+ if (state?.error) {
417
+ throw new Error(String(state.error));
418
+ }
419
+ return {
420
+ provider,
421
+ filePath: String(state.filePath || "").trim(),
422
+ parsed: safeObject(state.parsed),
423
+ };
424
+ }
414
425
  const ensureTemplate = requireDependency(deps, "ensureProviderEnvTemplate");
415
426
  const filePathResolver = requireDependency(deps, "providerEnvFilePath");
416
427
  const parseEnv = requireDependency(deps, "parseSimpleEnvText");
@@ -441,6 +452,15 @@ function telegramEntryEnvKeys(botKey) {
441
452
  };
442
453
  }
443
454
 
455
+ function telegramEntryCommentName(entry) {
456
+ const current = safeObject(entry);
457
+ const username = String(current.username || "").trim();
458
+ if (username) return username;
459
+ const key = String(current.key || "").trim();
460
+ if (!key) return "telegram_bot";
461
+ return key.endsWith("_bot") ? key : `${key}_bot`;
462
+ }
463
+
444
464
  function persistTelegramUsername(entry) {
445
465
  const current = safeObject(entry);
446
466
  if (current.__preferServerIdentity) return "";
@@ -491,6 +511,11 @@ function telegramKnownKeys(parsedEnv, deps) {
491
511
  return keys;
492
512
  }
493
513
 
514
+ function isTelegramEntryEnvKey(rawKey) {
515
+ return /^TELEGRAM_BOT_([A-Z0-9_]+)_(TOKEN|USERNAME|SERVER_BOT_ID|ROLE_PROFILE|AI_CLIENT|AI_MODEL|AI_PERMISSION_MODE|AI_REASONING_EFFORT)$/i
516
+ .test(String(rawKey || "").trim());
517
+ }
518
+
494
519
  function renderTelegramEnv(parsedEnv, deps) {
495
520
  const parsed = safeObject(parsedEnv);
496
521
  const collectEntries = requireDependency(deps, "collectTelegramEnvBotEntries");
@@ -535,7 +560,7 @@ function renderTelegramEnv(parsedEnv, deps) {
535
560
  entries.forEach((entry) => {
536
561
  const keys = telegramEntryEnvKeys(entry.key);
537
562
  const entryLines = [
538
- `# Telegram bot entry: ${entry.key}`,
563
+ `# Telegram bot entry: ${telegramEntryCommentName(entry)}`,
539
564
  `${keys.serverBotID}=${formatEnvValue(entry.serverBotID || "")}`,
540
565
  ];
541
566
  if (String(entry.username || "").trim()) {
@@ -555,7 +580,7 @@ function renderTelegramEnv(parsedEnv, deps) {
555
580
  }
556
581
  const knownKeys = telegramKnownKeys(parsed, deps);
557
582
  const extras = Object.keys(parsed)
558
- .filter((key) => !knownKeys.has(key))
583
+ .filter((key) => !knownKeys.has(key) && !isTelegramEntryEnvKey(key))
559
584
  .sort((left, right) => left.localeCompare(right));
560
585
  if (extras.length) {
561
586
  lines.push("# Additional preserved keys");
@@ -591,6 +616,9 @@ function renderTokenOnlyProviderEnv(provider, parsedEnv, deps) {
591
616
  }
592
617
 
593
618
  function writeProviderEnvState(provider, parsedEnv, deps) {
619
+ if (provider === "telegram" && typeof deps?.writeTelegramEnvState === "function") {
620
+ return deps.writeTelegramEnvState(parsedEnv);
621
+ }
594
622
  const filePath = String(requireDependency(deps, "providerEnvFilePath")(provider) || "").trim();
595
623
  const rendered = provider === "telegram"
596
624
  ? renderTelegramEnv(parsedEnv, deps)
@@ -289,6 +289,15 @@ export async function runSelftestBotCommands(push, deps) {
289
289
  const baseURL = `http://127.0.0.1:${mock.port}`;
290
290
  const telegramApiBaseURL = `${baseURL}/telegram`;
291
291
  const telegramEnvPath = path.join(tempHome, ".metheus", "telegram.env");
292
+ const telegramBotEntriesDir = path.join(tempHome, ".metheus", "telegram-bots");
293
+ const readTelegramGlobals = () => parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
294
+ const readTelegramBotEntry = (botKey) => {
295
+ const filePath = path.join(telegramBotEntriesDir, `${String(botKey || "").trim()}.env`);
296
+ if (!fs.existsSync(filePath)) {
297
+ return {};
298
+ }
299
+ return parseSimpleEnvText(fs.readFileSync(filePath, "utf8"));
300
+ };
292
301
 
293
302
  const setupResult = await runCLI({
294
303
  cliPath,
@@ -314,7 +323,7 @@ export async function runSelftestBotCommands(push, deps) {
314
323
  ],
315
324
  env,
316
325
  });
317
- const telegramGlobals = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
326
+ const telegramGlobals = readTelegramGlobals();
318
327
  push(
319
328
  "bot_global_updates_telegram_settings",
320
329
  String(telegramGlobals.TELEGRAM_API_BASE_URL || "") === telegramApiBaseURL
@@ -339,17 +348,18 @@ export async function runSelftestBotCommands(push, deps) {
339
348
  ]),
340
349
  },
341
350
  });
342
- const addState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
351
+ const addState = readTelegramBotEntry("monitorselftestbot");
352
+ const addGlobals = readTelegramGlobals();
343
353
  push(
344
354
  "bot_add_guided_creates_named_telegram_entry",
345
- String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_SERVER_BOT_ID || "") === mock.bots[0].id
346
- && String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_USERNAME || "").trim() === ""
347
- && String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_ROLE_PROFILE || "") === "monitor"
348
- && String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "") === "codex"
349
- && String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_PERMISSION_MODE || "") === "read_only"
350
- && String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_REASONING_EFFORT || "") === "low"
351
- && String(addState.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
352
- `default=${String(addState.TELEGRAM_DEFAULT_BOT_KEY || "")} role=${String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_ROLE_PROFILE || "")} client=${String(addState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "")}`,
355
+ String(addState.TELEGRAM_BOT_NAME || "").toLowerCase().includes("monitorselftestbot")
356
+ && String(addState.TELEGRAM_BOT_TOKEN || "") === "selftest-main-token"
357
+ && String(addState.TELEGRAM_BOT_ROLE_PROFILE || "") === "monitor"
358
+ && String(addState.TELEGRAM_BOT_AI_CLIENT || "") === "codex"
359
+ && String(addState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "read_only"
360
+ && String(addState.TELEGRAM_BOT_AI_REASONING_EFFORT || "") === "low"
361
+ && String(addGlobals.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
362
+ `default=${String(addGlobals.TELEGRAM_DEFAULT_BOT_KEY || "")} token=${String(addState.TELEGRAM_BOT_TOKEN || "")} role=${String(addState.TELEGRAM_BOT_ROLE_PROFILE || "")} client=${String(addState.TELEGRAM_BOT_AI_CLIENT || "")}`,
353
363
  );
354
364
 
355
365
  const groupedMock = await createMockServer({
@@ -388,15 +398,15 @@ export async function runSelftestBotCommands(push, deps) {
388
398
  ]),
389
399
  },
390
400
  });
391
- const groupedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
401
+ const groupedState = readTelegramBotEntry("ryoai");
392
402
  push(
393
403
  "bot_add_guided_autoresolves_server_bot_group_without_role_prompt",
394
- String(groupedState.TELEGRAM_BOT_RYOAI_USERNAME || "").trim() === ""
395
- && String(groupedState.TELEGRAM_BOT_RYOAI_SERVER_BOT_ID || "") === ""
396
- && String(groupedState.TELEGRAM_BOT_RYOAI_ROLE_PROFILE || "") === ""
397
- && String(groupedState.TELEGRAM_BOT_RYOAI_AI_CLIENT || "") === ""
398
- && String(groupedState.TELEGRAM_BOT_RYOAI_AI_PERMISSION_MODE || "") === "",
399
- `username=${String(groupedState.TELEGRAM_BOT_RYOAI_USERNAME || "")} server_bot_id=${String(groupedState.TELEGRAM_BOT_RYOAI_SERVER_BOT_ID || "")} role=${String(groupedState.TELEGRAM_BOT_RYOAI_ROLE_PROFILE || "")}`,
404
+ String(groupedState.TELEGRAM_BOT_NAME || "").toLowerCase() === "ryoai_bot"
405
+ && String(groupedState.TELEGRAM_BOT_SERVER_BOT_ID || "") === ""
406
+ && String(groupedState.TELEGRAM_BOT_ROLE_PROFILE || "") === ""
407
+ && String(groupedState.TELEGRAM_BOT_AI_CLIENT || "") === ""
408
+ && String(groupedState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "",
409
+ `name=${String(groupedState.TELEGRAM_BOT_NAME || "")} server_bot_id=${String(groupedState.TELEGRAM_BOT_SERVER_BOT_ID || "")} role=${String(groupedState.TELEGRAM_BOT_ROLE_PROFILE || "")}`,
400
410
  );
401
411
 
402
412
  const groupedEditResult = await runCLI({
@@ -571,14 +581,14 @@ export async function runSelftestBotCommands(push, deps) {
571
581
  !String(guidedEditResult.stdout || "").includes("Telegram username (without @)"),
572
582
  String(guidedEditResult.stdout || "").split(/\r?\n/).filter((line) => line.includes("Telegram username")).join(" | ") || "username prompt skipped",
573
583
  );
574
- const guidedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
584
+ const guidedState = readTelegramBotEntry("monitorselftestbot");
575
585
  push(
576
586
  "bot_edit_guided_prompts_update_ai_binding_fields",
577
- String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "") === "gemini"
578
- && String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "") === "gemini-3.1-pro"
579
- && String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_PERMISSION_MODE || "") === "workspace_write"
580
- && String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_REASONING_EFFORT || "") === "medium",
581
- `client=${String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "")} model=${String(guidedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "")}`,
587
+ String(guidedState.TELEGRAM_BOT_AI_CLIENT || "") === "gemini"
588
+ && String(guidedState.TELEGRAM_BOT_AI_MODEL || "") === "gemini-3.1-pro"
589
+ && String(guidedState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "workspace_write"
590
+ && String(guidedState.TELEGRAM_BOT_AI_REASONING_EFFORT || "") === "medium",
591
+ `client=${String(guidedState.TELEGRAM_BOT_AI_CLIENT || "")} model=${String(guidedState.TELEGRAM_BOT_AI_MODEL || "")}`,
582
592
  );
583
593
 
584
594
  await runCLI({
@@ -596,15 +606,15 @@ export async function runSelftestBotCommands(push, deps) {
596
606
  ],
597
607
  env,
598
608
  });
599
- const editedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
609
+ const editedState = readTelegramBotEntry("monitorselftestbot");
600
610
  push(
601
611
  "bot_edit_updates_ai_binding_fields",
602
- String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_TOKEN || "") === "selftest-edited-token"
603
- && String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "") === "claude"
604
- && String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "") === "Sonnet 4.6r"
605
- && String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_PERMISSION_MODE || "") === "workspace_write"
606
- && String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_REASONING_EFFORT || "") === "medium",
607
- `client=${String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "")} model=${String(editedState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "")}`,
612
+ String(editedState.TELEGRAM_BOT_TOKEN || "") === "selftest-edited-token"
613
+ && String(editedState.TELEGRAM_BOT_AI_CLIENT || "") === "claude"
614
+ && String(editedState.TELEGRAM_BOT_AI_MODEL || "") === "Sonnet 4.6r"
615
+ && String(editedState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "workspace_write"
616
+ && String(editedState.TELEGRAM_BOT_AI_REASONING_EFFORT || "") === "medium",
617
+ `client=${String(editedState.TELEGRAM_BOT_AI_CLIENT || "")} model=${String(editedState.TELEGRAM_BOT_AI_MODEL || "")}`,
608
618
  );
609
619
 
610
620
  await runCLI({
@@ -619,7 +629,7 @@ export async function runSelftestBotCommands(push, deps) {
619
629
  ]),
620
630
  },
621
631
  });
622
- const guidedDefaultState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
632
+ const guidedDefaultState = readTelegramGlobals();
623
633
  push(
624
634
  "bot_set_default_guided_selects_entry",
625
635
  String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
@@ -662,7 +672,7 @@ export async function runSelftestBotCommands(push, deps) {
662
672
  ],
663
673
  env,
664
674
  });
665
- const defaultState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
675
+ const defaultState = readTelegramGlobals();
666
676
  push(
667
677
  "bot_set_default_updates_default_bot_key",
668
678
  String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
@@ -744,16 +754,15 @@ export async function runSelftestBotCommands(push, deps) {
744
754
  ],
745
755
  env,
746
756
  });
747
- const aliasAddState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
757
+ const aliasAddState = readTelegramBotEntry("monitorselftestbot");
748
758
  push(
749
759
  "bot_add_accepts_ai_prefixed_option_aliases",
750
- String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_SERVER_BOT_ID || "") === mock.bots[0].id
751
- && String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_USERNAME || "") === ""
752
- && String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "") === "codex"
753
- && String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "") === "gpt-5.4"
754
- && String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_PERMISSION_MODE || "") === "read_only"
755
- && String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_REASONING_EFFORT || "") === "low",
756
- `client=${String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_CLIENT || "")} model=${String(aliasAddState.TELEGRAM_BOT_MONITORSELFTESTBOT_AI_MODEL || "")}`,
760
+ String(aliasAddState.TELEGRAM_BOT_SERVER_BOT_ID || "") === mock.bots[0].id
761
+ && String(aliasAddState.TELEGRAM_BOT_AI_CLIENT || "") === "codex"
762
+ && String(aliasAddState.TELEGRAM_BOT_AI_MODEL || "") === "gpt-5.4"
763
+ && String(aliasAddState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "read_only"
764
+ && String(aliasAddState.TELEGRAM_BOT_AI_REASONING_EFFORT || "") === "low",
765
+ `client=${String(aliasAddState.TELEGRAM_BOT_AI_CLIENT || "")} model=${String(aliasAddState.TELEGRAM_BOT_AI_MODEL || "")}`,
757
766
  );
758
767
 
759
768
  const guidedRemoveBeforeList = await runCLI({
@@ -823,12 +832,13 @@ export async function runSelftestBotCommands(push, deps) {
823
832
  ],
824
833
  env,
825
834
  });
826
- const migratedState = parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
835
+ const migratedState = readTelegramBotEntry("legacy_main");
836
+ const migratedGlobals = readTelegramGlobals();
827
837
  push(
828
838
  "bot_migrate_converts_legacy_token_to_named_entry",
829
- String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "") === "legacy-selftest-token"
830
- && String(migratedState.TELEGRAM_BOT_TOKEN || "") === "",
831
- `legacy_named=${String(migratedState.TELEGRAM_BOT_LEGACY_MAIN_TOKEN || "")} fallback=${String(migratedState.TELEGRAM_BOT_TOKEN || "")}`,
839
+ String(migratedState.TELEGRAM_BOT_TOKEN || "") === "legacy-selftest-token"
840
+ && String(migratedGlobals.TELEGRAM_BOT_TOKEN || "") === "",
841
+ `legacy_named=${String(migratedState.TELEGRAM_BOT_TOKEN || "")} fallback=${String(migratedGlobals.TELEGRAM_BOT_TOKEN || "")}`,
832
842
  );
833
843
  } catch (err) {
834
844
  push("bot_commands_smoke", false, String(err?.message || err));
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.94",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [