@offerpilot/axiomruntime 0.0.1

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.
Files changed (99) hide show
  1. package/README.md +185 -0
  2. package/dist/cli/commands/add.js +29 -0
  3. package/dist/cli/commands/context.js +29 -0
  4. package/dist/cli/commands/doctor.js +62 -0
  5. package/dist/cli/commands/edit.js +85 -0
  6. package/dist/cli/commands/help.js +16 -0
  7. package/dist/cli/commands/list.js +25 -0
  8. package/dist/cli/commands/log.js +63 -0
  9. package/dist/cli/commands/memory.js +241 -0
  10. package/dist/cli/commands/report.js +35 -0
  11. package/dist/cli/commands/session.js +74 -0
  12. package/dist/cli/commands/setup.js +86 -0
  13. package/dist/cli/commands/status.js +50 -0
  14. package/dist/cli/commands/telegram.js +701 -0
  15. package/dist/cli/commands/use.js +108 -0
  16. package/dist/cli/commands/version.js +12 -0
  17. package/dist/cli/index.js +276 -0
  18. package/dist/cli/output/table.js +22 -0
  19. package/dist/cli/prompts/prompt.js +37 -0
  20. package/dist/cli/registry.js +16 -0
  21. package/dist/core/config/cache-store.js +193 -0
  22. package/dist/core/config/json-store.js +114 -0
  23. package/dist/core/config/paths.js +85 -0
  24. package/dist/core/config/providers-store.js +89 -0
  25. package/dist/core/config/schema.js +60 -0
  26. package/dist/core/config/session-store.js +30 -0
  27. package/dist/core/config/usage-store.js +18 -0
  28. package/dist/core/context/context-service.js +186 -0
  29. package/dist/core/integrations/integration-state.js +105 -0
  30. package/dist/core/logs/log-service.js +56 -0
  31. package/dist/core/memory/embedding-check.js +121 -0
  32. package/dist/core/memory/memory-config.js +122 -0
  33. package/dist/core/models/model-discovery.js +430 -0
  34. package/dist/core/models/model-filter.js +13 -0
  35. package/dist/core/providers/provider-service.js +212 -0
  36. package/dist/core/reports/report-service.js +166 -0
  37. package/dist/core/runner/command-resolver.js +60 -0
  38. package/dist/core/runner/engine-registry.js +93 -0
  39. package/dist/core/runner/fallback.js +114 -0
  40. package/dist/core/runner/openai-usage-http.js +82 -0
  41. package/dist/core/runner/openai-usage-proxy.js +1 -0
  42. package/dist/core/runner/openai-usage-recording.js +172 -0
  43. package/dist/core/runner/openai-usage-responses.js +469 -0
  44. package/dist/core/runner/openai-usage-server.js +319 -0
  45. package/dist/core/runner/openai-usage-types.js +1 -0
  46. package/dist/core/runner/tool-runner.js +138 -0
  47. package/dist/core/sessions/session-service.js +47 -0
  48. package/dist/core/status/doctor-service.js +391 -0
  49. package/dist/core/status/status-service.js +60 -0
  50. package/dist/core/types.js +1 -0
  51. package/dist/core/usage/pricing.js +113 -0
  52. package/dist/core/usage/usage-service.js +30 -0
  53. package/dist/core/utils/is-record.js +3 -0
  54. package/dist/server/index.js +28 -0
  55. package/dist/server/runtime-server.js +430 -0
  56. package/dist/telegram/bot-registry.js +80 -0
  57. package/dist/telegram/bot.js +128 -0
  58. package/dist/telegram/config.js +235 -0
  59. package/dist/telegram/engine/claude-engine.js +240 -0
  60. package/dist/telegram/engine/codex-engine.js +437 -0
  61. package/dist/telegram/engine/engine-utils.js +67 -0
  62. package/dist/telegram/engine/process-utils.js +132 -0
  63. package/dist/telegram/engine/registry.js +31 -0
  64. package/dist/telegram/engine/types.js +1 -0
  65. package/dist/telegram/handler-registry.js +28 -0
  66. package/dist/telegram/handlers/callback.js +311 -0
  67. package/dist/telegram/handlers/command.js +272 -0
  68. package/dist/telegram/handlers/document.js +108 -0
  69. package/dist/telegram/handlers/memory.js +305 -0
  70. package/dist/telegram/handlers/message.js +701 -0
  71. package/dist/telegram/handlers/provider.js +332 -0
  72. package/dist/telegram/handlers/setup.js +527 -0
  73. package/dist/telegram/handlers/usage.js +124 -0
  74. package/dist/telegram/index.js +93 -0
  75. package/dist/telegram/interaction/approval.js +108 -0
  76. package/dist/telegram/interaction/command-menu.js +253 -0
  77. package/dist/telegram/interaction/formatter.js +487 -0
  78. package/dist/telegram/interaction/keyboards.js +145 -0
  79. package/dist/telegram/interaction/progress-reporter.js +160 -0
  80. package/dist/telegram/interaction/prompt-middleware.js +168 -0
  81. package/dist/telegram/interaction/result-store.js +41 -0
  82. package/dist/telegram/interaction/token-budget.js +21 -0
  83. package/dist/telegram/interaction/tool-name.js +41 -0
  84. package/dist/telegram/lifecycle-registry.js +47 -0
  85. package/dist/telegram/log.js +46 -0
  86. package/dist/telegram/memory/memory-inject.js +52 -0
  87. package/dist/telegram/memory/memory-service.js +413 -0
  88. package/dist/telegram/memory/memory-store.js +216 -0
  89. package/dist/telegram/memory/types.js +1 -0
  90. package/dist/telegram/network-retry.js +22 -0
  91. package/dist/telegram/network.js +53 -0
  92. package/dist/telegram/session/manager.js +229 -0
  93. package/dist/telegram/session/store.js +363 -0
  94. package/dist/telegram/session/types.js +1 -0
  95. package/dist/telegram/supervisor.js +57 -0
  96. package/dist/telegram/templates/messages.js +1 -0
  97. package/docs/README.md +98 -0
  98. package/docs/USAGE.html +853 -0
  99. package/package.json +57 -0
@@ -0,0 +1,701 @@
1
+ import { fork } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { getTelegramBotConfigPath, getTelegramBotLogPath, getTelegramBotPidPath, getTelegramConfigPath, getTelegramLogPath, getTelegramPidPath } from "../../core/config/paths.js";
6
+ import { describeTelegramConfig, loadBotConfig, loadTelegramConfig, saveBotConfig, saveTelegramConfig, setBotConfigValue, setTelegramConfigValue } from "../../telegram/config.js";
7
+ import { botExists, isBotRunning, isValidBotName, listBots, migrateLegacyConfig, removeBotFiles } from "../../telegram/bot-registry.js";
8
+ import { ensureTelegramLogFile, readRecentTelegramLogLines } from "../../telegram/log.js";
9
+ import { checkTelegramApi, getTelegramProxyUrl } from "../../telegram/network.js";
10
+ import { promptConfirm, promptSelect, promptText } from "../prompts/prompt.js";
11
+ export async function runTelegramCommand(args) {
12
+ const target = parseTelegramTargetArgs(args);
13
+ const [subcommand, ...rest] = target.args;
14
+ switch (subcommand) {
15
+ case undefined:
16
+ await runTelegramInteractive(target.botName, target.all);
17
+ return;
18
+ case "add":
19
+ assertNotAll(target, "add");
20
+ await addBot(resolveBotName(target.botName, rest[0], "add"));
21
+ return;
22
+ case "edit":
23
+ assertNotAll(target, "edit");
24
+ await editNamedBot(resolveBotName(target.botName, rest[0], "edit") ?? await selectNamedBot("Edit bot"));
25
+ return;
26
+ case "delete":
27
+ case "remove":
28
+ assertNotAll(target, subcommand);
29
+ {
30
+ const explicitName = resolveBotName(target.botName, rest[0], subcommand);
31
+ const name = explicitName ?? await selectNamedBot(`${subcommand === "delete" ? "Delete" : "Remove"} bot`);
32
+ if (!explicitName && !await promptConfirm(`Delete bot "${name}" and its config, PID, log, and session database?`)) {
33
+ console.log("Delete cancelled.");
34
+ return;
35
+ }
36
+ await removeBot(name);
37
+ }
38
+ return;
39
+ case "list":
40
+ assertUntargeted(target, "list");
41
+ await listAllBots();
42
+ return;
43
+ case "start":
44
+ await startTelegramBot(resolveLifecycleTarget(target, rest[0], "start"));
45
+ return;
46
+ case "stop":
47
+ await stopTelegramBot(resolveLifecycleTarget(target, rest[0], "stop"));
48
+ return;
49
+ case "status":
50
+ await showTelegramStatus(resolveLifecycleTarget(target, rest[0], "status"));
51
+ return;
52
+ case "config":
53
+ assertNotAll(target, "config");
54
+ await runTelegramConfigCommand(rest, target.botName);
55
+ return;
56
+ case "logs":
57
+ assertNotAll(target, "logs");
58
+ await showTelegramLogs(rest, target.botName);
59
+ return;
60
+ case "doctor":
61
+ assertNotAll(target, "doctor");
62
+ await runTelegramDoctor(resolveBotName(target.botName, rest[0], "doctor"));
63
+ return;
64
+ default:
65
+ throw new Error(`Unknown telegram command: ${subcommand}`);
66
+ }
67
+ }
68
+ export function parseTelegramTargetArgs(args) {
69
+ const remaining = [];
70
+ let botName;
71
+ let all = false;
72
+ for (let index = 0; index < args.length; index += 1) {
73
+ const arg = args[index];
74
+ if (arg === "--all") {
75
+ all = true;
76
+ continue;
77
+ }
78
+ if (arg === "--bot") {
79
+ const value = args[index + 1]?.trim();
80
+ if (!value || value.startsWith("--"))
81
+ throw new Error("Usage: --bot <name>");
82
+ if (botName && botName !== value)
83
+ throw new Error(`Conflicting bot targets: "${botName}" and "${value}".`);
84
+ botName = value;
85
+ index += 1;
86
+ continue;
87
+ }
88
+ if (arg.startsWith("--bot=")) {
89
+ const value = arg.slice("--bot=".length).trim();
90
+ if (!value)
91
+ throw new Error("Usage: --bot=<name>");
92
+ if (botName && botName !== value)
93
+ throw new Error(`Conflicting bot targets: "${botName}" and "${value}".`);
94
+ botName = value;
95
+ continue;
96
+ }
97
+ remaining.push(arg);
98
+ }
99
+ if (botName && !isValidBotName(botName)) {
100
+ throw new Error(`Invalid bot name: "${botName}". Use lowercase letters, digits, hyphens, underscores (max 32 chars).`);
101
+ }
102
+ if (botName && all)
103
+ throw new Error("Use either --bot <name> or --all, not both.");
104
+ return { args: remaining, ...(botName ? { botName } : {}), all };
105
+ }
106
+ async function runTelegramInteractive(botName, all = false) {
107
+ const choices = [
108
+ ...(!botName && !all ? [{ label: "add", value: "add" }] : []),
109
+ { label: "list", value: "list" },
110
+ ...(!all ? [
111
+ { label: "edit", value: "edit" },
112
+ { label: "delete", value: "delete" }
113
+ ] : []),
114
+ { label: "start", value: "start" },
115
+ { label: "stop", value: "stop" },
116
+ { label: "status", value: "status" },
117
+ ...(!all ? [
118
+ { label: "config", value: "config" },
119
+ { label: "logs", value: "logs" },
120
+ { label: "doctor", value: "doctor" }
121
+ ] : []),
122
+ { label: "exit", value: "exit" }
123
+ ];
124
+ const command = await promptSelect("Telegram", choices);
125
+ if (command === "add") {
126
+ assertNotAll({ all }, "add");
127
+ await addBot(botName);
128
+ }
129
+ if (command === "list")
130
+ await listAllBots();
131
+ if (command === "edit") {
132
+ assertNotAll({ all }, "edit");
133
+ await editNamedBot(botName ?? await selectNamedBot("Edit bot"));
134
+ }
135
+ if (command === "delete") {
136
+ assertNotAll({ all }, "delete");
137
+ const name = botName ?? await selectNamedBot("Delete bot");
138
+ const confirmed = await promptConfirm(`Delete bot "${name}" and its config, PID, log, and session database?`);
139
+ if (confirmed)
140
+ await removeBot(name);
141
+ else
142
+ console.log("Delete cancelled.");
143
+ }
144
+ if (command === "start")
145
+ await startTelegramBot(await selectLifecycleBot("Start bot", botName, all));
146
+ if (command === "stop")
147
+ await stopTelegramBot(await selectLifecycleBot("Stop bot", botName, all));
148
+ if (command === "status")
149
+ await showTelegramStatus(await selectLifecycleBot("Show bot status", botName, all));
150
+ if (command === "config") {
151
+ assertNotAll({ all }, "config");
152
+ await runTelegramConfigCommand([], botName ?? await selectOptionalBot("Edit config"));
153
+ }
154
+ if (command === "logs") {
155
+ assertNotAll({ all }, "logs");
156
+ await showTelegramLogs([], botName ?? await selectOptionalBot("Show logs"));
157
+ }
158
+ if (command === "doctor") {
159
+ assertNotAll({ all }, "doctor");
160
+ await runTelegramDoctor(botName ?? await selectOptionalBot("Diagnose bot"));
161
+ }
162
+ }
163
+ async function selectNamedBot(message) {
164
+ await migrateLegacyConfig();
165
+ const bots = listBots();
166
+ if (!bots.length)
167
+ throw new Error("No bots configured. Add one with: ai telegram add <name>");
168
+ return promptSelect(message, bots.map((bot) => ({ label: bot.name, value: bot.name })));
169
+ }
170
+ async function selectOptionalBot(message) {
171
+ await migrateLegacyConfig();
172
+ const bots = listBots();
173
+ if (!bots.length)
174
+ return undefined;
175
+ return promptSelect(message, bots.map((bot) => ({ label: bot.name, value: bot.name })));
176
+ }
177
+ async function selectLifecycleBot(message, botName, all = false) {
178
+ if (botName)
179
+ return botName;
180
+ if (all)
181
+ return undefined;
182
+ await migrateLegacyConfig();
183
+ const bots = listBots();
184
+ if (!bots.length)
185
+ return undefined;
186
+ const allTarget = "__all__";
187
+ const selected = await promptSelect(message, [
188
+ ...bots.map((bot) => ({ label: bot.name, value: bot.name })),
189
+ { label: "all bots", value: allTarget }
190
+ ]);
191
+ return selected === allTarget ? undefined : selected;
192
+ }
193
+ async function addBot(nameArg) {
194
+ const name = nameArg?.trim() || await promptText("Bot name (lowercase, a-z0-9_-)");
195
+ if (!isValidBotName(name)) {
196
+ throw new Error(`Invalid bot name: "${name}". Use lowercase letters, digits, hyphens, underscores (max 32 chars).`);
197
+ }
198
+ if (botExists(name)) {
199
+ throw new Error(`Bot "${name}" already exists. Use \`ai telegram config ${name}\` to edit.`);
200
+ }
201
+ const token = await promptText("Telegram bot token");
202
+ if (!token.trim())
203
+ throw new Error("Token is required.");
204
+ const allowedUserIds = await promptText("Allowed user IDs (comma-separated, empty for allowAll)");
205
+ const proxyUrl = await promptText("Proxy URL (empty to skip)", { defaultValue: "" });
206
+ const config = {
207
+ token: token.trim(),
208
+ ...(proxyUrl.trim() ? { proxyUrl: proxyUrl.trim() } : {})
209
+ };
210
+ if (allowedUserIds.trim()) {
211
+ config.allowedUserIds = allowedUserIds.split(/[,\s]+/).map(Number).filter((n) => Number.isInteger(n) && n > 0);
212
+ }
213
+ else {
214
+ config.allowAllUsers = true;
215
+ }
216
+ await saveBotConfig(name, config);
217
+ console.log(`Bot "${name}" added. Start with: ai telegram start ${name}`);
218
+ }
219
+ async function removeBot(name) {
220
+ if (!botExists(name))
221
+ throw new Error(`Bot "${name}" does not exist.`);
222
+ const config = await loadBotConfig(name);
223
+ const { running, pid } = isBotRunning(name);
224
+ if (running && pid) {
225
+ process.kill(pid, "SIGTERM");
226
+ if (!await waitForStop(pid, 5000)) {
227
+ throw new Error(`Bot "${name}" did not stop within 5 seconds; its files were kept.`);
228
+ }
229
+ console.log(`Stopped bot "${name}".`);
230
+ }
231
+ await removeBotFiles(name, config.dbPath);
232
+ console.log(`Bot "${name}" removed.`);
233
+ }
234
+ async function listAllBots() {
235
+ await migrateLegacyConfig();
236
+ const bots = listBots();
237
+ if (!bots.length) {
238
+ console.log("No bots configured. Add one with: ai telegram add <name>");
239
+ return;
240
+ }
241
+ console.log(`Configured bots (${bots.length}):\n`);
242
+ for (const bot of bots) {
243
+ const { running, pid } = isBotRunning(bot.name);
244
+ const status = running ? `running (PID: ${pid})` : "stopped";
245
+ console.log(` ${bot.name} — ${status}`);
246
+ }
247
+ }
248
+ async function startTelegramBot(nameArg) {
249
+ await migrateLegacyConfig();
250
+ if (!nameArg) {
251
+ const bots = listBots();
252
+ if (!bots.length) {
253
+ await startLegacyBot();
254
+ return;
255
+ }
256
+ let started = 0;
257
+ for (const bot of bots) {
258
+ await startNamedBot(bot.name);
259
+ started++;
260
+ }
261
+ console.log(`Started ${started} bot(s).`);
262
+ return;
263
+ }
264
+ await startNamedBot(nameArg);
265
+ }
266
+ async function startNamedBot(name) {
267
+ if (!botExists(name))
268
+ throw new Error(`Bot "${name}" does not exist.`);
269
+ const config = await loadBotConfig(name);
270
+ if (!config.token) {
271
+ throw new Error(`Bot "${name}": token is not configured.`);
272
+ }
273
+ if (!config.allowAllUsers && !config.allowedUserIds.length) {
274
+ throw new Error(`Bot "${name}": no users allowed.`);
275
+ }
276
+ const { running, pid: existingPid } = isBotRunning(name);
277
+ if (running) {
278
+ console.log(`Bot "${name}" is already running (PID: ${existingPid}).`);
279
+ return;
280
+ }
281
+ if (existingPid)
282
+ await removePidFile(getTelegramBotPidPath(name));
283
+ const logPath = getTelegramBotLogPath(name);
284
+ await fs.mkdir(path.dirname(logPath), { recursive: true });
285
+ const entry = resolveTelegramEntryPath();
286
+ const child = fork(entry, [], {
287
+ detached: true,
288
+ stdio: "ignore",
289
+ env: { ...process.env, AI_TELEGRAM_BOT_NAME: name, AI_GATEWAY_SOURCE: "telegram-cli" }
290
+ });
291
+ child.unref();
292
+ if (child.connected)
293
+ child.disconnect();
294
+ if (!child.pid)
295
+ throw new Error(`Failed to start bot "${name}".`);
296
+ await writePidFile(getTelegramBotPidPath(name), child.pid);
297
+ console.log(`Bot "${name}" started (PID: ${child.pid}).`);
298
+ }
299
+ async function startLegacyBot() {
300
+ const config = await loadTelegramConfig();
301
+ if (!config.token) {
302
+ throw new Error("Telegram token is not configured. Run `ai telegram config set token <token>`.");
303
+ }
304
+ if (!config.allowAllUsers && !config.allowedUserIds.length) {
305
+ throw new Error("No Telegram users are allowed.");
306
+ }
307
+ const existingPid = await readPid(getTelegramPidPath());
308
+ if (existingPid && isProcessRunning(existingPid)) {
309
+ console.log(`Telegram bot is already running (PID: ${existingPid}).`);
310
+ return;
311
+ }
312
+ if (existingPid)
313
+ await removePidFile(getTelegramPidPath());
314
+ const logPath = await ensureTelegramLogFile();
315
+ const entry = resolveTelegramEntryPath();
316
+ const child = fork(entry, [], {
317
+ detached: true,
318
+ stdio: "ignore",
319
+ env: { ...process.env, AI_GATEWAY_SOURCE: "telegram-cli" }
320
+ });
321
+ child.unref();
322
+ if (child.connected)
323
+ child.disconnect();
324
+ if (!child.pid)
325
+ throw new Error("Failed to start Telegram bot process.");
326
+ await writePidFile(getTelegramPidPath(), child.pid);
327
+ console.log(`Telegram bot started (PID: ${child.pid}).`);
328
+ console.log(`Log: ${logPath}`);
329
+ }
330
+ async function stopTelegramBot(nameArg) {
331
+ await migrateLegacyConfig();
332
+ if (!nameArg) {
333
+ const bots = listBots();
334
+ if (!bots.length) {
335
+ await stopLegacyBot();
336
+ return;
337
+ }
338
+ let stopped = 0;
339
+ for (const bot of bots) {
340
+ if (await stopNamedBot(bot.name))
341
+ stopped++;
342
+ }
343
+ console.log(`Stopped ${stopped} bot(s).`);
344
+ return;
345
+ }
346
+ await stopNamedBot(nameArg);
347
+ }
348
+ async function stopNamedBot(name) {
349
+ if (!botExists(name))
350
+ throw new Error(`Bot "${name}" does not exist.${availableBotsHint()}`);
351
+ const { running, pid } = isBotRunning(name);
352
+ if (!running) {
353
+ if (pid)
354
+ await removePidFile(getTelegramBotPidPath(name));
355
+ console.log(`Bot "${name}" is not running.`);
356
+ return false;
357
+ }
358
+ process.kill(pid, "SIGTERM");
359
+ if (!await waitForStop(pid, 5000)) {
360
+ throw new Error(`Bot "${name}" did not stop within 5 seconds; its PID file was kept.`);
361
+ }
362
+ await removePidFile(getTelegramBotPidPath(name));
363
+ console.log(`Bot "${name}" stopped.`);
364
+ return true;
365
+ }
366
+ async function stopLegacyBot() {
367
+ const pid = await readPid(getTelegramPidPath());
368
+ if (!pid) {
369
+ console.log("Telegram bot is not running.");
370
+ return;
371
+ }
372
+ if (!isProcessRunning(pid)) {
373
+ await removePidFile(getTelegramPidPath());
374
+ console.log("Telegram bot was not running; stale PID file removed.");
375
+ return;
376
+ }
377
+ process.kill(pid, "SIGTERM");
378
+ if (!await waitForStop(pid, 5000)) {
379
+ throw new Error("Telegram bot did not stop within 5 seconds; its PID file was kept.");
380
+ }
381
+ await removePidFile(getTelegramPidPath());
382
+ console.log("Telegram bot stopped.");
383
+ }
384
+ async function showTelegramStatus(nameArg) {
385
+ await migrateLegacyConfig();
386
+ if (!nameArg) {
387
+ const bots = listBots();
388
+ if (!bots.length) {
389
+ await showLegacyStatus();
390
+ return;
391
+ }
392
+ for (const bot of bots)
393
+ await showNamedBotStatus(bot.name);
394
+ return;
395
+ }
396
+ await showNamedBotStatus(nameArg);
397
+ }
398
+ async function showNamedBotStatus(name) {
399
+ if (!botExists(name))
400
+ throw new Error(`Bot "${name}" does not exist.${availableBotsHint()}`);
401
+ const config = await loadBotConfig(name);
402
+ const { running, pid } = isBotRunning(name);
403
+ console.log(`\n[${name}] ${running ? "running" : "stopped"}${pid ? ` (PID: ${pid})` : ""}`);
404
+ console.log(` Token: ${config.token ? "configured" : "missing"}`);
405
+ console.log(` Engine: ${config.defaultEngine}`);
406
+ console.log(` CWD: ${config.defaultCwd}`);
407
+ }
408
+ async function showLegacyStatus() {
409
+ const pid = await readPid(getTelegramPidPath());
410
+ const config = await loadTelegramConfig();
411
+ const running = Boolean(pid && isProcessRunning(pid));
412
+ console.log(`Telegram Bot: ${running ? "running" : "stopped"}`);
413
+ if (pid)
414
+ console.log(`PID: ${pid}${running ? "" : " (stale)"}`);
415
+ console.log(`Config: ${getTelegramConfigPath()}`);
416
+ console.log(`Log: ${getTelegramLogPath()}`);
417
+ console.log(`Token: ${config.token ? "configured" : "missing"}`);
418
+ console.log(`Default: ${config.defaultEngine} / ${config.defaultPermissionMode} / ${config.defaultCwd}`);
419
+ }
420
+ async function runTelegramConfigCommand(args, optionBotName) {
421
+ const [nameOrAction, ...rest] = args;
422
+ if (nameOrAction === "set") {
423
+ const [key, ...valueParts] = rest;
424
+ if (!key || !valueParts.length)
425
+ throw new Error("Usage: ai telegram config set <key> <value> [--bot <name>]");
426
+ const config = optionBotName
427
+ ? await setNamedBotConfigValue(optionBotName, key, valueParts.join(" "))
428
+ : await setTelegramConfigValue(key, valueParts.join(" "));
429
+ console.log(`Updated ${optionBotName ? `bot "${optionBotName}"` : "telegram"} config: ${key}`);
430
+ printTelegramConfig(config, optionBotName);
431
+ return;
432
+ }
433
+ if (nameOrAction === "show") {
434
+ if (optionBotName)
435
+ assertBotExists(optionBotName);
436
+ printTelegramConfig(optionBotName ? await loadBotConfig(optionBotName) : await loadTelegramConfig(), optionBotName);
437
+ return;
438
+ }
439
+ const targetName = resolveBotName(optionBotName, nameOrAction, "config");
440
+ if (targetName) {
441
+ await editNamedBot(targetName);
442
+ return;
443
+ }
444
+ await editTelegramConfigInteractive();
445
+ }
446
+ async function editNamedBot(name) {
447
+ assertBotExists(name);
448
+ await editBotConfigInteractive(name);
449
+ }
450
+ async function setNamedBotConfigValue(name, key, value) {
451
+ assertBotExists(name);
452
+ return setBotConfigValue(name, key, value);
453
+ }
454
+ async function editBotConfigInteractive(name) {
455
+ let config = await loadBotConfig(name);
456
+ while (true) {
457
+ console.log(`\n[${name}] config:`);
458
+ for (const line of describeTelegramConfig(config))
459
+ console.log(` ${line}`);
460
+ const field = await promptSelect(`Edit ${name}`, [
461
+ { label: "token", value: "token" },
462
+ { label: "allowAllUsers", value: "allowAllUsers" },
463
+ { label: "allowedUserIds", value: "allowedUserIds" },
464
+ { label: "defaultEngine", value: "defaultEngine" },
465
+ { label: "defaultCwd", value: "defaultCwd" },
466
+ { label: "defaultPermissionMode", value: "defaultPermissionMode" },
467
+ { label: "sessionTimeoutHours", value: "sessionTimeoutHours" },
468
+ { label: "maxOutputLength", value: "maxOutputLength" },
469
+ { label: "provider", value: "provider" },
470
+ { label: "model", value: "model" },
471
+ { label: "proxyUrl", value: "proxyUrl" },
472
+ { label: "done", value: "done" }
473
+ ]);
474
+ if (field === "done")
475
+ return;
476
+ if (field === "defaultEngine") {
477
+ const engine = await promptSelect("Engine", [
478
+ { label: "claude", value: "claude" },
479
+ { label: "codex", value: "codex" }
480
+ ]);
481
+ config = await saveBotConfig(name, { ...config, defaultEngine: engine });
482
+ continue;
483
+ }
484
+ if (field === "defaultPermissionMode") {
485
+ const defaultPermissionMode = await promptSelect("Default permission mode", [
486
+ { label: "bypassPermissions", value: "bypassPermissions" },
487
+ { label: "acceptEdits", value: "acceptEdits" },
488
+ { label: "default", value: "default" },
489
+ { label: "readOnly", value: "readOnly" }
490
+ ]);
491
+ config = await saveBotConfig(name, { ...config, defaultPermissionMode });
492
+ continue;
493
+ }
494
+ const current = String(config[field] ?? "");
495
+ const value = field === "token"
496
+ ? await promptText("token (leave empty to keep current)") || current
497
+ : await promptText(field, { defaultValue: current });
498
+ config = await setBotConfigValue(name, field, value);
499
+ }
500
+ }
501
+ async function editTelegramConfigInteractive() {
502
+ let config = await loadTelegramConfig();
503
+ while (true) {
504
+ console.log("");
505
+ printTelegramConfig(config);
506
+ const field = await promptSelect("Edit Telegram config", [
507
+ { label: "token", value: "token" },
508
+ { label: "allowAllUsers", value: "allowAllUsers" },
509
+ { label: "allowedUserIds", value: "allowedUserIds" },
510
+ { label: "defaultEngine", value: "defaultEngine" },
511
+ { label: "defaultCwd", value: "defaultCwd" },
512
+ { label: "defaultPermissionMode", value: "defaultPermissionMode" },
513
+ { label: "sessionTimeoutHours", value: "sessionTimeoutHours" },
514
+ { label: "maxOutputLength", value: "maxOutputLength" },
515
+ { label: "provider", value: "provider" },
516
+ { label: "model", value: "model" },
517
+ { label: "proxyUrl", value: "proxyUrl" },
518
+ { label: "done", value: "done" }
519
+ ]);
520
+ if (field === "done")
521
+ return;
522
+ if (field === "defaultEngine") {
523
+ const defaultEngine = await promptSelect("Default engine", [
524
+ { label: "claude", value: "claude" },
525
+ { label: "codex", value: "codex" }
526
+ ]);
527
+ config = await saveTelegramConfig({ ...config, defaultEngine });
528
+ continue;
529
+ }
530
+ if (field === "defaultPermissionMode") {
531
+ const defaultPermissionMode = await promptSelect("Default permission mode", [
532
+ { label: "bypassPermissions", value: "bypassPermissions" },
533
+ { label: "acceptEdits", value: "acceptEdits" },
534
+ { label: "default", value: "default" }
535
+ ]);
536
+ config = await saveTelegramConfig({ ...config, defaultPermissionMode });
537
+ continue;
538
+ }
539
+ const current = String(config[field] ?? "");
540
+ const value = field === "token"
541
+ ? await promptText("token (leave empty to keep current)") || current
542
+ : await promptText(field, { defaultValue: current });
543
+ config = await setTelegramConfigValue(field, value);
544
+ }
545
+ }
546
+ async function runTelegramDoctor(nameArg) {
547
+ if (nameArg)
548
+ assertBotExists(nameArg);
549
+ const config = nameArg ? await loadBotConfig(nameArg) : await loadTelegramConfig();
550
+ if (!config.token)
551
+ throw new Error("Telegram token is not configured.");
552
+ const proxyUrl = getTelegramProxyUrl(config);
553
+ console.log(`Telegram API: checking getMe${proxyUrl ? ` via ${proxyUrl}` : ""}...`);
554
+ const result = await checkTelegramApi(config.token, proxyUrl ?? undefined);
555
+ if (result.ok) {
556
+ console.log(`Telegram API: ok${result.username ? ` (@${result.username})` : ""}`);
557
+ return;
558
+ }
559
+ console.log(`Telegram API: failed${result.status ? ` (HTTP ${result.status})` : ""}`);
560
+ console.log(result.error ?? "Unknown error");
561
+ }
562
+ async function showTelegramLogs(args, optionBotName) {
563
+ const follow = args.includes("-f") || args.includes("--follow");
564
+ const positionalName = args.find((a) => !a.startsWith("-") && !/^\d+$/.test(a));
565
+ const nameArg = resolveBotName(optionBotName, positionalName, "logs");
566
+ const limitArg = args.find((arg) => /^\d+$/.test(arg));
567
+ const limit = limitArg ? Number(limitArg) : 50;
568
+ if (nameArg) {
569
+ assertBotExists(nameArg);
570
+ const logPath = getTelegramBotLogPath(nameArg);
571
+ await showLogFile(logPath, limit, follow);
572
+ return;
573
+ }
574
+ const filePath = await ensureTelegramLogFile();
575
+ const lines = await readRecentTelegramLogLines(limit);
576
+ if (!lines.length) {
577
+ console.log(`No Telegram logs yet: ${filePath}`);
578
+ }
579
+ else {
580
+ for (const line of lines)
581
+ console.log(formatLogLine(line));
582
+ }
583
+ if (follow)
584
+ await followLogFile(filePath);
585
+ }
586
+ async function showLogFile(filePath, limit, follow) {
587
+ try {
588
+ const content = await fs.readFile(filePath, "utf8");
589
+ const lines = content.trim().split("\n").slice(-limit);
590
+ for (const line of lines)
591
+ console.log(formatLogLine(line));
592
+ }
593
+ catch (error) {
594
+ if (error.code === "ENOENT") {
595
+ console.log(`No logs yet: ${filePath}`);
596
+ }
597
+ else {
598
+ throw error;
599
+ }
600
+ }
601
+ if (follow)
602
+ await followLogFile(filePath);
603
+ }
604
+ function printTelegramConfig(config, botName) {
605
+ console.log(`${botName ? `Telegram bot "${botName}"` : "Telegram"} config: ${botName ? getTelegramBotConfigPath(botName) : getTelegramConfigPath()}`);
606
+ for (const line of describeTelegramConfig(config))
607
+ console.log(line);
608
+ }
609
+ function resolveBotName(optionName, positionalName, command) {
610
+ const positional = positionalName?.trim() || undefined;
611
+ if (optionName && positional && optionName !== positional) {
612
+ throw new Error(`Conflicting bot targets for ${command}: "${optionName}" and "${positional}".`);
613
+ }
614
+ return optionName ?? positional;
615
+ }
616
+ function resolveLifecycleTarget(target, positionalName, command) {
617
+ if (target.all && positionalName)
618
+ throw new Error(`Use either a bot name or --all for ${command}, not both.`);
619
+ return target.all ? undefined : resolveBotName(target.botName, positionalName, command);
620
+ }
621
+ function assertNotAll(target, command) {
622
+ if (target.all)
623
+ throw new Error(`ai telegram ${command} does not support --all.`);
624
+ }
625
+ function assertUntargeted(target, command) {
626
+ if (target.botName || target.all)
627
+ throw new Error(`ai telegram ${command} does not accept a bot target.`);
628
+ }
629
+ function assertBotExists(name) {
630
+ if (!botExists(name))
631
+ throw new Error(`Bot "${name}" does not exist.${availableBotsHint()}`);
632
+ }
633
+ function availableBotsHint() {
634
+ const names = listBots().map((bot) => bot.name);
635
+ return names.length ? ` Available bots: ${names.join(", ")}.` : " Add one with: ai telegram add <name>.";
636
+ }
637
+ async function readPid(pidPath) {
638
+ try {
639
+ const raw = await fs.readFile(pidPath, "utf8");
640
+ const pid = Number(raw.trim());
641
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
642
+ }
643
+ catch (error) {
644
+ if (error.code === "ENOENT")
645
+ return null;
646
+ throw error;
647
+ }
648
+ }
649
+ async function writePidFile(pidPath, pid) {
650
+ await fs.mkdir(path.dirname(pidPath), { recursive: true });
651
+ await fs.writeFile(pidPath, `${pid}\n`, "utf8");
652
+ }
653
+ async function removePidFile(pidPath) {
654
+ try {
655
+ await fs.unlink(pidPath);
656
+ }
657
+ catch (error) {
658
+ if (error.code !== "ENOENT")
659
+ throw error;
660
+ }
661
+ }
662
+ function isProcessRunning(pid) {
663
+ try {
664
+ process.kill(pid, 0);
665
+ return true;
666
+ }
667
+ catch {
668
+ return false;
669
+ }
670
+ }
671
+ async function waitForStop(pid, timeoutMs) {
672
+ const start = Date.now();
673
+ while (Date.now() - start < timeoutMs) {
674
+ if (!isProcessRunning(pid))
675
+ return true;
676
+ await new Promise((r) => setTimeout(r, 100));
677
+ }
678
+ return !isProcessRunning(pid);
679
+ }
680
+ function resolveTelegramEntryPath() {
681
+ const currentFile = fileURLToPath(import.meta.url);
682
+ return path.resolve(path.dirname(currentFile), "../../telegram/index.js");
683
+ }
684
+ function formatLogLine(line) {
685
+ try {
686
+ const event = JSON.parse(line);
687
+ return `[${event.timestamp ?? ""}] ${event.level ?? "info"} ${event.action ?? ""}: ${event.message ?? ""}`;
688
+ }
689
+ catch {
690
+ return line;
691
+ }
692
+ }
693
+ async function followLogFile(filePath) {
694
+ console.log(`Following ${filePath}. Press Ctrl+C to stop.`);
695
+ const { spawn } = await import("node:child_process");
696
+ const child = spawn("tail", ["-f", filePath], { stdio: "inherit" });
697
+ await new Promise((resolve) => {
698
+ child.on("close", () => resolve());
699
+ child.on("error", () => resolve());
700
+ });
701
+ }