metheus-governance-mcp-cli 0.2.93 → 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,11 +165,16 @@ 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
 
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>_*`.
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
168
178
 
169
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.
170
180
 
@@ -230,7 +240,7 @@ Behavior:
230
240
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
231
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.
232
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.
233
- - 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.
234
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.
235
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.
236
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.
@@ -249,15 +259,15 @@ Behavior:
249
259
  - `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
250
260
  - `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
251
261
  - `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
252
- - Telegram supports named local bot entries with:
253
- - `SERVER_BOT_ID`
254
- - `USERNAME` (optional fallback only when server binding is unavailable)
255
- - `TOKEN`
256
- - `ROLE_PROFILE`
257
- - `AI_CLIENT`
258
- - `AI_MODEL`
259
- - `AI_PERMISSION_MODE`
260
- - `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`
261
271
  - Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
262
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.
263
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 [
@@ -2719,6 +2706,113 @@ function collectTelegramEnvBotEntries(parsedEnv) {
2719
2706
  return Array.from(entries.values());
2720
2707
  }
2721
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
+
2722
2816
  function isTelegramEntryEnvKey(rawKey) {
2723
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
2724
2818
  .test(String(rawKey || "").trim());
@@ -2735,30 +2829,12 @@ function telegramEntryCommentNameForEnv(entry) {
2735
2829
 
2736
2830
  function renderNormalizedTelegramEnv(parsedEnv) {
2737
2831
  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
2832
  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
2833
  const lines = [
2759
2834
  "# Metheus local Telegram bot settings",
2760
2835
  "# Keep this file on your machine only. Do not commit it.",
2761
- "# Store Telegram bot secrets, AI bindings, and local transport options only.",
2836
+ "# Store only Telegram-wide settings here.",
2837
+ "# Per-bot secrets and AI settings live in ~/.metheus/telegram-bots/<bot-key>.env.",
2762
2838
  "",
2763
2839
  `TELEGRAM_API_BASE_URL=${formatProviderEnvValue(parsed.TELEGRAM_API_BASE_URL || "")}`,
2764
2840
  `TELEGRAM_AUTO_CLEAR_WEBHOOK=${boolFromRaw(parsed.TELEGRAM_AUTO_CLEAR_WEBHOOK, true) ? "true" : "false"}`,
@@ -2769,29 +2845,6 @@ function renderNormalizedTelegramEnv(parsedEnv) {
2769
2845
  `TELEGRAM_BOT_TOKEN=${formatProviderEnvValue(parsed.TELEGRAM_BOT_TOKEN || "")}`,
2770
2846
  "",
2771
2847
  ];
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
2848
  const knownKeys = new Set([
2796
2849
  "TELEGRAM_API_BASE_URL",
2797
2850
  "TELEGRAM_AUTO_CLEAR_WEBHOOK",
@@ -2799,17 +2852,6 @@ function renderNormalizedTelegramEnv(parsedEnv) {
2799
2852
  "TELEGRAM_DEFAULT_BOT_KEY",
2800
2853
  "TELEGRAM_BOT_TOKEN",
2801
2854
  ]);
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
2855
  const extras = Object.keys(parsed)
2814
2856
  .filter((key) => !knownKeys.has(key) && !isTelegramEntryEnvKey(key))
2815
2857
  .sort((left, right) => left.localeCompare(right));
@@ -2823,17 +2865,78 @@ function renderNormalizedTelegramEnv(parsedEnv) {
2823
2865
  return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
2824
2866
  }
2825
2867
 
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, {
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), {
2834
2911
  encoding: "utf8",
2835
2912
  mode: 0o600,
2836
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);
2837
2940
  return true;
2838
2941
  }
2839
2942
 
@@ -2967,6 +3070,20 @@ function resolveTelegramEnvConfig(parsedEnv, filePath, config, selectors = {}) {
2967
3070
  function loadProviderEnvConfig(provider, selectors = {}) {
2968
3071
  const normalizedProvider = normalizeBotProvider(provider);
2969
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
+ }
2970
3087
  const ensured = ensureProviderEnvTemplate(normalizedProvider);
2971
3088
  const filePath = ensured.filePath;
2972
3089
  if (ensured.error) {
@@ -2982,9 +3099,6 @@ function loadProviderEnvConfig(provider, selectors = {}) {
2982
3099
  try {
2983
3100
  const raw = fs.readFileSync(filePath, "utf8");
2984
3101
  const parsed = parseSimpleEnvText(raw);
2985
- if (normalizedProvider === "telegram") {
2986
- return resolveTelegramEnvConfig(parsed, filePath, config, selectors);
2987
- }
2988
3102
  const token = String(parsed[config.tokenKey] || "").trim();
2989
3103
  if (!token) {
2990
3104
  return {
@@ -3484,10 +3598,14 @@ function buildBotCommandDeps() {
3484
3598
  providerEnvOrder: PROVIDER_ENV_ORDER,
3485
3599
  providerEnvConfig,
3486
3600
  providerEnvFilePath,
3601
+ telegramBotEntriesDirPath,
3602
+ telegramBotEntryFilePath,
3487
3603
  ensureProviderEnvTemplate,
3488
3604
  parseCommandAndFlags,
3489
3605
  parseSimpleEnvText,
3490
3606
  collectTelegramEnvBotEntries,
3607
+ readTelegramEnvState,
3608
+ writeTelegramEnvState,
3491
3609
  normalizeBotProvider,
3492
3610
  normalizeTelegramBotEnvKey,
3493
3611
  normalizeTelegramBotUsername,
@@ -5246,19 +5364,44 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5246
5364
  && telegramEnvV2ByUsername.allowedUpdates.includes("channel_post"),
5247
5365
  `bot=${String(telegramEnvV2ByUsername.botKey || "(none)")} updates=${String((telegramEnvV2ByUsername.allowedUpdates || []).join(","))}`,
5248
5366
  );
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
- `));
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
+ }
5254
5396
  push(
5255
5397
  "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,
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}`,
5262
5405
  );
5263
5406
  const telegramSupport = getProviderSupport("telegram");
5264
5407
  push(
@@ -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");
@@ -605,6 +616,9 @@ function renderTokenOnlyProviderEnv(provider, parsedEnv, deps) {
605
616
  }
606
617
 
607
618
  function writeProviderEnvState(provider, parsedEnv, deps) {
619
+ if (provider === "telegram" && typeof deps?.writeTelegramEnvState === "function") {
620
+ return deps.writeTelegramEnvState(parsedEnv);
621
+ }
608
622
  const filePath = String(requireDependency(deps, "providerEnvFilePath")(provider) || "").trim();
609
623
  const rendered = provider === "telegram"
610
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.93",
3
+ "version": "0.2.94",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [