@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,93 @@
1
+ #!/usr/bin/env node
2
+ import { fork } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import { getTelegramBotPidPath, getTelegramPidPath } from "../core/config/paths.js";
5
+ import { createBot } from "./bot.js";
6
+ import { writeTelegramLog } from "./log.js";
7
+ const botName = process.env.AI_TELEGRAM_BOT_NAME || "";
8
+ function resolvePidPath() {
9
+ return botName ? getTelegramBotPidPath(botName) : getTelegramPidPath();
10
+ }
11
+ async function main() {
12
+ const runtime = await createBot(botName || undefined);
13
+ const pidPath = resolvePidPath();
14
+ await fs.writeFile(pidPath, `${process.pid}\n`, "utf8");
15
+ await writeTelegramLog({
16
+ action: "bot_start",
17
+ message: `Telegram bot started with PID ${process.pid}`,
18
+ metadata: {
19
+ allowedUsers: runtime.config.allowedUserIds.length,
20
+ defaultEngine: runtime.config.defaultEngine
21
+ }
22
+ });
23
+ const stop = async (signal, restart = false) => {
24
+ await writeTelegramLog({ action: "bot_stop", message: `Telegram bot stopping after ${signal}` });
25
+ runtime.engines.claude.abort(Number.NaN);
26
+ runtime.engines.codex.abort(Number.NaN);
27
+ await runtime.bot.stop();
28
+ runtime.store.close();
29
+ try {
30
+ await fs.unlink(resolvePidPath());
31
+ }
32
+ catch {
33
+ // PID file may already be removed by CLI stop.
34
+ }
35
+ if (restart) {
36
+ const entry = process.argv[1];
37
+ if (!entry)
38
+ throw new Error("Cannot restart Telegram bot: entry path is unavailable.");
39
+ const replacement = fork(entry, [], {
40
+ detached: true,
41
+ stdio: "ignore",
42
+ env: { ...process.env, AI_GATEWAY_SOURCE: "telegram-restart" }
43
+ });
44
+ replacement.unref();
45
+ if (replacement.connected)
46
+ replacement.disconnect();
47
+ await writeTelegramLog({
48
+ action: "bot_restart_spawned",
49
+ message: `Telegram bot replacement started with PID ${replacement.pid ?? "unknown"}`
50
+ });
51
+ }
52
+ };
53
+ process.once("SIGTERM", () => {
54
+ void stop("SIGTERM").finally(() => process.exit(0));
55
+ });
56
+ process.once("SIGINT", () => {
57
+ void stop("SIGINT").finally(() => process.exit(0));
58
+ });
59
+ process.once("SIGHUP", () => {
60
+ void stop("SIGHUP", true)
61
+ .then(() => process.exit(0))
62
+ .catch(async (error) => {
63
+ await writeTelegramLog({
64
+ level: "error",
65
+ action: "bot_restart_failed",
66
+ message: error instanceof Error ? error.message : String(error)
67
+ });
68
+ process.exit(1);
69
+ });
70
+ });
71
+ await runtime.bot.start({
72
+ drop_pending_updates: false,
73
+ onStart: (botInfo) => {
74
+ void writeTelegramLog({
75
+ action: "polling_started",
76
+ message: `Telegram polling started as @${botInfo.username}`,
77
+ metadata: { username: botInfo.username }
78
+ });
79
+ }
80
+ });
81
+ }
82
+ main().catch(async (error) => {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ await writeTelegramLog({ level: "error", action: "bot_fatal", message });
85
+ try {
86
+ await fs.unlink(resolvePidPath());
87
+ }
88
+ catch {
89
+ // Ignore cleanup failures during fatal startup errors.
90
+ }
91
+ console.error(message);
92
+ process.exitCode = 1;
93
+ });
@@ -0,0 +1,108 @@
1
+ import { escapeHtml, truncateText } from "./formatter.js";
2
+ import { buildApprovalKeyboard } from "./keyboards.js";
3
+ const TIMEOUT_MS = 5 * 60 * 1000;
4
+ const ALWAYS_CONFIRM_PATTERNS = [
5
+ /rm\s+(-rf?|--recursive)/i,
6
+ /git\s+push\s+.*--force/i,
7
+ /git\s+reset\s+--hard/i,
8
+ /DROP\s+(TABLE|DATABASE)/i,
9
+ /sudo\s+/i,
10
+ /chmod\s+777/i,
11
+ />\s*\/dev\//i
12
+ ];
13
+ export class ApprovalManager {
14
+ bot;
15
+ pending = new Map();
16
+ constructor(bot) {
17
+ this.bot = bot;
18
+ }
19
+ async request(chatId, req) {
20
+ const message = await this.bot.api.sendMessage(chatId, formatPermissionMessage(req), {
21
+ parse_mode: "HTML",
22
+ reply_markup: buildApprovalKeyboard(req.id)
23
+ });
24
+ return new Promise((resolve) => {
25
+ const timeout = setTimeout(() => {
26
+ this.pending.delete(req.id);
27
+ resolve(false);
28
+ void this.markExpired(chatId, message.message_id);
29
+ }, TIMEOUT_MS);
30
+ this.pending.set(req.id, {
31
+ id: req.id,
32
+ chatId,
33
+ messageId: message.message_id,
34
+ resolve,
35
+ timeout
36
+ });
37
+ });
38
+ }
39
+ resolve(id, approved) {
40
+ const entry = this.pending.get(id);
41
+ if (!entry)
42
+ return false;
43
+ clearTimeout(entry.timeout);
44
+ this.pending.delete(id);
45
+ entry.resolve(approved);
46
+ void this.markResolved(entry, approved);
47
+ return true;
48
+ }
49
+ resolveAll(chatId, approved) {
50
+ let count = 0;
51
+ for (const [id, entry] of this.pending) {
52
+ if (entry.chatId === chatId) {
53
+ clearTimeout(entry.timeout);
54
+ this.pending.delete(id);
55
+ entry.resolve(approved);
56
+ void this.markResolved(entry, approved);
57
+ count += 1;
58
+ }
59
+ }
60
+ return count;
61
+ }
62
+ hasPending(chatId) {
63
+ return [...this.pending.values()].some((entry) => entry.chatId === chatId);
64
+ }
65
+ async markResolved(entry, approved) {
66
+ if (!entry.messageId)
67
+ return;
68
+ try {
69
+ await this.bot.api.editMessageReplyMarkup(entry.chatId, entry.messageId, {
70
+ reply_markup: undefined
71
+ });
72
+ await this.bot.api.sendMessage(entry.chatId, approved ? "Permission allowed." : "Permission denied.");
73
+ }
74
+ catch {
75
+ // Telegram messages may be gone or no longer editable.
76
+ }
77
+ }
78
+ async markExpired(chatId, messageId) {
79
+ try {
80
+ await this.bot.api.editMessageReplyMarkup(chatId, messageId, { reply_markup: undefined });
81
+ await this.bot.api.sendMessage(chatId, "Permission request timed out.");
82
+ }
83
+ catch {
84
+ // Best-effort UI cleanup.
85
+ }
86
+ }
87
+ }
88
+ export function requiresDangerousConfirmation(command) {
89
+ return ALWAYS_CONFIRM_PATTERNS.some((pattern) => pattern.test(command));
90
+ }
91
+ export function formatPermissionMessage(req) {
92
+ const lines = [
93
+ "<b>Permission request</b>",
94
+ "",
95
+ `tool: <code>${escapeHtml(req.tool)}</code>`
96
+ ];
97
+ if (req.command) {
98
+ lines.push("command:", `<pre>${escapeHtml(truncateText(req.command, 300))}</pre>`);
99
+ }
100
+ if (req.filePath) {
101
+ lines.push(`file: <code>${escapeHtml(req.filePath)}</code>`);
102
+ }
103
+ if (req.description) {
104
+ lines.push("", escapeHtml(truncateText(req.description, 300)));
105
+ }
106
+ lines.push("", "<i>Automatically denied after 5 minutes.</i>");
107
+ return lines.join("\n");
108
+ }
@@ -0,0 +1,253 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { writeTelegramLog } from "../log.js";
5
+ const MAX_TELEGRAM_COMMANDS = 100;
6
+ const BASE_COMMANDS = [
7
+ { command: "claude", description: "使用 Claude Code", prompt: "/claude" },
8
+ { command: "codex", description: "使用 Codex", prompt: "/codex" },
9
+ { command: "reset", description: "重置会话信息", prompt: "/reset" },
10
+ { command: "fresh", description: "清空聊天窗口", prompt: "/fresh" },
11
+ { command: "cwd", description: "查看或切换工作路径", prompt: "/cwd" },
12
+ { command: "config", description: "查看或编辑 Bot 配置", prompt: "/config" },
13
+ { command: "provider", description: "管理 AI Provider", prompt: "/provider" },
14
+ { command: "usage", description: "查看当前会话额度", prompt: "/usage" },
15
+ { command: "remember", description: "保存一条 Bot 记忆", prompt: "/remember" },
16
+ { command: "forget", description: "删除 Bot 记忆", prompt: "/forget" },
17
+ { command: "memories", description: "查看 Bot 记忆", prompt: "/memories" },
18
+ { command: "memory", description: "管理 Bot 记忆", prompt: "/memory" },
19
+ { command: "pin", description: "固定一条重要对话", prompt: "/pin" },
20
+ { command: "unpin", description: "取消固定对话", prompt: "/unpin" },
21
+ { command: "command", description: "注册自定义命令", prompt: "/__register_command" },
22
+ { command: "terminal", description: "执行终端命令", prompt: "/terminal" },
23
+ { command: "restart", description: "重启 Bot", prompt: "/restart" },
24
+ { command: "help", description: "查看帮助", prompt: "/help" }
25
+ ];
26
+ const BUILTIN_CLI_COMMANDS = {
27
+ claude: [
28
+ { command: "add_dir", description: "Claude Code: add working directories", prompt: "/add-dir" },
29
+ { command: "agents", description: "Claude Code: manage agents", prompt: "/agents" },
30
+ { command: "clear", description: "Claude Code: clear conversation", prompt: "/clear" },
31
+ { command: "compact", description: "Claude Code: compact context", prompt: "/compact" },
32
+ { command: "config", description: "Claude Code: open config", prompt: "/config" },
33
+ { command: "cost", description: "Claude Code: show cost", prompt: "/cost" },
34
+ { command: "doctor", description: "Claude Code: health check", prompt: "/doctor" },
35
+ { command: "help_claude", description: "Claude Code: slash command help", prompt: "/help" },
36
+ { command: "ide", description: "Claude Code: manage IDE connection", prompt: "/ide" },
37
+ { command: "init", description: "Claude Code: initialize project memory", prompt: "/init" },
38
+ { command: "login", description: "Claude Code: login", prompt: "/login" },
39
+ { command: "logout", description: "Claude Code: logout", prompt: "/logout" },
40
+ { command: "mcp", description: "Claude Code: manage MCP", prompt: "/mcp" },
41
+ { command: "memory", description: "Claude Code: edit memory", prompt: "/memory" },
42
+ { command: "model", description: "Claude Code: switch model", prompt: "/model" },
43
+ { command: "permissions", description: "Claude Code: manage permissions", prompt: "/permissions" },
44
+ { command: "pr_comments", description: "Claude Code: view PR comments", prompt: "/pr-comments" },
45
+ { command: "resume", description: "Claude Code: resume conversation", prompt: "/resume" },
46
+ { command: "review", description: "Claude Code: review changes", prompt: "/review" },
47
+ { command: "status", description: "Claude Code: show status", prompt: "/status" },
48
+ { command: "terminal_setup", description: "Claude Code: terminal setup", prompt: "/terminal-setup" },
49
+ { command: "vim", description: "Claude Code: toggle vim mode", prompt: "/vim" }
50
+ ],
51
+ codex: [
52
+ { command: "clear", description: "Codex: clear conversation", prompt: "/clear" },
53
+ { command: "compact", description: "Codex: compact context", prompt: "/compact" },
54
+ { command: "help_codex", description: "Codex: slash command help", prompt: "/help" },
55
+ { command: "init", description: "Codex: initialize project instructions", prompt: "/init" },
56
+ { command: "model", description: "Codex: switch model", prompt: "/model" },
57
+ { command: "new", description: "Codex: new conversation", prompt: "/new" },
58
+ { command: "review", description: "Codex: review changes", prompt: "/review" },
59
+ { command: "status", description: "Codex: show status", prompt: "/status" }
60
+ ]
61
+ };
62
+ export function getBaseBotCommands() {
63
+ return BASE_COMMANDS.map(toBotCommand);
64
+ }
65
+ export function getChatBotCommands(engine, customCommands = []) {
66
+ return getCommandMappings(engine, customCommands).map(toBotCommand).slice(0, MAX_TELEGRAM_COMMANDS);
67
+ }
68
+ export function getCommandMappings(engine, customCommands = []) {
69
+ const custom = customCommands.map((c) => ({
70
+ command: c.name,
71
+ description: c.description,
72
+ prompt: `/__custom_${c.name}`
73
+ }));
74
+ return mergeCommandMappings([
75
+ ...BASE_COMMANDS,
76
+ ...BUILTIN_CLI_COMMANDS[engine],
77
+ ...custom,
78
+ ...readConfiguredCommands(engine)
79
+ ]);
80
+ }
81
+ export function resolveMappedCommand(engine, command, customCommands = []) {
82
+ const normalized = normalizeTelegramCommandName(command);
83
+ return getCommandMappings(engine, customCommands).find((item) => item.command === normalized) ?? null;
84
+ }
85
+ export async function setDefaultCommandMenu(bot) {
86
+ await bot.api.setMyCommands(getBaseBotCommands());
87
+ }
88
+ export async function setChatCommandMenu(bot, chatId, engine, customCommands = []) {
89
+ await bot.api.setMyCommands(getChatBotCommands(engine, customCommands), {
90
+ scope: { type: "chat", chat_id: chatId }
91
+ });
92
+ }
93
+ export async function resetChatCommandMenu(bot, chatId) {
94
+ await bot.api.deleteMyCommands({
95
+ scope: { type: "chat", chat_id: chatId }
96
+ });
97
+ }
98
+ export async function safeSetDefaultCommandMenu(bot) {
99
+ try {
100
+ await setDefaultCommandMenu(bot);
101
+ }
102
+ catch (error) {
103
+ await writeTelegramLog({
104
+ level: "warn",
105
+ action: "set_commands_failed",
106
+ message: error instanceof Error ? error.message : String(error)
107
+ });
108
+ }
109
+ }
110
+ export async function safeSetChatCommandMenu(bot, chatId, engine, customCommands = []) {
111
+ try {
112
+ await setChatCommandMenu(bot, chatId, engine, customCommands);
113
+ }
114
+ catch (error) {
115
+ await writeTelegramLog({
116
+ level: "warn",
117
+ action: "set_chat_commands_failed",
118
+ message: error instanceof Error ? error.message : String(error),
119
+ metadata: { chatId, engine }
120
+ });
121
+ }
122
+ }
123
+ export async function safeResetChatCommandMenu(bot, chatId) {
124
+ try {
125
+ await resetChatCommandMenu(bot, chatId);
126
+ }
127
+ catch (error) {
128
+ await writeTelegramLog({
129
+ level: "warn",
130
+ action: "reset_chat_commands_failed",
131
+ message: error instanceof Error ? error.message : String(error),
132
+ metadata: { chatId }
133
+ });
134
+ }
135
+ }
136
+ export function parseTelegramCommand(text) {
137
+ const match = text.match(/^\/([A-Za-z0-9_]+)(?:@[A-Za-z0-9_]+)?(?:\s+([\s\S]*))?$/);
138
+ if (!match)
139
+ return null;
140
+ return {
141
+ command: normalizeTelegramCommandName(match[1] ?? ""),
142
+ args: (match[2] ?? "").trim()
143
+ };
144
+ }
145
+ function toBotCommand(mapping) {
146
+ return {
147
+ command: mapping.command,
148
+ description: truncateDescription(mapping.description)
149
+ };
150
+ }
151
+ function readConfiguredCommands(engine) {
152
+ const roots = getCommandRoots(engine);
153
+ const commands = [];
154
+ for (const root of roots) {
155
+ commands.push(...readCommandFiles(root, root));
156
+ }
157
+ return commands;
158
+ }
159
+ function getCommandRoots(engine) {
160
+ const home = os.homedir();
161
+ if (engine === "claude") {
162
+ return [
163
+ path.join(home, ".claude", "commands"),
164
+ path.join(home, ".config", "ai", "claude", "commands"),
165
+ path.join(home, ".config", "ai", "claude", "plugins", "cache"),
166
+ path.join(home, ".config", "ai", "claude", "plugins", "marketplaces")
167
+ ];
168
+ }
169
+ return [
170
+ path.join(home, ".codex", "commands"),
171
+ path.join(home, ".config", "ai", "codex", "commands"),
172
+ path.join(home, ".config", "ai", "codex", "plugins", "cache")
173
+ ];
174
+ }
175
+ function readCommandFiles(root, current, depth = 0) {
176
+ if (!fs.existsSync(current) || depth > 8)
177
+ return [];
178
+ let entries;
179
+ try {
180
+ entries = fs.readdirSync(current, { withFileTypes: true });
181
+ }
182
+ catch {
183
+ return [];
184
+ }
185
+ const results = [];
186
+ for (const entry of entries) {
187
+ if (entry.name.startsWith("."))
188
+ continue;
189
+ const filePath = path.join(current, entry.name);
190
+ if (entry.isDirectory()) {
191
+ results.push(...readCommandFiles(root, filePath, depth + 1));
192
+ continue;
193
+ }
194
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
195
+ continue;
196
+ const mapping = commandFromMarkdown(root, filePath);
197
+ if (mapping)
198
+ results.push(mapping);
199
+ }
200
+ return results;
201
+ }
202
+ function commandFromMarkdown(root, filePath) {
203
+ const originalName = path.basename(filePath, ".md");
204
+ const command = normalizeTelegramCommandName(originalName);
205
+ if (!command)
206
+ return null;
207
+ let description = `${originalName} command`;
208
+ try {
209
+ const content = fs.readFileSync(filePath, "utf8");
210
+ const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
211
+ const descriptionLine = content.match(/^description:\s*["']?(.+?)["']?\s*$/im)?.[1]?.trim();
212
+ description = descriptionLine || title || description;
213
+ }
214
+ catch {
215
+ // Keep the fallback description when command metadata is unreadable.
216
+ }
217
+ return {
218
+ command,
219
+ description,
220
+ prompt: `/${originalName}`
221
+ };
222
+ }
223
+ function mergeCommandMappings(commands) {
224
+ const seen = new Set();
225
+ const result = [];
226
+ for (const command of commands) {
227
+ const normalized = normalizeTelegramCommandName(command.command);
228
+ if (!normalized || seen.has(normalized))
229
+ continue;
230
+ seen.add(normalized);
231
+ result.push({
232
+ command: normalized,
233
+ description: truncateDescription(command.description || command.prompt),
234
+ prompt: command.prompt
235
+ });
236
+ }
237
+ return result;
238
+ }
239
+ function normalizeTelegramCommandName(value) {
240
+ return value
241
+ .trim()
242
+ .replace(/^\//, "")
243
+ .replace(/\.md$/i, "")
244
+ .toLowerCase()
245
+ .replace(/[^a-z0-9_]/g, "_")
246
+ .replace(/_+/g, "_")
247
+ .replace(/^_+|_+$/g, "")
248
+ .slice(0, 32);
249
+ }
250
+ function truncateDescription(value) {
251
+ const normalized = value.replace(/\s+/g, " ").trim() || "Run command";
252
+ return normalized.length <= 120 ? normalized : `${normalized.slice(0, 117)}...`;
253
+ }