metheus-governance-mcp-cli 0.2.70 → 0.2.72
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 +3 -2
- package/cli.mjs +1 -0
- package/lib/bot-commands.mjs +226 -185
- package/lib/selftest-bot-commands.mjs +45 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,8 +231,9 @@ Behavior:
|
|
|
231
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.
|
|
232
232
|
- 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
233
|
- 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
|
-
- `bot edit` without flags
|
|
235
|
-
-
|
|
234
|
+
- `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.
|
|
235
|
+
- if one server bot name maps to multiple server roles, `bot edit` keeps the Telegram env entry bound to the server identity and lets you review the local `role_profiles` for each detected role instead of forcing one role/profile UUID choice up front.
|
|
236
|
+
- In the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically. It no longer asks you to pick `approval / worker / review / monitor` or a server bot UUID first.
|
|
236
237
|
- `bot set-default` without flags starts a guided numbered flow: provider -> bot entry -> confirm default change.
|
|
237
238
|
- `bot verify` without flags starts a guided numbered flow: provider -> bot entry -> output format.
|
|
238
239
|
- `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
|
package/cli.mjs
CHANGED
|
@@ -3362,6 +3362,7 @@ function buildBotCommandDeps() {
|
|
|
3362
3362
|
loadProviderEnvConfig,
|
|
3363
3363
|
verifyLocalProviderToken,
|
|
3364
3364
|
loadBotRunnerConfig,
|
|
3365
|
+
saveBotRunnerConfig,
|
|
3365
3366
|
listServerBots: async ({ provider, baseURL, timeoutSeconds }) => {
|
|
3366
3367
|
const authFlowDeps = buildAuthFlowDeps();
|
|
3367
3368
|
const resolved = resolveCurrentAccessToken(authFlowDeps);
|
package/lib/bot-commands.mjs
CHANGED
|
@@ -147,7 +147,8 @@ function printBotUsage(deps) {
|
|
|
147
147
|
` - bot add without flags uses the shortest practical guided flow: provider -> token -> verify -> optional username fallback -> optional default bot.`,
|
|
148
148
|
` - in the normal Telegram path, bot add does not ask for a local bot key or a server role/profile choice.`,
|
|
149
149
|
` - server bot name/UUID is the source of truth; local key is auto-derived unless you intentionally override it with --bot-key.`,
|
|
150
|
-
` - bot edit without flags asks for provider, existing entry, then a numbered
|
|
150
|
+
` - bot edit without flags asks for provider, existing entry, then walks field-by-field in a numbered flow.`,
|
|
151
|
+
` - in the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically instead of asking you to pick a server role/profile UUID first.`,
|
|
151
152
|
` - bot set-default / bot verify / bot remove also support guided numbered selection when run without flags.`,
|
|
152
153
|
"",
|
|
153
154
|
].join("\n"),
|
|
@@ -594,58 +595,11 @@ function buildDerivedTelegramBotKey(parsedEnv, deps, preferredValues, { excludeK
|
|
|
594
595
|
|
|
595
596
|
async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps) {
|
|
596
597
|
let serverRoleAutoResolved = "";
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
||
|
|
602
|
-
|| hasOwnFlag(flags, "ai-permission-mode")
|
|
603
|
-
|| hasOwnFlag(flags, "reasoning-effort")
|
|
604
|
-
|| hasOwnFlag(flags, "ai-reasoning-effort");
|
|
605
|
-
const bindingAction = await promptKeepChangeClear(ui, "Server bot binding", {
|
|
606
|
-
allowClear: true,
|
|
607
|
-
defaultValue: current.serverBotID ? "keep" : "change",
|
|
608
|
-
});
|
|
609
|
-
if (bindingAction === "change") {
|
|
610
|
-
const serverBot = await chooseServerBot(
|
|
611
|
-
ui,
|
|
612
|
-
"telegram",
|
|
613
|
-
flags["base-url"] || deps.defaultSiteURL,
|
|
614
|
-
intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
615
|
-
deps,
|
|
616
|
-
{
|
|
617
|
-
preferredUsername: current.username,
|
|
618
|
-
preferredName: current.username,
|
|
619
|
-
},
|
|
620
|
-
);
|
|
621
|
-
if (serverBot.matchMode === "group") {
|
|
622
|
-
current.serverBotID = "";
|
|
623
|
-
current.__preferServerIdentity = true;
|
|
624
|
-
if (!String(flags["role-profile"] || "").trim()) {
|
|
625
|
-
current.roleProfile = "";
|
|
626
|
-
}
|
|
627
|
-
if (!aiFlagOverrides) {
|
|
628
|
-
current.client = "";
|
|
629
|
-
current.model = "";
|
|
630
|
-
current.permissionMode = "";
|
|
631
|
-
current.reasoningEffort = "";
|
|
632
|
-
}
|
|
633
|
-
serverRoleAutoResolved = "__server_role_group__";
|
|
634
|
-
process.stdout.write(
|
|
635
|
-
`Matched server Telegram bot "${serverBot.name || current.username || current.key}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Runtime will use the server role, so local role/AI fields can stay empty.\n`,
|
|
636
|
-
);
|
|
637
|
-
} else {
|
|
638
|
-
current.serverBotID = serverBot.botID;
|
|
639
|
-
current.__preferServerIdentity = true;
|
|
640
|
-
if (!current.roleProfile && serverBot.role) {
|
|
641
|
-
current.roleProfile = serverBot.role;
|
|
642
|
-
}
|
|
643
|
-
serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim();
|
|
644
|
-
applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
|
|
645
|
-
}
|
|
646
|
-
} else if (bindingAction === "clear") {
|
|
647
|
-
current.serverBotID = "";
|
|
648
|
-
current.__preferServerIdentity = false;
|
|
598
|
+
let groupedServerRoles = [];
|
|
599
|
+
let groupedServerName = "";
|
|
600
|
+
if (current.serverBotID || current.__preferServerIdentity) {
|
|
601
|
+
current.__preferServerIdentity = true;
|
|
602
|
+
serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
|
|
649
603
|
}
|
|
650
604
|
|
|
651
605
|
const usernameAction = await promptKeepChangeClear(ui, "Telegram username", {
|
|
@@ -683,7 +637,35 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
|
|
|
683
637
|
}
|
|
684
638
|
}
|
|
685
639
|
|
|
686
|
-
if (!
|
|
640
|
+
if (!current.serverBotID || current.__preferServerIdentity || usernameAction === "change" || tokenAction === "change") {
|
|
641
|
+
const serverBot = await autoResolveTelegramServerBot(current, flags, deps);
|
|
642
|
+
if (serverBot.matchMode === "group") {
|
|
643
|
+
current.serverBotID = "";
|
|
644
|
+
current.__preferServerIdentity = true;
|
|
645
|
+
current.roleProfile = "";
|
|
646
|
+
current.client = "";
|
|
647
|
+
current.model = "";
|
|
648
|
+
current.permissionMode = "";
|
|
649
|
+
current.reasoningEffort = "";
|
|
650
|
+
serverRoleAutoResolved = "__server_role_group__";
|
|
651
|
+
groupedServerRoles = preferredRoleSort(serverBot.roles);
|
|
652
|
+
groupedServerName = String(serverBot.name || current.username || current.key).trim();
|
|
653
|
+
process.stdout.write(
|
|
654
|
+
`Using server Telegram bot "${groupedServerName}" with roles: ${groupedServerRoles.join(", ")}. Runtime will use the server role automatically.\n`,
|
|
655
|
+
);
|
|
656
|
+
} else if (String(serverBot.botID || "").trim()) {
|
|
657
|
+
current.serverBotID = String(serverBot.botID || "").trim();
|
|
658
|
+
current.__preferServerIdentity = true;
|
|
659
|
+
current.roleProfile = String(serverBot.role || current.roleProfile || "").trim();
|
|
660
|
+
serverRoleAutoResolved = String(serverBot.role || current.roleProfile || "").trim() || "__server_binding__";
|
|
661
|
+
applyRoleProfileDefaults(current, resolveRoleProfileDefaults(current.roleProfile || serverBot.role, deps));
|
|
662
|
+
} else if (current.serverBotID) {
|
|
663
|
+
current.__preferServerIdentity = true;
|
|
664
|
+
serverRoleAutoResolved = String(current.roleProfile || "").trim() || "__server_binding__";
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (!current.serverBotID && !current.__preferServerIdentity) {
|
|
687
669
|
const roleAction = await promptKeepChangeClear(ui, "Role profile", {
|
|
688
670
|
allowClear: true,
|
|
689
671
|
defaultValue: current.roleProfile ? "keep" : "change",
|
|
@@ -696,7 +678,18 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
|
|
|
696
678
|
} else if (roleAction === "clear") {
|
|
697
679
|
current.roleProfile = "";
|
|
698
680
|
}
|
|
681
|
+
}
|
|
699
682
|
|
|
683
|
+
if (serverRoleAutoResolved === "__server_role_group__") {
|
|
684
|
+
await maybePromptGroupedServerRoleProfiles(
|
|
685
|
+
ui,
|
|
686
|
+
{
|
|
687
|
+
name: groupedServerName,
|
|
688
|
+
roles: groupedServerRoles,
|
|
689
|
+
},
|
|
690
|
+
deps,
|
|
691
|
+
);
|
|
692
|
+
} else {
|
|
700
693
|
const clientAction = await promptKeepChangeClear(ui, "AI client", {
|
|
701
694
|
allowClear: true,
|
|
702
695
|
defaultValue: current.client ? "keep" : "change",
|
|
@@ -747,21 +740,6 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
|
|
|
747
740
|
}
|
|
748
741
|
}
|
|
749
742
|
|
|
750
|
-
const botKeyAction = await promptKeepChangeClear(ui, "Local bot key", {
|
|
751
|
-
allowClear: false,
|
|
752
|
-
defaultValue: "keep",
|
|
753
|
-
});
|
|
754
|
-
if (botKeyAction === "change") {
|
|
755
|
-
const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
|
|
756
|
-
let nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", current.key), current.key);
|
|
757
|
-
const existing = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key).filter((key) => key !== current.key));
|
|
758
|
-
while (existing.has(nextKey)) {
|
|
759
|
-
process.stdout.write(`Telegram bot key "${nextKey}" already exists.\n`);
|
|
760
|
-
nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", `${nextKey}_2`), `${nextKey}_2`);
|
|
761
|
-
}
|
|
762
|
-
current.key = nextKey;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
743
|
const defaultChoice = await promptChoice(
|
|
766
744
|
ui,
|
|
767
745
|
"Default Telegram bot setting",
|
|
@@ -1326,6 +1304,144 @@ function applyRoleProfileDefaults(entry, defaults, { overwrite = false } = {}) {
|
|
|
1326
1304
|
}
|
|
1327
1305
|
}
|
|
1328
1306
|
|
|
1307
|
+
function preferredRoleSort(roles) {
|
|
1308
|
+
const order = ["monitor", "review", "worker", "approval"];
|
|
1309
|
+
return ensureArray(roles).slice().sort((left, right) => {
|
|
1310
|
+
const leftText = String(left || "").trim();
|
|
1311
|
+
const rightText = String(right || "").trim();
|
|
1312
|
+
const leftIndex = order.indexOf(leftText);
|
|
1313
|
+
const rightIndex = order.indexOf(rightText);
|
|
1314
|
+
if (leftIndex >= 0 && rightIndex >= 0) return leftIndex - rightIndex;
|
|
1315
|
+
if (leftIndex >= 0) return -1;
|
|
1316
|
+
if (rightIndex >= 0) return 1;
|
|
1317
|
+
return leftText.localeCompare(rightText);
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function currentRoleProfileState(roleName, deps) {
|
|
1322
|
+
const normalizedRole = requireDependency(deps, "normalizeRunnerRoleProfileName")(roleName || "");
|
|
1323
|
+
const defaults = resolveRoleProfileDefaults(normalizedRole, deps);
|
|
1324
|
+
const config = requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true });
|
|
1325
|
+
const profile = safeObject(safeObject(config.roleProfiles || {})[normalizedRole]);
|
|
1326
|
+
return {
|
|
1327
|
+
role: normalizedRole,
|
|
1328
|
+
client: requireDependency(deps, "normalizeLocalAIClientName")(profile.client || defaults.client || "", ""),
|
|
1329
|
+
model: String(profile.model || defaults.model || "").trim(),
|
|
1330
|
+
permissionMode: requireDependency(deps, "normalizeLocalAIPermissionMode")(
|
|
1331
|
+
profile.permission_mode || profile.permissionMode || defaults.permissionMode || "",
|
|
1332
|
+
"",
|
|
1333
|
+
),
|
|
1334
|
+
reasoningEffort: requireDependency(deps, "normalizeLocalAIReasoningEffort")(
|
|
1335
|
+
profile.reasoning_effort || profile.reasoningEffort || defaults.reasoningEffort || "",
|
|
1336
|
+
"",
|
|
1337
|
+
),
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function formatRoleProfileSummary(profile) {
|
|
1342
|
+
const current = safeObject(profile);
|
|
1343
|
+
return [
|
|
1344
|
+
current.client ? `client:${current.client}` : "client:(blank)",
|
|
1345
|
+
current.model ? `model:${current.model}` : "model:(blank)",
|
|
1346
|
+
current.permissionMode ? `permission:${current.permissionMode}` : "permission:(blank)",
|
|
1347
|
+
current.reasoningEffort ? `reasoning:${current.reasoningEffort}` : "reasoning:(blank)",
|
|
1348
|
+
].join(" | ");
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function persistRoleProfileState(profile, deps) {
|
|
1352
|
+
const current = safeObject(profile);
|
|
1353
|
+
const role = requireDependency(deps, "normalizeRunnerRoleProfileName")(current.role || "");
|
|
1354
|
+
if (!role) return null;
|
|
1355
|
+
const config = requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true });
|
|
1356
|
+
const nextRoleProfiles = {
|
|
1357
|
+
...safeObject(config.roleProfiles || {}),
|
|
1358
|
+
[role]: {
|
|
1359
|
+
client: String(current.client || "").trim(),
|
|
1360
|
+
model: String(current.model || "").trim(),
|
|
1361
|
+
permission_mode: String(current.permissionMode || "").trim(),
|
|
1362
|
+
reasoning_effort: String(current.reasoningEffort || "").trim(),
|
|
1363
|
+
},
|
|
1364
|
+
};
|
|
1365
|
+
const nextConfig = {
|
|
1366
|
+
...config,
|
|
1367
|
+
roleProfiles: nextRoleProfiles,
|
|
1368
|
+
};
|
|
1369
|
+
return requireDependency(deps, "saveBotRunnerConfig")(nextConfig, config.filePath);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
async function promptRoleExecutionProfile(ui, roleName, deps) {
|
|
1373
|
+
const current = currentRoleProfileState(roleName, deps);
|
|
1374
|
+
const action = await promptChoice(
|
|
1375
|
+
ui,
|
|
1376
|
+
`Role "${current.role}" local execution profile`,
|
|
1377
|
+
[
|
|
1378
|
+
{
|
|
1379
|
+
value: "keep",
|
|
1380
|
+
label: "Keep current settings",
|
|
1381
|
+
description: formatRoleProfileSummary(current),
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
value: "edit",
|
|
1385
|
+
label: "Edit settings",
|
|
1386
|
+
description: "change AI client, model, permission, and reasoning",
|
|
1387
|
+
},
|
|
1388
|
+
],
|
|
1389
|
+
{ defaultIndex: 0 },
|
|
1390
|
+
);
|
|
1391
|
+
if (action?.value !== "edit") {
|
|
1392
|
+
return { changed: false, filePath: "" };
|
|
1393
|
+
}
|
|
1394
|
+
current.client = requireDependency(deps, "normalizeLocalAIClientName")(
|
|
1395
|
+
await promptAIClient(ui, deps, current.client),
|
|
1396
|
+
"",
|
|
1397
|
+
);
|
|
1398
|
+
current.model = await promptLine(ui, `AI model for role "${current.role}"`, current.model);
|
|
1399
|
+
current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
|
|
1400
|
+
await promptPermissionMode(ui, current.permissionMode),
|
|
1401
|
+
"",
|
|
1402
|
+
);
|
|
1403
|
+
current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
|
|
1404
|
+
await promptReasoningEffort(ui, current.reasoningEffort),
|
|
1405
|
+
"",
|
|
1406
|
+
);
|
|
1407
|
+
const filePath = persistRoleProfileState(current, deps);
|
|
1408
|
+
if (filePath) {
|
|
1409
|
+
process.stdout.write(`Saved local execution profile for role "${current.role}" to ${filePath}\n`);
|
|
1410
|
+
}
|
|
1411
|
+
return { changed: true, filePath };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
|
|
1415
|
+
const roles = preferredRoleSort(ensureArray(serverBot?.roles).filter(Boolean));
|
|
1416
|
+
if (roles.length <= 1) {
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
const editChoice = await promptChoice(
|
|
1420
|
+
ui,
|
|
1421
|
+
`Server bot "${String(serverBot?.name || "").trim() || "telegram"}" uses multiple roles. What should happen to the local execution settings?`,
|
|
1422
|
+
[
|
|
1423
|
+
{
|
|
1424
|
+
value: "keep",
|
|
1425
|
+
label: "Keep current role settings",
|
|
1426
|
+
description: "use the existing role_profiles defaults as-is",
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
value: "review",
|
|
1430
|
+
label: "Review role settings",
|
|
1431
|
+
description: "check each role and change AI client/model/permission/reasoning when needed",
|
|
1432
|
+
},
|
|
1433
|
+
],
|
|
1434
|
+
{ defaultIndex: 0 },
|
|
1435
|
+
);
|
|
1436
|
+
if (editChoice?.value !== "review") {
|
|
1437
|
+
return false;
|
|
1438
|
+
}
|
|
1439
|
+
for (const role of roles) {
|
|
1440
|
+
await promptRoleExecutionProfile(ui, role, deps);
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1329
1445
|
function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
|
|
1330
1446
|
return {
|
|
1331
1447
|
ok: true,
|
|
@@ -1339,6 +1455,46 @@ async function verifyTelegramTokenCandidate(provider, token, apiBaseURL, timeout
|
|
|
1339
1455
|
return verifier(provider, buildTemporaryTelegramEnvConfig({ token, apiBaseURL }), timeoutSeconds);
|
|
1340
1456
|
}
|
|
1341
1457
|
|
|
1458
|
+
async function autoResolveTelegramServerBot(current, flags, deps) {
|
|
1459
|
+
const existingBotID = String(current?.serverBotID || "").trim();
|
|
1460
|
+
const preferredIdentity = firstNonEmptyString([
|
|
1461
|
+
current?.username,
|
|
1462
|
+
current?.key,
|
|
1463
|
+
]);
|
|
1464
|
+
if (!preferredIdentity) {
|
|
1465
|
+
return {
|
|
1466
|
+
botID: existingBotID,
|
|
1467
|
+
role: String(current?.roleProfile || "").trim(),
|
|
1468
|
+
name: "",
|
|
1469
|
+
roles: [],
|
|
1470
|
+
matchMode: existingBotID ? "existing" : "blank",
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
try {
|
|
1474
|
+
return await resolveServerBotForNonInteractive(
|
|
1475
|
+
"telegram",
|
|
1476
|
+
{
|
|
1477
|
+
"bot-name": preferredIdentity,
|
|
1478
|
+
"base-url": flags["base-url"] || deps.defaultSiteURL,
|
|
1479
|
+
"timeout-seconds": intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
1480
|
+
},
|
|
1481
|
+
deps,
|
|
1482
|
+
{
|
|
1483
|
+
preferredUsername: preferredIdentity,
|
|
1484
|
+
preferredName: preferredIdentity,
|
|
1485
|
+
},
|
|
1486
|
+
);
|
|
1487
|
+
} catch {
|
|
1488
|
+
return {
|
|
1489
|
+
botID: existingBotID,
|
|
1490
|
+
role: String(current?.roleProfile || "").trim(),
|
|
1491
|
+
name: "",
|
|
1492
|
+
roles: [],
|
|
1493
|
+
matchMode: existingBotID ? "existing" : "blank",
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1342
1498
|
async function addTelegramBot(ui, flags, deps) {
|
|
1343
1499
|
const state = loadProviderEnvState("telegram", deps);
|
|
1344
1500
|
const parsed = { ...state.parsed };
|
|
@@ -1624,122 +1780,7 @@ async function editTelegramBot(ui, flags, deps) {
|
|
|
1624
1780
|
saveTelegramBotEdit(parsed, selected, current, deps);
|
|
1625
1781
|
return;
|
|
1626
1782
|
}
|
|
1627
|
-
|
|
1628
|
-
ui,
|
|
1629
|
-
`How do you want to edit Telegram bot "${current.key}"?`,
|
|
1630
|
-
[
|
|
1631
|
-
{ value: "guided", label: "Guided edit (Recommended)", description: "Step-by-step prompts with numbered choices" },
|
|
1632
|
-
{ value: "field_menu", label: "Field menu", description: "Choose one field at a time until save" },
|
|
1633
|
-
],
|
|
1634
|
-
{ defaultIndex: 0 },
|
|
1635
|
-
);
|
|
1636
|
-
if (editMode?.value === "guided") {
|
|
1637
|
-
await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
while (true) {
|
|
1641
|
-
const choice = await promptChoice(
|
|
1642
|
-
ui,
|
|
1643
|
-
`Edit Telegram bot "${current.key}"`,
|
|
1644
|
-
[
|
|
1645
|
-
{ value: "server_bot_id", label: "Server bot UUID", description: current.serverBotID || "-" },
|
|
1646
|
-
{ value: "username", label: "Telegram username", description: current.username ? `@${current.username}` : "-" },
|
|
1647
|
-
{ value: "token", label: "Telegram token", description: current.token ? maskSecret(current.token) : "-" },
|
|
1648
|
-
{ value: "role_profile", label: "Role profile", description: current.roleProfile || "-" },
|
|
1649
|
-
{ value: "client", label: "AI client", description: current.client || "-" },
|
|
1650
|
-
{ value: "model", label: "AI model", description: current.model || "-" },
|
|
1651
|
-
{ value: "permission", label: "AI permission mode", description: current.permissionMode || "-" },
|
|
1652
|
-
{ value: "reasoning", label: "AI reasoning effort", description: current.reasoningEffort || "-" },
|
|
1653
|
-
{ value: "bot_key", label: "Local bot key", description: current.key },
|
|
1654
|
-
{ value: "set_default", label: "Set as default bot", description: boolFromRaw(flags.default, false) || String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === current.key ? "yes" : "no" },
|
|
1655
|
-
{ value: "save", label: "Save changes" },
|
|
1656
|
-
],
|
|
1657
|
-
{ defaultIndex: 0 },
|
|
1658
|
-
);
|
|
1659
|
-
if (!choice) return;
|
|
1660
|
-
if (choice.value === "save") {
|
|
1661
|
-
saveTelegramBotEdit(parsed, selected, current, deps);
|
|
1662
|
-
return;
|
|
1663
|
-
}
|
|
1664
|
-
if (choice.value === "server_bot_id") {
|
|
1665
|
-
const serverBot = await chooseServerBot(
|
|
1666
|
-
ui,
|
|
1667
|
-
"telegram",
|
|
1668
|
-
flags["base-url"] || deps.defaultSiteURL,
|
|
1669
|
-
intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
1670
|
-
deps,
|
|
1671
|
-
{
|
|
1672
|
-
preferredUsername: current.username,
|
|
1673
|
-
preferredName: current.username,
|
|
1674
|
-
},
|
|
1675
|
-
);
|
|
1676
|
-
if (serverBot.matchMode === "group") {
|
|
1677
|
-
current.serverBotID = "";
|
|
1678
|
-
process.stdout.write(
|
|
1679
|
-
`Matched server Telegram bot "${serverBot.name || current.username || current.key}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Keep role/AI fields empty if you want runtime role-based resolution.\n`,
|
|
1680
|
-
);
|
|
1681
|
-
} else {
|
|
1682
|
-
current.serverBotID = serverBot.botID;
|
|
1683
|
-
if (!current.roleProfile && serverBot.role) {
|
|
1684
|
-
current.roleProfile = serverBot.role;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
} else if (choice.value === "username") {
|
|
1688
|
-
current.username = requireDependency(deps, "normalizeTelegramBotUsername")(
|
|
1689
|
-
await promptLine(ui, "Telegram username (without @)", current.username),
|
|
1690
|
-
"",
|
|
1691
|
-
);
|
|
1692
|
-
} else if (choice.value === "token") {
|
|
1693
|
-
current.token = await promptRequiredLine(ui, "Telegram bot token", current.token);
|
|
1694
|
-
if (await promptYesNo(ui, "Verify updated token now?", true)) {
|
|
1695
|
-
const verifyResult = await verifyTelegramTokenCandidate(
|
|
1696
|
-
"telegram",
|
|
1697
|
-
current.token,
|
|
1698
|
-
String(parsed.TELEGRAM_API_BASE_URL || "").trim(),
|
|
1699
|
-
intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
1700
|
-
deps,
|
|
1701
|
-
);
|
|
1702
|
-
process.stdout.write(`Verify: ${verifyResult.ok ? "OK" : "FAIL"}${verifyResult.detail ? ` - ${verifyResult.detail}` : ""}\n`);
|
|
1703
|
-
const maybeUsername = extractVerifiedTelegramUsername(verifyResult.detail);
|
|
1704
|
-
if (verifyResult.ok && maybeUsername && !current.username) {
|
|
1705
|
-
current.username = maybeUsername;
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
} else if (choice.value === "role_profile") {
|
|
1709
|
-
current.roleProfile = requireDependency(deps, "normalizeRunnerRoleProfileName")(
|
|
1710
|
-
await promptTelegramRoleProfile(ui, deps, current.roleProfile),
|
|
1711
|
-
);
|
|
1712
|
-
} else if (choice.value === "client") {
|
|
1713
|
-
current.client = requireDependency(deps, "normalizeLocalAIClientName")(
|
|
1714
|
-
await promptAIClient(ui, deps, current.client),
|
|
1715
|
-
"",
|
|
1716
|
-
);
|
|
1717
|
-
} else if (choice.value === "model") {
|
|
1718
|
-
current.model = await promptLine(ui, "AI model", current.model);
|
|
1719
|
-
} else if (choice.value === "permission") {
|
|
1720
|
-
current.permissionMode = requireDependency(deps, "normalizeLocalAIPermissionMode")(
|
|
1721
|
-
await promptPermissionMode(ui, current.permissionMode),
|
|
1722
|
-
"",
|
|
1723
|
-
);
|
|
1724
|
-
} else if (choice.value === "reasoning") {
|
|
1725
|
-
current.reasoningEffort = requireDependency(deps, "normalizeLocalAIReasoningEffort")(
|
|
1726
|
-
await promptReasoningEffort(ui, current.reasoningEffort),
|
|
1727
|
-
"",
|
|
1728
|
-
);
|
|
1729
|
-
} else if (choice.value === "bot_key") {
|
|
1730
|
-
const normalizeBotKey = requireDependency(deps, "normalizeTelegramBotEnvKey");
|
|
1731
|
-
let nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", current.key), current.key);
|
|
1732
|
-
const existing = new Set(telegramEntriesForDisplay(parsed, deps).map((entry) => entry.key).filter((key) => key !== current.key));
|
|
1733
|
-
while (existing.has(nextKey)) {
|
|
1734
|
-
process.stdout.write(`Telegram bot key "${nextKey}" already exists.\n`);
|
|
1735
|
-
nextKey = normalizeBotKey(await promptRequiredLine(ui, "Local bot key", `${nextKey}_2`), `${nextKey}_2`);
|
|
1736
|
-
}
|
|
1737
|
-
current.key = nextKey;
|
|
1738
|
-
} else if (choice.value === "set_default") {
|
|
1739
|
-
parsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
|
|
1740
|
-
process.stdout.write(`Default Telegram bot will be "${current.key}".\n`);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1783
|
+
await editTelegramBotGuided(ui, parsed, selected, current, flags, deps);
|
|
1743
1784
|
}
|
|
1744
1785
|
|
|
1745
1786
|
async function removeTelegramBot(ui, deps) {
|
|
@@ -341,6 +341,51 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
341
341
|
&& String(groupedState.TELEGRAM_BOT_RYOAI_BOT_AI_PERMISSION_MODE || "") === "",
|
|
342
342
|
`username=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_USERNAME || "")} server_bot_id=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_SERVER_BOT_ID || "")} role=${String(groupedState.TELEGRAM_BOT_RYOAI_BOT_ROLE_PROFILE || "")}`,
|
|
343
343
|
);
|
|
344
|
+
|
|
345
|
+
await runCLI({
|
|
346
|
+
cliPath,
|
|
347
|
+
args: [
|
|
348
|
+
"bot", "edit",
|
|
349
|
+
"--base-url", `http://127.0.0.1:${groupedMock.port}`,
|
|
350
|
+
"--timeout-seconds", "5",
|
|
351
|
+
],
|
|
352
|
+
env: {
|
|
353
|
+
...env,
|
|
354
|
+
METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
|
|
355
|
+
"1", // provider: telegram
|
|
356
|
+
"2", // bot entry: ryoai_bot
|
|
357
|
+
"1", // keep username
|
|
358
|
+
"1", // keep token
|
|
359
|
+
"2", // review grouped server role settings
|
|
360
|
+
"1", // monitor: keep current role profile settings
|
|
361
|
+
"1", // review: keep current role profile settings
|
|
362
|
+
"2", // worker: edit settings
|
|
363
|
+
"3", // worker AI client: claude
|
|
364
|
+
"worker-sonnet-4",
|
|
365
|
+
"4", // worker permission: danger_full_access
|
|
366
|
+
"4", // worker reasoning: high
|
|
367
|
+
"2", // approval: edit settings
|
|
368
|
+
"4", // approval AI client: gemini
|
|
369
|
+
"approval-pro-2",
|
|
370
|
+
"4", // approval permission: danger_full_access
|
|
371
|
+
"4", // approval reasoning: high
|
|
372
|
+
"1", // keep current default setting
|
|
373
|
+
"y", // save
|
|
374
|
+
]),
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
const groupedRunnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
|
|
378
|
+
const groupedRunnerConfig = readJSON(fs.readFileSync(groupedRunnerConfigPath, "utf8"));
|
|
379
|
+
push(
|
|
380
|
+
"bot_edit_grouped_server_roles_updates_role_profiles",
|
|
381
|
+
String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).client || "") === "claude"
|
|
382
|
+
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).model || "") === "worker-sonnet-4"
|
|
383
|
+
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).permission_mode || "") === "danger_full_access"
|
|
384
|
+
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).reasoning_effort || "") === "high"
|
|
385
|
+
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).client || "") === "gemini"
|
|
386
|
+
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).model || "") === "approval-pro-2",
|
|
387
|
+
`worker=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval))}`,
|
|
388
|
+
);
|
|
344
389
|
} finally {
|
|
345
390
|
await groupedMock.close();
|
|
346
391
|
await runCLI({
|
|
@@ -381,11 +426,8 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
381
426
|
METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
|
|
382
427
|
"1", // provider: telegram
|
|
383
428
|
"1", // bot entry: @monitorselftestbot
|
|
384
|
-
"1", // edit mode: guided
|
|
385
|
-
"1", // keep server bot binding
|
|
386
429
|
"1", // keep username
|
|
387
430
|
"1", // keep token
|
|
388
|
-
"1", // keep role profile
|
|
389
431
|
"2", // change AI client
|
|
390
432
|
"4", // gemini
|
|
391
433
|
"2", // change AI model
|