metheus-governance-mcp-cli 0.2.73 → 0.2.75
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 +2 -2
- package/lib/bot-commands.mjs +236 -13
- package/lib/selftest-bot-commands.mjs +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -247,8 +247,8 @@ Behavior:
|
|
|
247
247
|
- `AI_PERMISSION_MODE`
|
|
248
248
|
- `AI_REASONING_EFFORT`
|
|
249
249
|
- Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
|
|
250
|
-
- `bot verify` checks the configured local token, cross-checks the server bot binding,
|
|
251
|
-
- `bot show` prints one local bot entry in detail, including grouped role summaries when one server bot name expands to multiple roles.
|
|
250
|
+
- `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.
|
|
251
|
+
- `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.
|
|
252
252
|
- `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
|
|
253
253
|
- `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
|
|
254
254
|
- `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
|
package/lib/bot-commands.mjs
CHANGED
|
@@ -108,6 +108,70 @@ function maskSecret(rawValue) {
|
|
|
108
108
|
return `${text.slice(0, 4)}...${text.slice(-4)}`;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
function supportsANSIColors() {
|
|
112
|
+
if (process.env.NO_COLOR) return false;
|
|
113
|
+
if (!process.stdout?.isTTY) return false;
|
|
114
|
+
const term = String(process.env.TERM || "").trim().toLowerCase();
|
|
115
|
+
return term !== "dumb";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function colorText(text, colorCode) {
|
|
119
|
+
const value = String(text || "");
|
|
120
|
+
if (!value || !supportsANSIColors()) return value;
|
|
121
|
+
return `\u001b[${colorCode}m${value}\u001b[0m`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function padRight(text, width) {
|
|
125
|
+
const value = String(text || "");
|
|
126
|
+
if (value.length >= width) return value.slice(0, width);
|
|
127
|
+
return `${value}${" ".repeat(width - value.length)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function wrapAsciiLine(text, width) {
|
|
131
|
+
const value = String(text || "");
|
|
132
|
+
if (!value) return [""];
|
|
133
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
134
|
+
if (!words.length) return [""];
|
|
135
|
+
const lines = [];
|
|
136
|
+
let current = "";
|
|
137
|
+
words.forEach((word) => {
|
|
138
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
139
|
+
if (candidate.length <= width) {
|
|
140
|
+
current = candidate;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (current) {
|
|
144
|
+
lines.push(current);
|
|
145
|
+
current = word;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let remaining = word;
|
|
149
|
+
while (remaining.length > width) {
|
|
150
|
+
lines.push(remaining.slice(0, width));
|
|
151
|
+
remaining = remaining.slice(width);
|
|
152
|
+
}
|
|
153
|
+
current = remaining;
|
|
154
|
+
});
|
|
155
|
+
if (current) lines.push(current);
|
|
156
|
+
return lines.length ? lines : [""];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatAsciiBlock(title, lines = [], { accentColor = "36" } = {}) {
|
|
160
|
+
const contentWidth = 62;
|
|
161
|
+
const blockLines = [
|
|
162
|
+
title ? String(title || "").trim() : "",
|
|
163
|
+
...ensureArray(lines).flatMap((line) => wrapAsciiLine(line, contentWidth)),
|
|
164
|
+
].filter((line, index, all) => !(index > 0 && !line && !all[index - 1]));
|
|
165
|
+
const border = `+${"-".repeat(contentWidth + 2)}+`;
|
|
166
|
+
const formatted = [colorText(border, accentColor)];
|
|
167
|
+
blockLines.forEach((line, index) => {
|
|
168
|
+
const rendered = `| ${padRight(line, contentWidth)} |`;
|
|
169
|
+
formatted.push(index === 0 ? colorText(rendered, accentColor) : rendered);
|
|
170
|
+
});
|
|
171
|
+
formatted.push(colorText(border, accentColor));
|
|
172
|
+
return `${formatted.join("\n")}\n`;
|
|
173
|
+
}
|
|
174
|
+
|
|
111
175
|
function formatEnvValue(rawValue) {
|
|
112
176
|
const text = String(rawValue ?? "");
|
|
113
177
|
if (!text) return "";
|
|
@@ -125,6 +189,13 @@ function providerTokenKey(provider, deps) {
|
|
|
125
189
|
return safeObject(requireDependency(deps, "providerEnvConfig")(provider)).tokenKey || "";
|
|
126
190
|
}
|
|
127
191
|
|
|
192
|
+
function shouldRenderPromptChrome(flags) {
|
|
193
|
+
const parsedFlags = safeObject(flags);
|
|
194
|
+
if (boolFromRaw(parsedFlags["non-interactive"] ?? parsedFlags.yes, false)) return false;
|
|
195
|
+
if (boolFromRaw(parsedFlags.json, false)) return false;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
128
199
|
function printBotUsage(deps) {
|
|
129
200
|
const cliName = String(deps?.cliName || "metheus-governance-mcp-cli").trim() || "metheus-governance-mcp-cli";
|
|
130
201
|
process.stdout.write(
|
|
@@ -156,6 +227,24 @@ function printBotUsage(deps) {
|
|
|
156
227
|
}
|
|
157
228
|
|
|
158
229
|
function createPrompter() {
|
|
230
|
+
let flowTitle = "";
|
|
231
|
+
let flowSubtitle = "";
|
|
232
|
+
let stepIndex = 0;
|
|
233
|
+
function write(text) {
|
|
234
|
+
process.stdout.write(String(text || ""));
|
|
235
|
+
}
|
|
236
|
+
function printFlowBanner() {
|
|
237
|
+
if (!flowTitle) return;
|
|
238
|
+
const lines = flowSubtitle ? [flowSubtitle] : [];
|
|
239
|
+
write(`\n${formatAsciiBlock(flowTitle, lines, { accentColor: "35" })}`);
|
|
240
|
+
}
|
|
241
|
+
function beginPrompt(title, lines = []) {
|
|
242
|
+
stepIndex += 1;
|
|
243
|
+
const bannerTitle = flowTitle
|
|
244
|
+
? `${flowTitle} | STEP ${stepIndex}`
|
|
245
|
+
: `STEP ${stepIndex}`;
|
|
246
|
+
write(`\n${formatAsciiBlock(bannerTitle, [title, ...ensureArray(lines)], { accentColor: "36" })}`);
|
|
247
|
+
}
|
|
159
248
|
const scriptedPromptAnswersRaw = String(process.env.METHEUS_SCRIPTED_PROMPT_ANSWERS || "").trim();
|
|
160
249
|
if (scriptedPromptAnswersRaw) {
|
|
161
250
|
let scriptedAnswers = [];
|
|
@@ -167,17 +256,31 @@ function createPrompter() {
|
|
|
167
256
|
}
|
|
168
257
|
let answerIndex = 0;
|
|
169
258
|
return {
|
|
259
|
+
setFlow(title, subtitle = "") {
|
|
260
|
+
flowTitle = String(title || "").trim();
|
|
261
|
+
flowSubtitle = String(subtitle || "").trim();
|
|
262
|
+
stepIndex = 0;
|
|
263
|
+
printFlowBanner();
|
|
264
|
+
},
|
|
265
|
+
beginPrompt,
|
|
170
266
|
ask(promptText) {
|
|
171
|
-
|
|
267
|
+
write(promptText);
|
|
172
268
|
const answer = answerIndex < scriptedAnswers.length ? scriptedAnswers[answerIndex] : "";
|
|
173
269
|
answerIndex += 1;
|
|
174
|
-
|
|
270
|
+
write(`${answer}\n`);
|
|
175
271
|
return Promise.resolve(String(answer || ""));
|
|
176
272
|
},
|
|
177
273
|
close() {},
|
|
178
274
|
};
|
|
179
275
|
}
|
|
180
276
|
return {
|
|
277
|
+
setFlow(title, subtitle = "") {
|
|
278
|
+
flowTitle = String(title || "").trim();
|
|
279
|
+
flowSubtitle = String(subtitle || "").trim();
|
|
280
|
+
stepIndex = 0;
|
|
281
|
+
printFlowBanner();
|
|
282
|
+
},
|
|
283
|
+
beginPrompt,
|
|
181
284
|
ask(promptText) {
|
|
182
285
|
return new Promise((resolve) => {
|
|
183
286
|
const rl = readline.createInterface({
|
|
@@ -195,8 +298,13 @@ function createPrompter() {
|
|
|
195
298
|
}
|
|
196
299
|
|
|
197
300
|
async function promptLine(ui, promptText, defaultValue = "") {
|
|
301
|
+
ui.beginPrompt(promptText, [
|
|
302
|
+
String(defaultValue || "").trim()
|
|
303
|
+
? `Press Enter to keep the current value: ${defaultValue}`
|
|
304
|
+
: "Type a value and press Enter.",
|
|
305
|
+
]);
|
|
198
306
|
const suffix = String(defaultValue || "").trim() ? ` [${defaultValue}]` : "";
|
|
199
|
-
const answer = await ui.ask(`${promptText}${suffix}: `);
|
|
307
|
+
const answer = await ui.ask(`${colorText(">", "32")} ${promptText}${suffix}: `);
|
|
200
308
|
const text = String(answer || "").trim();
|
|
201
309
|
return text || String(defaultValue || "").trim();
|
|
202
310
|
}
|
|
@@ -205,18 +313,19 @@ async function promptRequiredLine(ui, promptText, defaultValue = "") {
|
|
|
205
313
|
while (true) {
|
|
206
314
|
const answer = await promptLine(ui, promptText, defaultValue);
|
|
207
315
|
if (answer) return answer;
|
|
208
|
-
process.stdout.write("Value is required.\n");
|
|
316
|
+
process.stdout.write(`${colorText("Value is required.\n", "31")}`);
|
|
209
317
|
}
|
|
210
318
|
}
|
|
211
319
|
|
|
212
320
|
async function promptYesNo(ui, promptText, defaultValue = true) {
|
|
321
|
+
ui.beginPrompt(promptText, ["Choose y or n, then press Enter."]);
|
|
213
322
|
const hint = defaultValue ? "Y/n" : "y/N";
|
|
214
323
|
while (true) {
|
|
215
|
-
const answer = String(await ui.ask(`${promptText} [${hint}]: `) || "").trim().toLowerCase();
|
|
324
|
+
const answer = String(await ui.ask(`${colorText(">", "32")} ${promptText} [${hint}]: `) || "").trim().toLowerCase();
|
|
216
325
|
if (!answer) return defaultValue;
|
|
217
326
|
if (["y", "yes", "1", "true"].includes(answer)) return true;
|
|
218
327
|
if (["n", "no", "0", "false"].includes(answer)) return false;
|
|
219
|
-
process.stdout.write("Choose y or n.\n");
|
|
328
|
+
process.stdout.write(`${colorText("Choose y or n.\n", "31")}`);
|
|
220
329
|
}
|
|
221
330
|
}
|
|
222
331
|
|
|
@@ -249,16 +358,20 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
|
|
|
249
358
|
if (!list.length) {
|
|
250
359
|
throw new Error(`no options available for ${title}`);
|
|
251
360
|
}
|
|
361
|
+
const guidance = [
|
|
362
|
+
"Choose one option by number and press Enter.",
|
|
363
|
+
allowCancel ? "Enter 0 to cancel." : "",
|
|
364
|
+
].filter(Boolean);
|
|
365
|
+
ui.beginPrompt(title, guidance);
|
|
252
366
|
while (true) {
|
|
253
|
-
process.stdout.write(`${title}\n`);
|
|
254
367
|
list.forEach((option, index) => {
|
|
255
|
-
process.stdout.write(` ${index + 1}
|
|
368
|
+
process.stdout.write(` ${colorText(`[${index + 1}]`, "33")} ${formatChoiceLabel(option)}\n`);
|
|
256
369
|
});
|
|
257
370
|
if (allowCancel) {
|
|
258
|
-
process.stdout.write("
|
|
371
|
+
process.stdout.write(` ${colorText("[0]", "33")} Cancel\n`);
|
|
259
372
|
}
|
|
260
373
|
const defaultLabel = defaultIndex >= 0 && defaultIndex < list.length ? String(defaultIndex + 1) : "";
|
|
261
|
-
const answer = String(await ui.ask(
|
|
374
|
+
const answer = String(await ui.ask(`${colorText(">", "32")} ${defaultLabel ? `[${defaultLabel}] ` : ""}`) || "").trim();
|
|
262
375
|
if (!answer && defaultLabel) {
|
|
263
376
|
return list[defaultIndex];
|
|
264
377
|
}
|
|
@@ -267,7 +380,7 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
|
|
|
267
380
|
if (Number.isFinite(numeric) && numeric >= 1 && numeric <= list.length) {
|
|
268
381
|
return list[numeric - 1];
|
|
269
382
|
}
|
|
270
|
-
process.stdout.write("Select a valid number.\n");
|
|
383
|
+
process.stdout.write(`${colorText("Select a valid number.\n", "31")}`);
|
|
271
384
|
}
|
|
272
385
|
}
|
|
273
386
|
|
|
@@ -840,6 +953,7 @@ function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
|
|
|
840
953
|
reasoningEffort: selectedEntry.reasoningEffort,
|
|
841
954
|
} : null,
|
|
842
955
|
serverBinding: safeObject(extras.serverBinding),
|
|
956
|
+
routeLinks: safeObject(extras.routeLinks),
|
|
843
957
|
...safeObject(extras),
|
|
844
958
|
};
|
|
845
959
|
}
|
|
@@ -857,6 +971,60 @@ function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
|
|
|
857
971
|
};
|
|
858
972
|
}
|
|
859
973
|
|
|
974
|
+
function describeRouteRuntimeProfile(routeRole, serverBinding) {
|
|
975
|
+
const binding = safeObject(serverBinding);
|
|
976
|
+
if (binding.mode === "group") {
|
|
977
|
+
return safeObject(safeObject(binding.effectiveRoleProfiles)[String(routeRole || "").trim()]);
|
|
978
|
+
}
|
|
979
|
+
return safeObject(binding.effectiveRoleProfile);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function summarizeTelegramRouteLinks(parsedEnv, entry, serverBinding, deps) {
|
|
983
|
+
const selectedEntry = safeObject(entry);
|
|
984
|
+
const binding = safeObject(serverBinding);
|
|
985
|
+
const config = safeObject(requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true }));
|
|
986
|
+
const routes = ensureArray(config.routes);
|
|
987
|
+
const entryKey = String(selectedEntry.key || "").trim();
|
|
988
|
+
const defaultBotKey = String(safeObject(parsedEnv).TELEGRAM_DEFAULT_BOT_KEY || "").trim();
|
|
989
|
+
const serverBotID = String(binding.serverBotID || selectedEntry.serverBotID || "").trim();
|
|
990
|
+
const normalizedNames = new Set(
|
|
991
|
+
[
|
|
992
|
+
normalizeServerBotIdentityText(selectedEntry.username),
|
|
993
|
+
normalizeServerBotIdentityText(binding.name),
|
|
994
|
+
normalizeServerBotIdentityText(entryKey),
|
|
995
|
+
].filter(Boolean),
|
|
996
|
+
);
|
|
997
|
+
const linkedRoutes = [];
|
|
998
|
+
routes.forEach((rawRoute, index) => {
|
|
999
|
+
const route = safeObject(rawRoute);
|
|
1000
|
+
if (route.enabled === false) return;
|
|
1001
|
+
if (String(route.provider || "").trim().toLowerCase() !== "telegram") return;
|
|
1002
|
+
const routeName = String(route.name || route.route_name || `telegram-route-${index + 1}`).trim();
|
|
1003
|
+
const routeBotID = String(route.bot_id || route.botID || "").trim();
|
|
1004
|
+
const routeBotName = normalizeServerBotIdentityText(route.bot_name || route.botName || "");
|
|
1005
|
+
const routeRole = String(route.role_profile || route.roleProfile || route.role || "").trim();
|
|
1006
|
+
let matchedBy = "";
|
|
1007
|
+
if (serverBotID && routeBotID === serverBotID) {
|
|
1008
|
+
matchedBy = "bot_id";
|
|
1009
|
+
} else if (routeBotName && normalizedNames.has(routeBotName)) {
|
|
1010
|
+
matchedBy = "bot_name";
|
|
1011
|
+
} else if (!routeBotID && !routeBotName && entryKey && defaultBotKey && entryKey === defaultBotKey) {
|
|
1012
|
+
matchedBy = "default_bot";
|
|
1013
|
+
}
|
|
1014
|
+
if (!matchedBy) return;
|
|
1015
|
+
linkedRoutes.push({
|
|
1016
|
+
routeName,
|
|
1017
|
+
matchedBy,
|
|
1018
|
+
routeRole,
|
|
1019
|
+
runtimeRoleProfile: describeRouteRuntimeProfile(routeRole, binding),
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
return {
|
|
1023
|
+
total: linkedRoutes.length,
|
|
1024
|
+
routes: linkedRoutes,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
860
1028
|
function printBotList(provider, state, deps) {
|
|
861
1029
|
process.stdout.write(`${providerLabel(provider, deps)}\n`);
|
|
862
1030
|
process.stdout.write(` file: ${state.filePath}\n`);
|
|
@@ -895,6 +1063,7 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
|
|
|
895
1063
|
if (provider === "telegram") {
|
|
896
1064
|
const selectedEntry = entry || {};
|
|
897
1065
|
const serverBinding = safeObject(extras.serverBinding);
|
|
1066
|
+
const routeLinks = safeObject(extras.routeLinks);
|
|
898
1067
|
process.stdout.write(`${providerLabel(provider, deps)} bot\n`);
|
|
899
1068
|
process.stdout.write(` file: ${state.filePath}\n`);
|
|
900
1069
|
process.stdout.write(` name: ${telegramEntryDisplayName(selectedEntry)}\n`);
|
|
@@ -924,6 +1093,16 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
|
|
|
924
1093
|
process.stdout.write(` runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}\n`);
|
|
925
1094
|
}
|
|
926
1095
|
}
|
|
1096
|
+
process.stdout.write(` route_links: ${intFromRaw(routeLinks.total, 0)}\n`);
|
|
1097
|
+
ensureArray(routeLinks.routes).forEach((route) => {
|
|
1098
|
+
const routeRuntime = safeObject(route.runtimeRoleProfile);
|
|
1099
|
+
const routeDetail = [
|
|
1100
|
+
`matched_by=${String(route.matchedBy || "-")}`,
|
|
1101
|
+
route.routeRole ? `route_role=${String(route.routeRole || "").trim()}` : "",
|
|
1102
|
+
Object.keys(routeRuntime).length ? `runtime=${formatRoleProfileOutputLine(routeRuntime)}` : "",
|
|
1103
|
+
].filter(Boolean).join(" ");
|
|
1104
|
+
process.stdout.write(` linked_route[${String(route.routeName || "-")}]: ${routeDetail || "-"}\n`);
|
|
1105
|
+
});
|
|
927
1106
|
return;
|
|
928
1107
|
}
|
|
929
1108
|
const tokenKey = providerTokenKey(provider, deps);
|
|
@@ -2047,11 +2226,17 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
2047
2226
|
);
|
|
2048
2227
|
let overallOK = Boolean(result.ok);
|
|
2049
2228
|
let serverBinding = null;
|
|
2229
|
+
let routeLinks = null;
|
|
2050
2230
|
if (provider === "telegram") {
|
|
2051
2231
|
serverBinding = await resolveTelegramServerBindingDetails(envConfig, flags, deps);
|
|
2052
2232
|
if (serverBinding && !serverBinding.ok) {
|
|
2053
2233
|
overallOK = false;
|
|
2054
2234
|
}
|
|
2235
|
+
routeLinks = summarizeTelegramRouteLinks(state.parsed, {
|
|
2236
|
+
key: envConfig.botKey,
|
|
2237
|
+
serverBotID: envConfig.serverBotID,
|
|
2238
|
+
username: envConfig.botUsername,
|
|
2239
|
+
}, serverBinding, deps);
|
|
2055
2240
|
}
|
|
2056
2241
|
const interactiveJsonChoice = (
|
|
2057
2242
|
!boolFromRaw(flags.json, false)
|
|
@@ -2084,6 +2269,7 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
2084
2269
|
permissionMode: envConfig.permissionMode || "",
|
|
2085
2270
|
reasoningEffort: envConfig.reasoningEffort || "",
|
|
2086
2271
|
serverBinding,
|
|
2272
|
+
routeLinks,
|
|
2087
2273
|
}, null, 2)}\n`,
|
|
2088
2274
|
);
|
|
2089
2275
|
} else {
|
|
@@ -2099,6 +2285,7 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
2099
2285
|
provider === "telegram" ? `reasoning_effort: ${envConfig.reasoningEffort || "-"}` : "",
|
|
2100
2286
|
`detail: ${result.detail || "-"}`,
|
|
2101
2287
|
serverBinding ? `server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}` : "",
|
|
2288
|
+
routeLinks ? `route_links: ${intFromRaw(routeLinks.total, 0)}` : "",
|
|
2102
2289
|
].filter(Boolean);
|
|
2103
2290
|
if (provider === "telegram" && serverBinding) {
|
|
2104
2291
|
lines.push(`server_binding_mode: ${serverBinding.mode || "-"}`);
|
|
@@ -2113,6 +2300,17 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
2113
2300
|
lines.push(`runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}`);
|
|
2114
2301
|
}
|
|
2115
2302
|
}
|
|
2303
|
+
if (provider === "telegram" && routeLinks) {
|
|
2304
|
+
ensureArray(routeLinks.routes).forEach((route) => {
|
|
2305
|
+
const routeRuntime = safeObject(route.runtimeRoleProfile);
|
|
2306
|
+
const routeDetail = [
|
|
2307
|
+
`matched_by=${String(route.matchedBy || "-")}`,
|
|
2308
|
+
route.routeRole ? `route_role=${String(route.routeRole || "").trim()}` : "",
|
|
2309
|
+
Object.keys(routeRuntime).length ? `runtime=${formatRoleProfileOutputLine(routeRuntime)}` : "",
|
|
2310
|
+
].filter(Boolean).join(" ");
|
|
2311
|
+
lines.push(`linked_route[${String(route.routeName || "-")}]: ${routeDetail || "-"}`);
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2116
2314
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
2117
2315
|
}
|
|
2118
2316
|
if (!overallOK) {
|
|
@@ -2169,6 +2367,9 @@ async function removeTokenOnlyProvider(ui, provider, flags, deps) {
|
|
|
2169
2367
|
}
|
|
2170
2368
|
|
|
2171
2369
|
async function runBotSetup(ui, flags, deps) {
|
|
2370
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2371
|
+
ui.setFlow("BOT SETUP", "Guided local bot management");
|
|
2372
|
+
}
|
|
2172
2373
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2173
2374
|
const telegramActions = [
|
|
2174
2375
|
{ value: "list", label: "List bots" },
|
|
@@ -2281,12 +2482,13 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
|
|
|
2281
2482
|
flags,
|
|
2282
2483
|
deps,
|
|
2283
2484
|
);
|
|
2284
|
-
const
|
|
2485
|
+
const routeLinks = summarizeTelegramRouteLinks(state.parsed, entry, serverBinding, deps);
|
|
2486
|
+
const payload = buildBotShowPayload(provider, state, entry, deps, { serverBinding, routeLinks });
|
|
2285
2487
|
if (boolFromRaw(flags.json, false)) {
|
|
2286
2488
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
2287
2489
|
return;
|
|
2288
2490
|
}
|
|
2289
|
-
printBotShow(provider, state, entry, deps, { serverBinding });
|
|
2491
|
+
printBotShow(provider, state, entry, deps, { serverBinding, routeLinks });
|
|
2290
2492
|
return;
|
|
2291
2493
|
}
|
|
2292
2494
|
const payload = buildBotShowPayload(provider, state, null, deps);
|
|
@@ -2298,6 +2500,9 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
|
|
|
2298
2500
|
}
|
|
2299
2501
|
|
|
2300
2502
|
async function runBotAdd(ui, flags, deps) {
|
|
2503
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2504
|
+
ui.setFlow("BOT ADD", "Create a local bot entry from server bot identity and local token");
|
|
2505
|
+
}
|
|
2301
2506
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2302
2507
|
if (provider === "telegram") {
|
|
2303
2508
|
await addTelegramBot(ui, flags, deps);
|
|
@@ -2307,6 +2512,9 @@ async function runBotAdd(ui, flags, deps) {
|
|
|
2307
2512
|
}
|
|
2308
2513
|
|
|
2309
2514
|
async function runBotEdit(ui, flags, deps) {
|
|
2515
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2516
|
+
ui.setFlow("BOT EDIT", "Update an existing local bot entry");
|
|
2517
|
+
}
|
|
2310
2518
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2311
2519
|
if (provider === "telegram") {
|
|
2312
2520
|
await editTelegramBot(ui, flags, deps);
|
|
@@ -2316,6 +2524,9 @@ async function runBotEdit(ui, flags, deps) {
|
|
|
2316
2524
|
}
|
|
2317
2525
|
|
|
2318
2526
|
async function runBotRemove(ui, flags, deps) {
|
|
2527
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2528
|
+
ui.setFlow("BOT REMOVE", "Delete a local bot entry");
|
|
2529
|
+
}
|
|
2319
2530
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2320
2531
|
if (provider === "telegram") {
|
|
2321
2532
|
const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
|
|
@@ -2341,11 +2552,17 @@ async function runBotRemove(ui, flags, deps) {
|
|
|
2341
2552
|
}
|
|
2342
2553
|
|
|
2343
2554
|
async function runBotVerify(ui, flags, deps) {
|
|
2555
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2556
|
+
ui.setFlow("BOT VERIFY", "Check local token, server binding, and linked routes");
|
|
2557
|
+
}
|
|
2344
2558
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2345
2559
|
await verifyProviderEntry(ui, provider, flags, deps);
|
|
2346
2560
|
}
|
|
2347
2561
|
|
|
2348
2562
|
async function runBotSetDefault(ui, flags, deps) {
|
|
2563
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2564
|
+
ui.setFlow("BOT SET-DEFAULT", "Choose the default Telegram local entry");
|
|
2565
|
+
}
|
|
2349
2566
|
const provider = await selectProvider(ui, flags.provider, deps);
|
|
2350
2567
|
if (provider !== "telegram") {
|
|
2351
2568
|
throw new Error("bot set-default currently supports only --provider telegram");
|
|
@@ -2375,6 +2592,9 @@ async function runBotSetDefault(ui, flags, deps) {
|
|
|
2375
2592
|
}
|
|
2376
2593
|
|
|
2377
2594
|
async function runBotMigrate(ui, flags, deps) {
|
|
2595
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2596
|
+
ui.setFlow("BOT MIGRATE", "Convert legacy Telegram token config into a named entry");
|
|
2597
|
+
}
|
|
2378
2598
|
const provider = String(flags.provider || "").trim()
|
|
2379
2599
|
? requireDependency(deps, "normalizeBotProvider")(flags.provider)
|
|
2380
2600
|
: "telegram";
|
|
@@ -2425,6 +2645,9 @@ async function runBotMigrate(ui, flags, deps) {
|
|
|
2425
2645
|
}
|
|
2426
2646
|
|
|
2427
2647
|
async function runBotGlobal(ui, flags, deps) {
|
|
2648
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
2649
|
+
ui.setFlow("BOT GLOBAL", "Edit Telegram global local settings");
|
|
2650
|
+
}
|
|
2428
2651
|
const provider = String(flags.provider || "").trim()
|
|
2429
2652
|
? requireDependency(deps, "normalizeBotProvider")(flags.provider)
|
|
2430
2653
|
: "telegram";
|
|
@@ -84,6 +84,11 @@ function readTrailingJSON(rawText) {
|
|
|
84
84
|
return readJSON(text.slice(start));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function intFromRaw(rawValue, fallback = 0) {
|
|
88
|
+
const parsed = Number.parseInt(String(rawValue ?? "").trim(), 10);
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
function createMockServer(options = {}) {
|
|
88
93
|
const serverBots = ensureArray(options.serverBots).length
|
|
89
94
|
? ensureArray(options.serverBots)
|
|
@@ -434,6 +439,37 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
434
439
|
env,
|
|
435
440
|
});
|
|
436
441
|
const showPayload = readJSON(showResult.stdout);
|
|
442
|
+
const runnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
|
|
443
|
+
const runnerConfig = readJSON(fs.readFileSync(runnerConfigPath, "utf8"));
|
|
444
|
+
runnerConfig.routes = [
|
|
445
|
+
{
|
|
446
|
+
name: "telegram-monitor",
|
|
447
|
+
enabled: true,
|
|
448
|
+
project_id: "03c586a2-006d-4051-83b4-f353a5813176",
|
|
449
|
+
provider: "telegram",
|
|
450
|
+
bot_id: mock.bots[0].id,
|
|
451
|
+
role_profile: "monitor",
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "telegram-default",
|
|
455
|
+
enabled: true,
|
|
456
|
+
project_id: "03c586a2-006d-4051-83b4-f353a5813176",
|
|
457
|
+
provider: "telegram",
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
fs.writeFileSync(runnerConfigPath, `${JSON.stringify(runnerConfig, null, 2)}\n`, "utf8");
|
|
461
|
+
const showWithRoutesResult = await runCLI({
|
|
462
|
+
cliPath,
|
|
463
|
+
args: [
|
|
464
|
+
"bot", "show",
|
|
465
|
+
"--provider", "telegram",
|
|
466
|
+
"--bot-key", "monitorselftestbot",
|
|
467
|
+
"--base-url", baseURL,
|
|
468
|
+
"--json", "true",
|
|
469
|
+
],
|
|
470
|
+
env,
|
|
471
|
+
});
|
|
472
|
+
const showWithRoutesPayload = readJSON(showWithRoutesResult.stdout);
|
|
437
473
|
push(
|
|
438
474
|
"bot_show_returns_selected_telegram_entry",
|
|
439
475
|
safeObject(showPayload.entry).key === "monitorselftestbot"
|
|
@@ -441,6 +477,15 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
441
477
|
&& safeObject(showPayload.serverBinding).mode === "single",
|
|
442
478
|
`key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")} mode=${String(safeObject(showPayload.serverBinding).mode || "")}`,
|
|
443
479
|
);
|
|
480
|
+
push(
|
|
481
|
+
"bot_show_reports_linked_runner_routes",
|
|
482
|
+
intFromRaw(safeObject(showWithRoutesPayload.routeLinks).total, 0) >= 1
|
|
483
|
+
&& ensureArray(safeObject(showWithRoutesPayload.routeLinks).routes).some((route) => (
|
|
484
|
+
String(safeObject(route).routeName || "") === "telegram-monitor"
|
|
485
|
+
&& String(safeObject(route).matchedBy || "") === "bot_id"
|
|
486
|
+
)),
|
|
487
|
+
`routes=${JSON.stringify(safeObject(showWithRoutesPayload.routeLinks))}`,
|
|
488
|
+
);
|
|
444
489
|
|
|
445
490
|
await runCLI({
|
|
446
491
|
cliPath,
|
|
@@ -584,6 +629,14 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
584
629
|
&& String(verifyPayload.client || "") === "claude",
|
|
585
630
|
`verify=${String(verifyPayload.ok)} mode=${String(safeObject(verifyPayload.serverBinding).mode || "")} server=${String(safeObject(verifyPayload.serverBinding).detail || "")}`,
|
|
586
631
|
);
|
|
632
|
+
push(
|
|
633
|
+
"bot_verify_reports_linked_runner_routes",
|
|
634
|
+
intFromRaw(safeObject(verifyPayload.routeLinks).total, 0) >= 1
|
|
635
|
+
&& ensureArray(safeObject(verifyPayload.routeLinks).routes).some((route) => (
|
|
636
|
+
String(safeObject(route).routeName || "") === "telegram-monitor"
|
|
637
|
+
)),
|
|
638
|
+
`routes=${JSON.stringify(safeObject(verifyPayload.routeLinks))}`,
|
|
639
|
+
);
|
|
587
640
|
|
|
588
641
|
await runCLI({
|
|
589
642
|
cliPath,
|