appback-remoteagent 0.13.4 → 0.13.6
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/dist/bot.js +67 -4
- package/dist/config.js +1 -1
- package/dist/index.js +2 -63
- package/dist/secret-helper.js +23 -5
- package/dist/services/agent-memory-service.js +39 -2
- package/dist/telegram-command-menu.js +71 -0
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -9,6 +9,7 @@ import { config } from "./config.js";
|
|
|
9
9
|
import { ProviderSetupService } from "./services/provider-setup-service.js";
|
|
10
10
|
import { RemoteShellService } from "./services/remote-shell-service.js";
|
|
11
11
|
import { AgentMemoryService } from "./services/agent-memory-service.js";
|
|
12
|
+
import { deleteTelegramCommandMenu, setTelegramCommandMenu } from "./telegram-command-menu.js";
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
const HELP_TEXT = [
|
|
14
15
|
"Commands:",
|
|
@@ -24,7 +25,7 @@ const HELP_TEXT = [
|
|
|
24
25
|
"/stop",
|
|
25
26
|
"/sandbox codex <read-only|workspace-write|danger-full-access>",
|
|
26
27
|
"/status",
|
|
27
|
-
"/option [retry <count>|timeout <seconds>|intent <count>]",
|
|
28
|
+
"/option [retry <count>|timeout <seconds>|intent <count>|command-menu <on|off|refresh>]",
|
|
28
29
|
"/state [clear|note <text>]",
|
|
29
30
|
"/artifacts list|cleanup <days>",
|
|
30
31
|
"/secret set|list|remove",
|
|
@@ -484,8 +485,8 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
484
485
|
await reply(ctx, formatRuntimeOptions());
|
|
485
486
|
return;
|
|
486
487
|
}
|
|
487
|
-
if (option !== "retry" && option !== "timeout" && option !== "intent") {
|
|
488
|
-
await reply(ctx, "Usage: `/option retry <count>`, `/option timeout <seconds>`, or `/option
|
|
488
|
+
if (option !== "retry" && option !== "timeout" && option !== "intent" && option !== "command-menu") {
|
|
489
|
+
await reply(ctx, "Usage: `/option retry <count>`, `/option timeout <seconds>`, `/option intent <count>`, or `/option command-menu <on|off|refresh>`\n\n`retry` controls automatic continuation turns. `timeout` controls one provider execution limit. `intent` controls retries for untagged intent-only provider replies. `command-menu` controls Telegram slash-command autocomplete for all configured bots.", {
|
|
489
490
|
parse_mode: "Markdown",
|
|
490
491
|
});
|
|
491
492
|
return;
|
|
@@ -495,12 +496,39 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
495
496
|
? `Current automatic continuation retry limit: ${formatRetryLimit(config.telegramAutoProgressMaxTurns)}\n\nUsage: \`/option retry <count>\``
|
|
496
497
|
: option === "intent"
|
|
497
498
|
? `Current untagged intent retry limit: ${formatRetryLimit(config.telegramUntaggedIntentRetries)}\n\nUsage: \`/option intent <count>\``
|
|
498
|
-
:
|
|
499
|
+
: option === "command-menu"
|
|
500
|
+
? `Current Telegram command menu: ${config.telegramCommandMenuEnabled ? "on" : "off"}\n\nUsage: \`/option command-menu on\`, \`/option command-menu off\`, or \`/option command-menu refresh\``
|
|
501
|
+
: `Current provider execution timeout: ${formatTimeoutSeconds(config.commandTimeoutMs)}\n\nUsage: \`/option timeout <seconds>\``;
|
|
499
502
|
await reply(ctx, current, {
|
|
500
503
|
parse_mode: "Markdown",
|
|
501
504
|
});
|
|
502
505
|
return;
|
|
503
506
|
}
|
|
507
|
+
if (option === "command-menu") {
|
|
508
|
+
const action = value.toLowerCase();
|
|
509
|
+
if (!["on", "off", "refresh"].includes(action)) {
|
|
510
|
+
await reply(ctx, "Invalid command-menu option. Use `/option command-menu on`, `/option command-menu off`, or `/option command-menu refresh`.", {
|
|
511
|
+
parse_mode: "Markdown",
|
|
512
|
+
});
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const enabled = action === "refresh" ? config.telegramCommandMenuEnabled : action === "on";
|
|
516
|
+
const result = await applyTelegramCommandMenuOption(enabled);
|
|
517
|
+
if (action !== "refresh") {
|
|
518
|
+
config.telegramCommandMenuEnabled = enabled;
|
|
519
|
+
await upsertInstalledEnvValue("TELEGRAM_COMMAND_MENU_ENABLED", String(enabled));
|
|
520
|
+
}
|
|
521
|
+
await bridge.logSystem(botId, chatId, `Runtime option TELEGRAM_COMMAND_MENU_ENABLED ${action === "refresh" ? "refreshed" : `set to ${enabled}`}. ${result.summary}`);
|
|
522
|
+
await reply(ctx, [
|
|
523
|
+
action === "refresh"
|
|
524
|
+
? `Refreshed Telegram command menu for configured bots.`
|
|
525
|
+
: `Set Telegram command menu to ${enabled ? "on" : "off"}.`,
|
|
526
|
+
"",
|
|
527
|
+
result.summary,
|
|
528
|
+
action === "refresh" ? "" : `Saved: TELEGRAM_COMMAND_MENU_ENABLED=${enabled}`,
|
|
529
|
+
].filter(Boolean).join("\n"));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
504
532
|
const parsed = Number.parseInt(value, 10);
|
|
505
533
|
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== value.trim()) {
|
|
506
534
|
await reply(ctx, option === "retry"
|
|
@@ -1571,6 +1599,10 @@ function formatSecretHelp() {
|
|
|
1571
1599
|
"```text",
|
|
1572
1600
|
"node \"$REMOTEAGENT_SECRET_BIN\" get <KEY>",
|
|
1573
1601
|
"```",
|
|
1602
|
+
"If an agent obtains a new secret value such as an OAuth refresh token, it can delegate storage without printing the value:",
|
|
1603
|
+
"```text",
|
|
1604
|
+
"printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>",
|
|
1605
|
+
"```",
|
|
1574
1606
|
].join("\n");
|
|
1575
1607
|
}
|
|
1576
1608
|
function formatRuntimeOptions() {
|
|
@@ -1579,14 +1611,17 @@ function formatRuntimeOptions() {
|
|
|
1579
1611
|
`- retry: ${formatRetryLimit(config.telegramAutoProgressMaxTurns)} (TELEGRAM_AUTO_PROGRESS_MAX_TURNS)`,
|
|
1580
1612
|
`- timeout: ${formatTimeoutSeconds(config.commandTimeoutMs)} (COMMAND_TIMEOUT_MS)`,
|
|
1581
1613
|
`- intent: ${formatRetryLimit(config.telegramUntaggedIntentRetries)} (TELEGRAM_UNTAGGED_INTENT_RETRIES)`,
|
|
1614
|
+
`- command-menu: ${config.telegramCommandMenuEnabled ? "on" : "off"} (TELEGRAM_COMMAND_MENU_ENABLED)`,
|
|
1582
1615
|
"",
|
|
1583
1616
|
"Usage:",
|
|
1584
1617
|
"/option retry <count>",
|
|
1585
1618
|
"/option timeout <seconds>",
|
|
1586
1619
|
"/option intent <count>",
|
|
1620
|
+
"/option command-menu <on|off|refresh>",
|
|
1587
1621
|
"",
|
|
1588
1622
|
"`retry 0` disables the automatic continuation limit.",
|
|
1589
1623
|
"`intent 0` disables untagged intent-only response retries.",
|
|
1624
|
+
"`command-menu refresh` reapplies Telegram slash-command autocomplete without changing the saved option.",
|
|
1590
1625
|
].join("\n");
|
|
1591
1626
|
}
|
|
1592
1627
|
function formatRetryLimit(value) {
|
|
@@ -1616,6 +1651,34 @@ async function upsertInstalledEnvValue(key, value) {
|
|
|
1616
1651
|
}
|
|
1617
1652
|
await fs.writeFile(envPath, `${next.join("\n").replace(/\n+$/u, "")}\n`, "utf8");
|
|
1618
1653
|
}
|
|
1654
|
+
async function applyTelegramCommandMenuOption(enabled) {
|
|
1655
|
+
let applied = 0;
|
|
1656
|
+
const failures = [];
|
|
1657
|
+
for (const token of config.telegramBotTokens) {
|
|
1658
|
+
const botLabel = await resolveTelegramBotLabel(token).catch(() => "unknown-bot");
|
|
1659
|
+
try {
|
|
1660
|
+
if (enabled) {
|
|
1661
|
+
await setTelegramCommandMenu(token);
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
await deleteTelegramCommandMenu(token);
|
|
1665
|
+
}
|
|
1666
|
+
applied += 1;
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
failures.push(`${botLabel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const parts = [`applied=${applied}/${config.telegramBotTokens.length}`];
|
|
1673
|
+
if (failures.length > 0) {
|
|
1674
|
+
parts.push(`failed=${failures.length}`, ...failures.map((failure) => `- ${failure}`));
|
|
1675
|
+
}
|
|
1676
|
+
return { summary: parts.join("\n") };
|
|
1677
|
+
}
|
|
1678
|
+
async function resolveTelegramBotLabel(token) {
|
|
1679
|
+
const payload = await callTelegramApi(token, "getMe", {});
|
|
1680
|
+
return payload.username ? `@${payload.username}` : String(payload.id);
|
|
1681
|
+
}
|
|
1619
1682
|
function chunkMessage(text, size) {
|
|
1620
1683
|
if (text.length <= size) {
|
|
1621
1684
|
return [text];
|
package/dist/config.js
CHANGED
|
@@ -130,7 +130,7 @@ const telegramBotUsernames = readTelegramBotUsernames();
|
|
|
130
130
|
export const config = {
|
|
131
131
|
telegramBotTokens,
|
|
132
132
|
telegramBotUsernames,
|
|
133
|
-
telegramCommandMenuEnabled: readBoolean("TELEGRAM_COMMAND_MENU_ENABLED",
|
|
133
|
+
telegramCommandMenuEnabled: readBoolean("TELEGRAM_COMMAND_MENU_ENABLED", true),
|
|
134
134
|
telegramPollingBackoffMinMs: readTimeout("TELEGRAM_POLLING_BACKOFF_MIN_MS", 60_000),
|
|
135
135
|
telegramPollingBackoffMaxMs: readTimeout("TELEGRAM_POLLING_BACKOFF_MAX_MS", 900_000),
|
|
136
136
|
telegramPollingMaxConcurrency: readTimeout("TELEGRAM_POLLING_MAX_CONCURRENCY", 3),
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { BotManagementService } from "./services/bot-management-service.js";
|
|
|
15
15
|
import { LocalUiService } from "./services/local-ui-service.js";
|
|
16
16
|
import { AgentMemoryService } from "./services/agent-memory-service.js";
|
|
17
17
|
import { terminateAllSpawnedExecutions } from "./adapters/windows-shell.js";
|
|
18
|
+
import { setTelegramCommandMenu } from "./telegram-command-menu.js";
|
|
18
19
|
const execFileAsync = promisify(execFile);
|
|
19
20
|
const TELEGRAM_GET_UPDATES_HTTP_TIMEOUT_SECONDS = 30;
|
|
20
21
|
const TELEGRAM_GET_UPDATES_CURL_TIMEOUT_SECONDS = 60;
|
|
@@ -107,23 +108,6 @@ function startArtifactCleanupSchedule(memoryService) {
|
|
|
107
108
|
interval.unref();
|
|
108
109
|
}
|
|
109
110
|
async function configureTelegramCommandMenu(bot) {
|
|
110
|
-
const commands = [
|
|
111
|
-
{ command: "start", description: "Start a new Codex or Claude session" },
|
|
112
|
-
{ command: "list", description: "List sessions" },
|
|
113
|
-
{ command: "switch", description: "Switch to a session" },
|
|
114
|
-
{ command: "status", description: "Show current session status" },
|
|
115
|
-
{ command: "state", description: "Show or edit session state notes" },
|
|
116
|
-
{ command: "option", description: "Show or change runtime options" },
|
|
117
|
-
{ command: "model", description: "Show or change provider model" },
|
|
118
|
-
{ command: "stop", description: "Stop active work and clear queued messages" },
|
|
119
|
-
{ command: "batch", description: "Collect and send a multi-message batch" },
|
|
120
|
-
{ command: "bots", description: "List configured Telegram bots" },
|
|
121
|
-
{ command: "bot", description: "Manage Telegram bots" },
|
|
122
|
-
{ command: "install", description: "Install or update Codex or Claude" },
|
|
123
|
-
{ command: "login", description: "Run provider login flow" },
|
|
124
|
-
{ command: "reset", description: "Clear this chat binding" },
|
|
125
|
-
{ command: "help", description: "Show command help" },
|
|
126
|
-
];
|
|
127
111
|
const token = bot.token;
|
|
128
112
|
if (!token) {
|
|
129
113
|
throw new Error("Telegram bot token is unavailable for command menu registration.");
|
|
@@ -131,7 +115,7 @@ async function configureTelegramCommandMenu(bot) {
|
|
|
131
115
|
let lastError;
|
|
132
116
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
133
117
|
try {
|
|
134
|
-
await
|
|
118
|
+
await setTelegramCommandMenu(token);
|
|
135
119
|
return;
|
|
136
120
|
}
|
|
137
121
|
catch (error) {
|
|
@@ -143,51 +127,6 @@ async function configureTelegramCommandMenu(bot) {
|
|
|
143
127
|
}
|
|
144
128
|
throw lastError;
|
|
145
129
|
}
|
|
146
|
-
async function setTelegramCommandsViaCurl(token, commands) {
|
|
147
|
-
const url = `https://api.telegram.org/bot${token}/setMyCommands`;
|
|
148
|
-
const payload = JSON.stringify({ commands });
|
|
149
|
-
let stdout;
|
|
150
|
-
let stderr;
|
|
151
|
-
try {
|
|
152
|
-
const result = await execFileAsync("curl", [
|
|
153
|
-
"-sS",
|
|
154
|
-
"-4",
|
|
155
|
-
"--max-time",
|
|
156
|
-
"20",
|
|
157
|
-
"-H",
|
|
158
|
-
"Content-Type: application/json",
|
|
159
|
-
"-d",
|
|
160
|
-
payload,
|
|
161
|
-
url,
|
|
162
|
-
]);
|
|
163
|
-
stdout = result.stdout;
|
|
164
|
-
stderr = result.stderr;
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
throw new Error(`Telegram setMyCommands curl failed: ${formatCurlError(error)}`);
|
|
168
|
-
}
|
|
169
|
-
if (stderr?.trim()) {
|
|
170
|
-
console.error(`curl stderr for setMyCommands: ${stderr.trim()}`);
|
|
171
|
-
}
|
|
172
|
-
const parsed = JSON.parse(stdout);
|
|
173
|
-
if (!parsed.ok) {
|
|
174
|
-
throw new Error(parsed.description || "Telegram setMyCommands failed.");
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function formatCurlError(error) {
|
|
178
|
-
if (!(error instanceof Error)) {
|
|
179
|
-
return String(error);
|
|
180
|
-
}
|
|
181
|
-
const code = typeof error === "object" && error !== null && "code" in error
|
|
182
|
-
? String(error.code ?? "")
|
|
183
|
-
: "";
|
|
184
|
-
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
185
|
-
? String(error.stderr ?? "").trim()
|
|
186
|
-
: "";
|
|
187
|
-
return [code ? `code=${code}` : undefined, stderr || error.message]
|
|
188
|
-
.filter(Boolean)
|
|
189
|
-
.join(" ");
|
|
190
|
-
}
|
|
191
130
|
main().catch((error) => {
|
|
192
131
|
console.error("RemoteAgent fatal error:", error);
|
|
193
132
|
releaseProcessLockSync();
|
package/dist/secret-helper.js
CHANGED
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
import { readSecretValue } from "./services/agent-memory-service.js";
|
|
6
|
-
function main() {
|
|
5
|
+
import { readSecretValue, writeSecretValue } from "./services/agent-memory-service.js";
|
|
6
|
+
async function main() {
|
|
7
7
|
const [command, key] = process.argv.slice(2);
|
|
8
|
-
if (command
|
|
9
|
-
process.stderr.write("Usage: secret-helper get <KEY>\n");
|
|
8
|
+
if (!["get", "set"].includes(command ?? "") || !key) {
|
|
9
|
+
process.stderr.write("Usage: secret-helper get <KEY>\n secret-helper set <KEY> # reads value from stdin\n");
|
|
10
10
|
process.exitCode = 2;
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
const dataDir = process.env.REMOTEAGENT_DATA_DIR?.trim()
|
|
14
14
|
|| process.env.DATA_DIR?.trim()
|
|
15
15
|
|| path.join(os.homedir(), ".remoteagent");
|
|
16
|
+
if (command === "set") {
|
|
17
|
+
const value = await readStdin();
|
|
18
|
+
if (!value) {
|
|
19
|
+
process.stderr.write("Secret value was empty.\n");
|
|
20
|
+
process.exitCode = 2;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
writeSecretValue(dataDir, key.trim(), value);
|
|
24
|
+
process.stdout.write(`Stored secret: ${key.trim()}\n`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
16
27
|
const value = readSecretValue(dataDir, key.trim());
|
|
17
28
|
if (!value) {
|
|
18
29
|
process.stderr.write(`Secret was not found: ${key}\n`);
|
|
@@ -21,4 +32,11 @@ function main() {
|
|
|
21
32
|
}
|
|
22
33
|
process.stdout.write(value);
|
|
23
34
|
}
|
|
24
|
-
|
|
35
|
+
async function readStdin() {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
for await (const chunk of process.stdin) {
|
|
38
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
39
|
+
}
|
|
40
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
41
|
+
}
|
|
42
|
+
void main();
|
|
@@ -338,8 +338,16 @@ export class AgentMemoryService {
|
|
|
338
338
|
? ["Document index:", ...docs.map((doc) => `- ${doc.keyword}: ${doc.targetPath}${doc.note ? ` (${doc.note})` : ""}`)].join("\n")
|
|
339
339
|
: undefined,
|
|
340
340
|
secrets.length > 0
|
|
341
|
-
? [
|
|
342
|
-
|
|
341
|
+
? [
|
|
342
|
+
"Secret keys available through `node \"$REMOTEAGENT_SECRET_BIN\" get <KEY>`:",
|
|
343
|
+
...secrets.map((key) => `- ${key}`),
|
|
344
|
+
"If you generate a new secret value such as an OAuth refresh token, store it without printing it:",
|
|
345
|
+
"`printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>`",
|
|
346
|
+
].join("\n")
|
|
347
|
+
: [
|
|
348
|
+
"Secrets can be stored without printing values:",
|
|
349
|
+
"`printf '%s' \"$VALUE\" | node \"$REMOTEAGENT_SECRET_BIN\" set <KEY>`",
|
|
350
|
+
].join("\n"),
|
|
343
351
|
artifacts.length > 0
|
|
344
352
|
? ["Recent session artifacts:", ...artifacts.map((artifact) => `- ${artifact.id} ${artifact.kind}: ${artifact.path}`)].join("\n")
|
|
345
353
|
: undefined,
|
|
@@ -735,3 +743,32 @@ export function readSecretValue(dataDir, key) {
|
|
|
735
743
|
return undefined;
|
|
736
744
|
}
|
|
737
745
|
}
|
|
746
|
+
export function writeSecretValue(dataDir, key, value) {
|
|
747
|
+
if (!/^[A-Z0-9_.-]{1,80}$/.test(key)) {
|
|
748
|
+
throw new Error("Secret key must be 1-80 chars using A-Z, 0-9, dot, underscore, or dash.");
|
|
749
|
+
}
|
|
750
|
+
const filePath = path.join(dataDir, "managed", "secrets.json");
|
|
751
|
+
fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
752
|
+
let secrets = {};
|
|
753
|
+
try {
|
|
754
|
+
const raw = fsSync.readFileSync(filePath, "utf8");
|
|
755
|
+
secrets = JSON.parse(raw);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
secrets = {};
|
|
759
|
+
}
|
|
760
|
+
const now = new Date().toISOString();
|
|
761
|
+
secrets[key] = {
|
|
762
|
+
key,
|
|
763
|
+
value,
|
|
764
|
+
createdAt: secrets[key]?.createdAt ?? now,
|
|
765
|
+
updatedAt: now,
|
|
766
|
+
};
|
|
767
|
+
fsSync.writeFileSync(filePath, `${JSON.stringify(secrets, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
768
|
+
try {
|
|
769
|
+
fsSync.chmodSync(filePath, 0o600);
|
|
770
|
+
}
|
|
771
|
+
catch {
|
|
772
|
+
// Best effort only; some platforms do not support chmod.
|
|
773
|
+
}
|
|
774
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export const TELEGRAM_COMMAND_MENU = [
|
|
5
|
+
{ command: "start", description: "Start a new Codex or Claude session" },
|
|
6
|
+
{ command: "list", description: "List sessions" },
|
|
7
|
+
{ command: "switch", description: "Switch to a session" },
|
|
8
|
+
{ command: "status", description: "Show current session status" },
|
|
9
|
+
{ command: "state", description: "Show or edit session state notes" },
|
|
10
|
+
{ command: "option", description: "Show or change runtime options" },
|
|
11
|
+
{ command: "model", description: "Show or change provider model" },
|
|
12
|
+
{ command: "stop", description: "Stop active work and clear queued messages" },
|
|
13
|
+
{ command: "batch", description: "Collect and send a multi-message batch" },
|
|
14
|
+
{ command: "bots", description: "List configured Telegram bots" },
|
|
15
|
+
{ command: "bot", description: "Manage Telegram bots" },
|
|
16
|
+
{ command: "install", description: "Install or update Codex or Claude" },
|
|
17
|
+
{ command: "login", description: "Run provider login flow" },
|
|
18
|
+
{ command: "reset", description: "Clear this chat binding" },
|
|
19
|
+
{ command: "help", description: "Show command help" },
|
|
20
|
+
];
|
|
21
|
+
export async function setTelegramCommandMenu(token) {
|
|
22
|
+
await callTelegramCommandApi(token, "setMyCommands", {
|
|
23
|
+
commands: TELEGRAM_COMMAND_MENU,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function deleteTelegramCommandMenu(token) {
|
|
27
|
+
await callTelegramCommandApi(token, "deleteMyCommands", {});
|
|
28
|
+
}
|
|
29
|
+
async function callTelegramCommandApi(token, method, payload) {
|
|
30
|
+
let stdout;
|
|
31
|
+
let stderr;
|
|
32
|
+
try {
|
|
33
|
+
const result = await execFileAsync("curl", [
|
|
34
|
+
"-sS",
|
|
35
|
+
"-4",
|
|
36
|
+
"--max-time",
|
|
37
|
+
"20",
|
|
38
|
+
"-H",
|
|
39
|
+
"Content-Type: application/json",
|
|
40
|
+
"-d",
|
|
41
|
+
JSON.stringify(payload),
|
|
42
|
+
`https://api.telegram.org/bot${token}/${method}`,
|
|
43
|
+
]);
|
|
44
|
+
stdout = result.stdout;
|
|
45
|
+
stderr = result.stderr;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`Telegram ${method} curl failed: ${formatCurlError(error)}`);
|
|
49
|
+
}
|
|
50
|
+
if (stderr?.trim()) {
|
|
51
|
+
console.error(`curl stderr for ${method}: ${stderr.trim()}`);
|
|
52
|
+
}
|
|
53
|
+
const parsed = JSON.parse(stdout);
|
|
54
|
+
if (!parsed.ok) {
|
|
55
|
+
throw new Error(parsed.description || `Telegram ${method} failed.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function formatCurlError(error) {
|
|
59
|
+
if (!(error instanceof Error)) {
|
|
60
|
+
return String(error);
|
|
61
|
+
}
|
|
62
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
63
|
+
? String(error.code ?? "")
|
|
64
|
+
: "";
|
|
65
|
+
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
66
|
+
? String(error.stderr ?? "").trim()
|
|
67
|
+
: "";
|
|
68
|
+
return [code ? `code=${code}` : undefined, stderr || error.message]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(" ");
|
|
71
|
+
}
|