@dreb/telegram 1.16.0

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 (82) hide show
  1. package/README.md +91 -0
  2. package/dist/agent-bridge.d.ts +146 -0
  3. package/dist/agent-bridge.d.ts.map +1 -0
  4. package/dist/agent-bridge.js +466 -0
  5. package/dist/agent-bridge.js.map +1 -0
  6. package/dist/bot.d.ts +11 -0
  7. package/dist/bot.d.ts.map +1 -0
  8. package/dist/bot.js +112 -0
  9. package/dist/bot.js.map +1 -0
  10. package/dist/bridge-lifecycle.d.ts +17 -0
  11. package/dist/bridge-lifecycle.d.ts.map +1 -0
  12. package/dist/bridge-lifecycle.js +71 -0
  13. package/dist/bridge-lifecycle.js.map +1 -0
  14. package/dist/commands/agent.d.ts +11 -0
  15. package/dist/commands/agent.d.ts.map +1 -0
  16. package/dist/commands/agent.js +171 -0
  17. package/dist/commands/agent.js.map +1 -0
  18. package/dist/commands/buddy.d.ts +20 -0
  19. package/dist/commands/buddy.d.ts.map +1 -0
  20. package/dist/commands/buddy.js +84 -0
  21. package/dist/commands/buddy.js.map +1 -0
  22. package/dist/commands/core.d.ts +13 -0
  23. package/dist/commands/core.d.ts.map +1 -0
  24. package/dist/commands/core.js +107 -0
  25. package/dist/commands/core.js.map +1 -0
  26. package/dist/commands/index.d.ts +16 -0
  27. package/dist/commands/index.d.ts.map +1 -0
  28. package/dist/commands/index.js +132 -0
  29. package/dist/commands/index.js.map +1 -0
  30. package/dist/commands/refresh.d.ts +18 -0
  31. package/dist/commands/refresh.d.ts.map +1 -0
  32. package/dist/commands/refresh.js +55 -0
  33. package/dist/commands/refresh.js.map +1 -0
  34. package/dist/commands/sessions.d.ts +10 -0
  35. package/dist/commands/sessions.d.ts.map +1 -0
  36. package/dist/commands/sessions.js +125 -0
  37. package/dist/commands/sessions.js.map +1 -0
  38. package/dist/commands/skills.d.ts +10 -0
  39. package/dist/commands/skills.d.ts.map +1 -0
  40. package/dist/commands/skills.js +48 -0
  41. package/dist/commands/skills.js.map +1 -0
  42. package/dist/config.d.ts +30 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +77 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/handlers/buddy.d.ts +31 -0
  47. package/dist/handlers/buddy.d.ts.map +1 -0
  48. package/dist/handlers/buddy.js +126 -0
  49. package/dist/handlers/buddy.js.map +1 -0
  50. package/dist/handlers/events.d.ts +65 -0
  51. package/dist/handlers/events.d.ts.map +1 -0
  52. package/dist/handlers/events.js +381 -0
  53. package/dist/handlers/events.js.map +1 -0
  54. package/dist/handlers/file.d.ts +11 -0
  55. package/dist/handlers/file.d.ts.map +1 -0
  56. package/dist/handlers/file.js +138 -0
  57. package/dist/handlers/file.js.map +1 -0
  58. package/dist/handlers/message.d.ts +34 -0
  59. package/dist/handlers/message.d.ts.map +1 -0
  60. package/dist/handlers/message.js +262 -0
  61. package/dist/handlers/message.js.map +1 -0
  62. package/dist/index.d.ts +8 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +82 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/state.d.ts +11 -0
  67. package/dist/state.d.ts.map +1 -0
  68. package/dist/state.js +47 -0
  69. package/dist/state.js.map +1 -0
  70. package/dist/types.d.ts +50 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -0
  73. package/dist/types.js.map +1 -0
  74. package/dist/util/files.d.ts +27 -0
  75. package/dist/util/files.d.ts.map +1 -0
  76. package/dist/util/files.js +75 -0
  77. package/dist/util/files.js.map +1 -0
  78. package/dist/util/telegram.d.ts +60 -0
  79. package/dist/util/telegram.d.ts.map +1 -0
  80. package/dist/util/telegram.js +192 -0
  81. package/dist/util/telegram.js.map +1 -0
  82. package/package.json +49 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/commands/core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core slash commands: /start, /status, /cwd, /new, /stop, /restart\n */\n\nimport { execSync } from \"node:child_process\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport type { Api, Context } from \"grammy\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdStart(ctx: Context): Promise<void> {\n\tawait ctx.reply(\n\t\t\"🤖 *dreb Telegram*\\n\\n\" +\n\t\t\t\"Send me a message and I'll forward it to dreb.\\n\\n\" +\n\t\t\t\"*Session:*\\n\" +\n\t\t\t\"/new \\\\[path\\\\] — Start a fresh session (optionally in a different directory)\\n\" +\n\t\t\t\"/sessions — List recent sessions\\n\" +\n\t\t\t\"/resume <id> — Resume a session\\n\" +\n\t\t\t\"/recent \\\\[N\\\\] — Resend last N messages\\n\\n\" +\n\t\t\t\"*Agent:*\\n\" +\n\t\t\t\"/status — Connection info\\n\" +\n\t\t\t\"/cwd — Working directory\\n\" +\n\t\t\t\"/stats — Token usage & cost\\n\" +\n\t\t\t\"/compact — Compact context\\n\" +\n\t\t\t\"/agents — Background subagents\\n\" +\n\t\t\t\"/model \\\\[pattern\\\\] — View/switch model\\n\" +\n\t\t\t\"/thinking \\\\[level\\\\] — View/set thinking\\n\" +\n\t\t\t\"/skills — List available skills\\n\\n\" +\n\t\t\t\"*Control:*\\n\" +\n\t\t\t\"/stop — Interrupt current task\\n\" +\n\t\t\t\"/restart — Restart the bot\",\n\t\t{ parse_mode: \"Markdown\" },\n\t);\n}\n\nexport async function cmdStatus(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tlet version = \"unknown\";\n\tlet model = \"none\";\n\ttry {\n\t\tif (bridge?.isAlive) {\n\t\t\tversion = await bridge.getVersion();\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) model = `${state.model.provider}/${state.model.id}`;\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /status error: ${e}`);\n\t}\n\n\tconst lines = [\n\t\tbridge?.isAlive ? \"✅ Connected\" : \"⚠️ Not connected (will start on next message)\",\n\t\t`📁 Working dir: \\`${config.workingDir}\\``,\n\t\t`🔧 dreb ${version}`,\n\t\t`🧠 Model: ${model}`,\n\t];\n\n\tif (bridge?.sessionId) {\n\t\tlines.push(`📂 Session: \\`${bridge.sessionId.slice(0, 8)}...\\``);\n\t}\n\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdCwd(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst cwd = userState.effectiveCwd ?? config.workingDir;\n\tawait safeSend(ctx.api, ctx.chat!.id, `📁 Working directory: \\`${cwd}\\``);\n}\n\nexport async function cmdNew(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst pathArg = args.trim();\n\n\tif (pathArg) {\n\t\t// Resolve path (expand ~ and make absolute)\n\t\tconst expanded = pathArg.startsWith(\"~\") ? pathArg.replace(\"~\", homedir()) : pathArg;\n\t\tconst resolved = resolve(expanded);\n\n\t\tif (!existsSync(resolved)) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Directory not found: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\t\tif (!statSync(resolved).isDirectory()) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Not a directory: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = resolved;\n\t\tawait ctx.reply(`🆕 Next message will start a fresh session in \\`${resolved}\\``);\n\t} else {\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = null;\n\t\tawait ctx.reply(\"🆕 Next message will start a fresh session.\");\n\t}\n}\n\nexport async function cmdStop(ctx: Context, _api: Api, userState: UserState): Promise<void> {\n\tuserState.stopRequested = true;\n\n\t// Abort current agent activity — like pressing Esc in the TUI.\n\t// This stops the agent, not the bridge. Session stays connected.\n\tif (userState.bridge?.isAlive) {\n\t\tawait userState.bridge.abort();\n\t}\n\n\tconst parts: string[] = [];\n\tif (userState.bridge?.isStreaming || userState.promptInFlight) parts.push(\"interrupted current task\");\n\tawait ctx.reply(parts.length > 0 ? `🛑 Stopped — ${parts.join(\", \")}.` : \"🛑 Stopped.\");\n}\n\nexport async function cmdRestart(ctx: Context, config: Config): Promise<void> {\n\tawait ctx.reply(\"🔄 Restarting...\");\n\tlog(\"[CMD] /restart — triggering systemctl restart\");\n\ttry {\n\t\texecSync(`systemctl --user restart ${config.serviceName}`, { timeout: 5000 });\n\t} catch {\n\t\t// Process will be killed by systemd restart\n\t}\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Register all slash commands with the bot and Telegram's command menu.
3
+ */
4
+ import type { Bot } from "grammy";
5
+ import type { Config } from "../config.js";
6
+ import type { UserState } from "../types.js";
7
+ /**
8
+ * Register all command handlers on the bot.
9
+ */
10
+ export declare function registerCommands(bot: Bot, config: Config, getUserState: (userId: number) => UserState): void;
11
+ /**
12
+ * Register commands with Telegram's autocomplete menu (static commands only).
13
+ */
14
+ export declare function setMyCommands(bot: Bot): Promise<void>;
15
+ export { refreshCommandsWithSkills } from "./refresh.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAS7C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAyH5G;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3D;AAGD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/**\n * Register all slash commands with the bot and Telegram's command menu.\n */\n\nimport type { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\nimport { cmdAgents, cmdCompact, cmdModel, cmdStats, cmdThinking } from \"./agent.js\";\nimport { cmdBuddy } from \"./buddy.js\";\nimport { cmdCwd, cmdNew, cmdRestart, cmdStart, cmdStatus, cmdStop } from \"./core.js\";\nimport { STATIC_COMMANDS } from \"./refresh.js\";\nimport { cmdRecent, cmdResume, cmdSessions } from \"./sessions.js\";\nimport { cmdSkills } from \"./skills.js\";\n\n/**\n * Register all command handlers on the bot.\n */\nexport function registerCommands(bot: Bot, config: Config, getUserState: (userId: number) => UserState): void {\n\tbot.command(\"start\", (ctx) => cmdStart(ctx));\n\n\tbot.command(\"status\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStatus(ctx, config, us);\n\t});\n\n\tbot.command(\"cwd\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdCwd(ctx, config, us);\n\t});\n\n\tbot.command(\"new\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdNew(ctx, us, args);\n\t});\n\n\tbot.command(\"stop\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStop(ctx, ctx.api, us);\n\t});\n\n\tbot.command(\"restart\", (ctx) => cmdRestart(ctx, config));\n\n\tbot.command(\"sessions\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdSessions(ctx, us, config);\n\t});\n\n\tbot.command(\"resume\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdResume(ctx, us, args, config);\n\t});\n\n\tbot.command(\"recent\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdRecent(ctx, us, args, config);\n\t});\n\n\tbot.command(\"skills\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdSkills(ctx, us, config);\n\t});\n\n\tbot.command(\"compact\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdCompact(ctx, us);\n\t});\n\n\tbot.command(\"agents\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdAgents(ctx, us);\n\t});\n\n\tbot.command(\"stats\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStats(ctx, us);\n\t});\n\n\tbot.command(\"model\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdModel(ctx, us, args);\n\t});\n\n\tbot.command(\"thinking\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdThinking(ctx, us, args);\n\t});\n\n\tbot.command(\"buddy\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdBuddy(ctx, config, us);\n\t});\n\n\t// Dynamic skill commands: /skill_<name> [args]\n\t// Catches any /skill_* command and sends it as a skill invocation prompt\n\tbot.hears(/^\\/skill_([a-z0-9_]+)(?:\\s+(.*))?$/i, async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId);\n\t\tconst chatId = ctx.chat!.id;\n\n\t\t// Convert back from Telegram-safe name to skill name: skill_reddit_reader → reddit-reader\n\t\tconst rawName = ctx.match[1].replace(/_/g, \"-\");\n\t\tconst args = ctx.match[2]?.trim() || \"\";\n\n\t\t// Ensure bridge is alive and session is selected\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Send as a prompt that invokes the skill\n\t\tconst prompt = args ? `Use the ${rawName} skill: ${args}` : `Use the ${rawName} skill`;\n\n\t\t// Show thinking status\n\t\tlet statusMsg: { chat_id: number; message_id: number } | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(\"🛠 _Invoking skill..._\", { parse_mode: \"Markdown\" });\n\t\t\tstatusMsg = { chat_id: sent.chat.id, message_id: sent.message_id };\n\t\t} catch (e) {\n\t\t\tlog(`[SKILL] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Import sendPrompt dynamically to avoid circular deps\n\t\tconst { sendPrompt } = await import(\"../handlers/message.js\");\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message!.message_id,\n\t\t\tuserId: ctx.from!.id,\n\t\t\tprompt,\n\t\t\tstatusMessageId: statusMsg?.message_id ?? null,\n\t\t});\n\t});\n}\n\n/**\n * Register commands with Telegram's autocomplete menu (static commands only).\n */\nexport async function setMyCommands(bot: Bot): Promise<void> {\n\ttry {\n\t\tawait bot.api.setMyCommands(STATIC_COMMANDS);\n\t\tlog(\"[BOT] Registered commands with Telegram\");\n\t} catch (e) {\n\t\tlog(`[BOT] Failed to set commands: ${e}`);\n\t}\n}\n\n// Re-export for use from other modules\nexport { refreshCommandsWithSkills } from \"./refresh.js\";\n"]}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Register all slash commands with the bot and Telegram's command menu.
3
+ */
4
+ import { ensureBridgeWithSession } from "../bridge-lifecycle.js";
5
+ import { log, safeSend } from "../util/telegram.js";
6
+ import { cmdAgents, cmdCompact, cmdModel, cmdStats, cmdThinking } from "./agent.js";
7
+ import { cmdBuddy } from "./buddy.js";
8
+ import { cmdCwd, cmdNew, cmdRestart, cmdStart, cmdStatus, cmdStop } from "./core.js";
9
+ import { STATIC_COMMANDS } from "./refresh.js";
10
+ import { cmdRecent, cmdResume, cmdSessions } from "./sessions.js";
11
+ import { cmdSkills } from "./skills.js";
12
+ /**
13
+ * Register all command handlers on the bot.
14
+ */
15
+ export function registerCommands(bot, config, getUserState) {
16
+ bot.command("start", (ctx) => cmdStart(ctx));
17
+ bot.command("status", (ctx) => {
18
+ const us = getUserState(ctx.from.id);
19
+ return cmdStatus(ctx, config, us);
20
+ });
21
+ bot.command("cwd", (ctx) => {
22
+ const us = getUserState(ctx.from.id);
23
+ return cmdCwd(ctx, config, us);
24
+ });
25
+ bot.command("new", (ctx) => {
26
+ const us = getUserState(ctx.from.id);
27
+ const args = ctx.match;
28
+ return cmdNew(ctx, us, args);
29
+ });
30
+ bot.command("stop", (ctx) => {
31
+ const us = getUserState(ctx.from.id);
32
+ return cmdStop(ctx, ctx.api, us);
33
+ });
34
+ bot.command("restart", (ctx) => cmdRestart(ctx, config));
35
+ bot.command("sessions", (ctx) => {
36
+ const us = getUserState(ctx.from.id);
37
+ return cmdSessions(ctx, us, config);
38
+ });
39
+ bot.command("resume", (ctx) => {
40
+ const us = getUserState(ctx.from.id);
41
+ const args = ctx.match;
42
+ return cmdResume(ctx, us, args, config);
43
+ });
44
+ bot.command("recent", (ctx) => {
45
+ const us = getUserState(ctx.from.id);
46
+ const args = ctx.match;
47
+ return cmdRecent(ctx, us, args, config);
48
+ });
49
+ bot.command("skills", (ctx) => {
50
+ const us = getUserState(ctx.from.id);
51
+ return cmdSkills(ctx, us, config);
52
+ });
53
+ bot.command("compact", (ctx) => {
54
+ const us = getUserState(ctx.from.id);
55
+ return cmdCompact(ctx, us);
56
+ });
57
+ bot.command("agents", (ctx) => {
58
+ const us = getUserState(ctx.from.id);
59
+ return cmdAgents(ctx, us);
60
+ });
61
+ bot.command("stats", (ctx) => {
62
+ const us = getUserState(ctx.from.id);
63
+ return cmdStats(ctx, us);
64
+ });
65
+ bot.command("model", (ctx) => {
66
+ const us = getUserState(ctx.from.id);
67
+ const args = ctx.match;
68
+ return cmdModel(ctx, us, args);
69
+ });
70
+ bot.command("thinking", (ctx) => {
71
+ const us = getUserState(ctx.from.id);
72
+ const args = ctx.match;
73
+ return cmdThinking(ctx, us, args);
74
+ });
75
+ bot.command("buddy", (ctx) => {
76
+ const us = getUserState(ctx.from.id);
77
+ return cmdBuddy(ctx, config, us);
78
+ });
79
+ // Dynamic skill commands: /skill_<name> [args]
80
+ // Catches any /skill_* command and sends it as a skill invocation prompt
81
+ bot.hears(/^\/skill_([a-z0-9_]+)(?:\s+(.*))?$/i, async (ctx) => {
82
+ const userId = ctx.from.id;
83
+ const userState = getUserState(userId);
84
+ const chatId = ctx.chat.id;
85
+ // Convert back from Telegram-safe name to skill name: skill_reddit_reader → reddit-reader
86
+ const rawName = ctx.match[1].replace(/_/g, "-");
87
+ const args = ctx.match[2]?.trim() || "";
88
+ // Ensure bridge is alive and session is selected
89
+ try {
90
+ await ensureBridgeWithSession(config, userState);
91
+ }
92
+ catch (e) {
93
+ await safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);
94
+ return;
95
+ }
96
+ // Send as a prompt that invokes the skill
97
+ const prompt = args ? `Use the ${rawName} skill: ${args}` : `Use the ${rawName} skill`;
98
+ // Show thinking status
99
+ let statusMsg = null;
100
+ try {
101
+ const sent = await ctx.reply("🛠 _Invoking skill..._", { parse_mode: "Markdown" });
102
+ statusMsg = { chat_id: sent.chat.id, message_id: sent.message_id };
103
+ }
104
+ catch (e) {
105
+ log(`[SKILL] Failed to send status: ${e}`);
106
+ }
107
+ // Import sendPrompt dynamically to avoid circular deps
108
+ const { sendPrompt } = await import("../handlers/message.js");
109
+ sendPrompt(ctx.api, userState, {
110
+ chatId: ctx.chat.id,
111
+ replyToId: ctx.message.message_id,
112
+ userId: ctx.from.id,
113
+ prompt,
114
+ statusMessageId: statusMsg?.message_id ?? null,
115
+ });
116
+ });
117
+ }
118
+ /**
119
+ * Register commands with Telegram's autocomplete menu (static commands only).
120
+ */
121
+ export async function setMyCommands(bot) {
122
+ try {
123
+ await bot.api.setMyCommands(STATIC_COMMANDS);
124
+ log("[BOT] Registered commands with Telegram");
125
+ }
126
+ catch (e) {
127
+ log(`[BOT] Failed to set commands: ${e}`);
128
+ }
129
+ }
130
+ // Re-export for use from other modules
131
+ export { refreshCommandsWithSkills } from "./refresh.js";
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Register all slash commands with the bot and Telegram's command menu.\n */\n\nimport type { Bot } from \"grammy\";\nimport { ensureBridgeWithSession } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\nimport { cmdAgents, cmdCompact, cmdModel, cmdStats, cmdThinking } from \"./agent.js\";\nimport { cmdBuddy } from \"./buddy.js\";\nimport { cmdCwd, cmdNew, cmdRestart, cmdStart, cmdStatus, cmdStop } from \"./core.js\";\nimport { STATIC_COMMANDS } from \"./refresh.js\";\nimport { cmdRecent, cmdResume, cmdSessions } from \"./sessions.js\";\nimport { cmdSkills } from \"./skills.js\";\n\n/**\n * Register all command handlers on the bot.\n */\nexport function registerCommands(bot: Bot, config: Config, getUserState: (userId: number) => UserState): void {\n\tbot.command(\"start\", (ctx) => cmdStart(ctx));\n\n\tbot.command(\"status\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStatus(ctx, config, us);\n\t});\n\n\tbot.command(\"cwd\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdCwd(ctx, config, us);\n\t});\n\n\tbot.command(\"new\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdNew(ctx, us, args);\n\t});\n\n\tbot.command(\"stop\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStop(ctx, ctx.api, us);\n\t});\n\n\tbot.command(\"restart\", (ctx) => cmdRestart(ctx, config));\n\n\tbot.command(\"sessions\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdSessions(ctx, us, config);\n\t});\n\n\tbot.command(\"resume\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdResume(ctx, us, args, config);\n\t});\n\n\tbot.command(\"recent\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdRecent(ctx, us, args, config);\n\t});\n\n\tbot.command(\"skills\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdSkills(ctx, us, config);\n\t});\n\n\tbot.command(\"compact\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdCompact(ctx, us);\n\t});\n\n\tbot.command(\"agents\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdAgents(ctx, us);\n\t});\n\n\tbot.command(\"stats\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdStats(ctx, us);\n\t});\n\n\tbot.command(\"model\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdModel(ctx, us, args);\n\t});\n\n\tbot.command(\"thinking\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\tconst args = ctx.match as string;\n\t\treturn cmdThinking(ctx, us, args);\n\t});\n\n\tbot.command(\"buddy\", (ctx) => {\n\t\tconst us = getUserState(ctx.from!.id);\n\t\treturn cmdBuddy(ctx, config, us);\n\t});\n\n\t// Dynamic skill commands: /skill_<name> [args]\n\t// Catches any /skill_* command and sends it as a skill invocation prompt\n\tbot.hears(/^\\/skill_([a-z0-9_]+)(?:\\s+(.*))?$/i, async (ctx) => {\n\t\tconst userId = ctx.from!.id;\n\t\tconst userState = getUserState(userId);\n\t\tconst chatId = ctx.chat!.id;\n\n\t\t// Convert back from Telegram-safe name to skill name: skill_reddit_reader → reddit-reader\n\t\tconst rawName = ctx.match[1].replace(/_/g, \"-\");\n\t\tconst args = ctx.match[2]?.trim() || \"\";\n\n\t\t// Ensure bridge is alive and session is selected\n\t\ttry {\n\t\t\tawait ensureBridgeWithSession(config, userState);\n\t\t} catch (e) {\n\t\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Send as a prompt that invokes the skill\n\t\tconst prompt = args ? `Use the ${rawName} skill: ${args}` : `Use the ${rawName} skill`;\n\n\t\t// Show thinking status\n\t\tlet statusMsg: { chat_id: number; message_id: number } | null = null;\n\t\ttry {\n\t\t\tconst sent = await ctx.reply(\"🛠 _Invoking skill..._\", { parse_mode: \"Markdown\" });\n\t\t\tstatusMsg = { chat_id: sent.chat.id, message_id: sent.message_id };\n\t\t} catch (e) {\n\t\t\tlog(`[SKILL] Failed to send status: ${e}`);\n\t\t}\n\n\t\t// Import sendPrompt dynamically to avoid circular deps\n\t\tconst { sendPrompt } = await import(\"../handlers/message.js\");\n\t\tsendPrompt(ctx.api, userState, {\n\t\t\tchatId: ctx.chat!.id,\n\t\t\treplyToId: ctx.message!.message_id,\n\t\t\tuserId: ctx.from!.id,\n\t\t\tprompt,\n\t\t\tstatusMessageId: statusMsg?.message_id ?? null,\n\t\t});\n\t});\n}\n\n/**\n * Register commands with Telegram's autocomplete menu (static commands only).\n */\nexport async function setMyCommands(bot: Bot): Promise<void> {\n\ttry {\n\t\tawait bot.api.setMyCommands(STATIC_COMMANDS);\n\t\tlog(\"[BOT] Registered commands with Telegram\");\n\t} catch (e) {\n\t\tlog(`[BOT] Failed to set commands: ${e}`);\n\t}\n}\n\n// Re-export for use from other modules\nexport { refreshCommandsWithSkills } from \"./refresh.js\";\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Dynamic command menu refresh — queries skills from the agent bridge
3
+ * and registers them as Telegram bot commands alongside the static ones.
4
+ */
5
+ import type { Bot } from "grammy";
6
+ import type { AgentBridge } from "../agent-bridge.js";
7
+ /** Static bot commands — always present in the command menu */
8
+ export declare const STATIC_COMMANDS: Array<{
9
+ command: string;
10
+ description: string;
11
+ }>;
12
+ /**
13
+ * Refresh the Telegram command menu with dynamic skill commands.
14
+ * Queries the bridge for available skills and adds them as /skill_<name> commands.
15
+ * Call after bridge startup or when the working directory changes.
16
+ */
17
+ export declare function refreshCommandsWithSkills(bot: Bot, bridge: AgentBridge): Promise<void>;
18
+ //# sourceMappingURL=refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,iEAA+D;AAC/D,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAiB3E,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B5F","sourcesContent":["/**\n * Dynamic command menu refresh — queries skills from the agent bridge\n * and registers them as Telegram bot commands alongside the static ones.\n */\n\nimport type { Bot } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { log } from \"../util/telegram.js\";\n\n/** Static bot commands — always present in the command menu */\nexport const STATIC_COMMANDS: Array<{ command: string; description: string }> = [\n\t{ command: \"start\", description: \"Help & command list\" },\n\t{ command: \"status\", description: \"Connection & version info\" },\n\t{ command: \"new\", description: \"Start fresh session [optional: path]\" },\n\t{ command: \"sessions\", description: \"List recent sessions\" },\n\t{ command: \"resume\", description: \"Resume a session by ID\" },\n\t{ command: \"recent\", description: \"Resend last N messages\" },\n\t{ command: \"skills\", description: \"List available skills\" },\n\t{ command: \"stats\", description: \"Token usage & cost\" },\n\t{ command: \"compact\", description: \"Compact context\" },\n\t{ command: \"model\", description: \"View/switch model\" },\n\t{ command: \"thinking\", description: \"View/set thinking level\" },\n\t{ command: \"agents\", description: \"Background subagents\" },\n\t{ command: \"cwd\", description: \"Working directory\" },\n\t{ command: \"stop\", description: \"Interrupt current task\" },\n\t{ command: \"restart\", description: \"Restart the bot\" },\n\t{ command: \"buddy\", description: \"Hatch or manage your companion\" },\n];\n\n/**\n * Refresh the Telegram command menu with dynamic skill commands.\n * Queries the bridge for available skills and adds them as /skill_<name> commands.\n * Call after bridge startup or when the working directory changes.\n */\nexport async function refreshCommandsWithSkills(bot: Bot, bridge: AgentBridge): Promise<void> {\n\ttry {\n\t\tconst commands = await bridge.getCommands();\n\t\tconst skills = commands.filter((c: any) => c.source === \"skill\");\n\n\t\tif (skills.length === 0) {\n\t\t\t// No skills — just use static commands\n\t\t\tawait bot.api.setMyCommands(STATIC_COMMANDS);\n\t\t\treturn;\n\t\t}\n\n\t\t// Map skill names to Telegram-safe command names\n\t\t// skill:reddit-reader → skill_reddit_reader\n\t\tconst skillCommands = skills.map((s: any) => {\n\t\t\tconst name = s.name.replace(\"skill:\", \"\").replace(/-/g, \"_\");\n\t\t\tconst desc = s.description ? s.description.slice(0, 200) : `Invoke ${s.name} skill`;\n\t\t\treturn { command: `skill_${name}`, description: desc };\n\t\t});\n\n\t\t// Telegram limits to 100 commands — static + skills should be well under\n\t\tconst allCommands = [...STATIC_COMMANDS, ...skillCommands];\n\t\tawait bot.api.setMyCommands(allCommands);\n\t\tlog(`[BOT] Registered ${STATIC_COMMANDS.length} static + ${skillCommands.length} skill commands with Telegram`);\n\t} catch (e) {\n\t\tlog(`[BOT] Failed to refresh commands with skills: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Dynamic command menu refresh — queries skills from the agent bridge
3
+ * and registers them as Telegram bot commands alongside the static ones.
4
+ */
5
+ import { log } from "../util/telegram.js";
6
+ /** Static bot commands — always present in the command menu */
7
+ export const STATIC_COMMANDS = [
8
+ { command: "start", description: "Help & command list" },
9
+ { command: "status", description: "Connection & version info" },
10
+ { command: "new", description: "Start fresh session [optional: path]" },
11
+ { command: "sessions", description: "List recent sessions" },
12
+ { command: "resume", description: "Resume a session by ID" },
13
+ { command: "recent", description: "Resend last N messages" },
14
+ { command: "skills", description: "List available skills" },
15
+ { command: "stats", description: "Token usage & cost" },
16
+ { command: "compact", description: "Compact context" },
17
+ { command: "model", description: "View/switch model" },
18
+ { command: "thinking", description: "View/set thinking level" },
19
+ { command: "agents", description: "Background subagents" },
20
+ { command: "cwd", description: "Working directory" },
21
+ { command: "stop", description: "Interrupt current task" },
22
+ { command: "restart", description: "Restart the bot" },
23
+ { command: "buddy", description: "Hatch or manage your companion" },
24
+ ];
25
+ /**
26
+ * Refresh the Telegram command menu with dynamic skill commands.
27
+ * Queries the bridge for available skills and adds them as /skill_<name> commands.
28
+ * Call after bridge startup or when the working directory changes.
29
+ */
30
+ export async function refreshCommandsWithSkills(bot, bridge) {
31
+ try {
32
+ const commands = await bridge.getCommands();
33
+ const skills = commands.filter((c) => c.source === "skill");
34
+ if (skills.length === 0) {
35
+ // No skills — just use static commands
36
+ await bot.api.setMyCommands(STATIC_COMMANDS);
37
+ return;
38
+ }
39
+ // Map skill names to Telegram-safe command names
40
+ // skill:reddit-reader → skill_reddit_reader
41
+ const skillCommands = skills.map((s) => {
42
+ const name = s.name.replace("skill:", "").replace(/-/g, "_");
43
+ const desc = s.description ? s.description.slice(0, 200) : `Invoke ${s.name} skill`;
44
+ return { command: `skill_${name}`, description: desc };
45
+ });
46
+ // Telegram limits to 100 commands — static + skills should be well under
47
+ const allCommands = [...STATIC_COMMANDS, ...skillCommands];
48
+ await bot.api.setMyCommands(allCommands);
49
+ log(`[BOT] Registered ${STATIC_COMMANDS.length} static + ${skillCommands.length} skill commands with Telegram`);
50
+ }
51
+ catch (e) {
52
+ log(`[BOT] Failed to refresh commands with skills: ${e}`);
53
+ }
54
+ }
55
+ //# sourceMappingURL=refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Dynamic command menu refresh — queries skills from the agent bridge\n * and registers them as Telegram bot commands alongside the static ones.\n */\n\nimport type { Bot } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { log } from \"../util/telegram.js\";\n\n/** Static bot commands — always present in the command menu */\nexport const STATIC_COMMANDS: Array<{ command: string; description: string }> = [\n\t{ command: \"start\", description: \"Help & command list\" },\n\t{ command: \"status\", description: \"Connection & version info\" },\n\t{ command: \"new\", description: \"Start fresh session [optional: path]\" },\n\t{ command: \"sessions\", description: \"List recent sessions\" },\n\t{ command: \"resume\", description: \"Resume a session by ID\" },\n\t{ command: \"recent\", description: \"Resend last N messages\" },\n\t{ command: \"skills\", description: \"List available skills\" },\n\t{ command: \"stats\", description: \"Token usage & cost\" },\n\t{ command: \"compact\", description: \"Compact context\" },\n\t{ command: \"model\", description: \"View/switch model\" },\n\t{ command: \"thinking\", description: \"View/set thinking level\" },\n\t{ command: \"agents\", description: \"Background subagents\" },\n\t{ command: \"cwd\", description: \"Working directory\" },\n\t{ command: \"stop\", description: \"Interrupt current task\" },\n\t{ command: \"restart\", description: \"Restart the bot\" },\n\t{ command: \"buddy\", description: \"Hatch or manage your companion\" },\n];\n\n/**\n * Refresh the Telegram command menu with dynamic skill commands.\n * Queries the bridge for available skills and adds them as /skill_<name> commands.\n * Call after bridge startup or when the working directory changes.\n */\nexport async function refreshCommandsWithSkills(bot: Bot, bridge: AgentBridge): Promise<void> {\n\ttry {\n\t\tconst commands = await bridge.getCommands();\n\t\tconst skills = commands.filter((c: any) => c.source === \"skill\");\n\n\t\tif (skills.length === 0) {\n\t\t\t// No skills — just use static commands\n\t\t\tawait bot.api.setMyCommands(STATIC_COMMANDS);\n\t\t\treturn;\n\t\t}\n\n\t\t// Map skill names to Telegram-safe command names\n\t\t// skill:reddit-reader → skill_reddit_reader\n\t\tconst skillCommands = skills.map((s: any) => {\n\t\t\tconst name = s.name.replace(\"skill:\", \"\").replace(/-/g, \"_\");\n\t\t\tconst desc = s.description ? s.description.slice(0, 200) : `Invoke ${s.name} skill`;\n\t\t\treturn { command: `skill_${name}`, description: desc };\n\t\t});\n\n\t\t// Telegram limits to 100 commands — static + skills should be well under\n\t\tconst allCommands = [...STATIC_COMMANDS, ...skillCommands];\n\t\tawait bot.api.setMyCommands(allCommands);\n\t\tlog(`[BOT] Registered ${STATIC_COMMANDS.length} static + ${skillCommands.length} skill commands with Telegram`);\n\t} catch (e) {\n\t\tlog(`[BOT] Failed to refresh commands with skills: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Session slash commands: /sessions, /resume, /recent
3
+ */
4
+ import type { Context } from "grammy";
5
+ import type { Config } from "../config.js";
6
+ import type { UserState } from "../types.js";
7
+ export declare function cmdSessions(ctx: Context, userState: UserState, config: Config): Promise<void>;
8
+ export declare function cmdResume(ctx: Context, userState: UserState, args: string, config: Config): Promise<void>;
9
+ export declare function cmdRecent(ctx: Context, userState: UserState, args: string, config: Config): Promise<void>;
10
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,wBAAsB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCnG;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC/G;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD/G","sourcesContent":["/**\n * Session slash commands: /sessions, /resume, /recent\n */\n\nimport type { Context } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { ensureBridge } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport { setUserSession } from \"../state.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend, sendLong } from \"../util/telegram.js\";\n\nexport async function cmdSessions(ctx: Context, userState: UserState, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\tconst sessions = await bridge.listSessions();\n\tif (sessions.length === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No saved sessions. Send a message to start one.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"📂 *Recent Sessions*:\\n\"];\n\t// Show newest first, max 10\n\tfor (const s of sessions.slice(0, 10)) {\n\t\tconst date = new Date(s.modified);\n\t\tconst ts =\n\t\t\tdate.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" }) +\n\t\t\t\" \" +\n\t\t\tdate.toLocaleTimeString(\"en-US\", { hour: \"2-digit\", minute: \"2-digit\", hour12: false });\n\t\tconst sid = s.id.slice(0, 8);\n\t\tconst preview = s.firstMessage.slice(0, 60);\n\t\tlines.push(`\\`${sid}\\` (${ts})\\n ${preview}`);\n\t}\n\n\tlines.push(\"\\nUse /resume <id> to resume a session.\");\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdResume(ctx: Context, userState: UserState, args: string, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (!args.trim()) {\n\t\tawait safeSend(ctx.api, chatId, \"Usage: /resume <session\\\\_id>\\nUse /sessions to list available sessions.\");\n\t\treturn;\n\t}\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\tconst partialId = args.trim();\n\tconst sessions = await bridge.listSessions();\n\tconst matches = sessions.filter((s) => s.id.startsWith(partialId));\n\n\tif (matches.length === 0) {\n\t\tawait safeSend(ctx.api, chatId, `No session found matching \\`${partialId}\\``);\n\t\treturn;\n\t}\n\tif (matches.length > 1) {\n\t\tawait safeSend(ctx.api, chatId, `Multiple sessions match \\`${partialId}\\` — be more specific.`);\n\t\treturn;\n\t}\n\n\tconst session = matches[0];\n\tconst switched = await bridge.switchSession(session.path);\n\tif (switched) {\n\t\tsetUserSession(ctx.from!.id, session.path);\n\t\tawait safeSend(\n\t\t\tctx.api,\n\t\t\tchatId,\n\t\t\t`✅ Resumed session \\`${session.id.slice(0, 8)}...\\`\\nUse /recent to see recent messages.`,\n\t\t);\n\t} else {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to switch to session \\`${session.id.slice(0, 8)}...\\``);\n\t}\n}\n\nexport async function cmdRecent(ctx: Context, userState: UserState, args: string, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\t// Parse N (default 1)\n\tlet n = 1;\n\tif (args.trim()) {\n\t\tn = Number.parseInt(args.trim(), 10);\n\t\tif (Number.isNaN(n) || n < 1) {\n\t\t\tawait safeSend(ctx.api, chatId, \"Usage: /recent \\\\[N\\\\] — resend last N messages (default 1, max 50)\");\n\t\t\treturn;\n\t\t}\n\t\tn = Math.min(n, 50);\n\t}\n\n\ttry {\n\t\tconst messages = await bridge.getMessages();\n\t\t// Filter to assistant text messages\n\t\tconst assistantTexts: string[] = [];\n\t\tfor (const msg of messages) {\n\t\t\tif (msg.role !== \"assistant\") continue;\n\t\t\tconst content = (msg as any).content;\n\t\t\tif (!Array.isArray(content)) continue;\n\t\t\tfor (const block of content) {\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tassistantTexts.push(block.text.trim());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst recent = assistantTexts.slice(-n);\n\t\tif (recent.length === 0) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No assistant messages found in this session.\");\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = 0; i < recent.length; i++) {\n\t\t\tlet text = recent[i];\n\t\t\tif (recent.length > 1) {\n\t\t\t\ttext = `📨 *Message ${i + 1}/${recent.length}*\\n\\n${text}`;\n\t\t\t}\n\t\t\tawait sendLong(ctx.api, chatId, text);\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /recent error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to retrieve messages: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Session slash commands: /sessions, /resume, /recent
3
+ */
4
+ import { ensureBridge } from "../bridge-lifecycle.js";
5
+ import { setUserSession } from "../state.js";
6
+ import { log, safeSend, sendLong } from "../util/telegram.js";
7
+ export async function cmdSessions(ctx, userState, config) {
8
+ const chatId = ctx.chat.id;
9
+ let bridge;
10
+ try {
11
+ bridge = await ensureBridge(config, userState);
12
+ }
13
+ catch (e) {
14
+ await safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);
15
+ return;
16
+ }
17
+ const sessions = await bridge.listSessions();
18
+ if (sessions.length === 0) {
19
+ await safeSend(ctx.api, chatId, "No saved sessions. Send a message to start one.");
20
+ return;
21
+ }
22
+ const lines = ["📂 *Recent Sessions*:\n"];
23
+ // Show newest first, max 10
24
+ for (const s of sessions.slice(0, 10)) {
25
+ const date = new Date(s.modified);
26
+ const ts = date.toLocaleDateString("en-US", { month: "short", day: "numeric" }) +
27
+ " " +
28
+ date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
29
+ const sid = s.id.slice(0, 8);
30
+ const preview = s.firstMessage.slice(0, 60);
31
+ lines.push(`\`${sid}\` (${ts})\n ${preview}`);
32
+ }
33
+ lines.push("\nUse /resume <id> to resume a session.");
34
+ await safeSend(ctx.api, chatId, lines.join("\n"));
35
+ }
36
+ export async function cmdResume(ctx, userState, args, config) {
37
+ const chatId = ctx.chat.id;
38
+ if (!args.trim()) {
39
+ await safeSend(ctx.api, chatId, "Usage: /resume <session\\_id>\nUse /sessions to list available sessions.");
40
+ return;
41
+ }
42
+ let bridge;
43
+ try {
44
+ bridge = await ensureBridge(config, userState);
45
+ }
46
+ catch (e) {
47
+ await safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);
48
+ return;
49
+ }
50
+ const partialId = args.trim();
51
+ const sessions = await bridge.listSessions();
52
+ const matches = sessions.filter((s) => s.id.startsWith(partialId));
53
+ if (matches.length === 0) {
54
+ await safeSend(ctx.api, chatId, `No session found matching \`${partialId}\``);
55
+ return;
56
+ }
57
+ if (matches.length > 1) {
58
+ await safeSend(ctx.api, chatId, `Multiple sessions match \`${partialId}\` — be more specific.`);
59
+ return;
60
+ }
61
+ const session = matches[0];
62
+ const switched = await bridge.switchSession(session.path);
63
+ if (switched) {
64
+ setUserSession(ctx.from.id, session.path);
65
+ await safeSend(ctx.api, chatId, `✅ Resumed session \`${session.id.slice(0, 8)}...\`\nUse /recent to see recent messages.`);
66
+ }
67
+ else {
68
+ await safeSend(ctx.api, chatId, `❌ Failed to switch to session \`${session.id.slice(0, 8)}...\``);
69
+ }
70
+ }
71
+ export async function cmdRecent(ctx, userState, args, config) {
72
+ const chatId = ctx.chat.id;
73
+ let bridge;
74
+ try {
75
+ bridge = await ensureBridge(config, userState);
76
+ }
77
+ catch (e) {
78
+ await safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);
79
+ return;
80
+ }
81
+ // Parse N (default 1)
82
+ let n = 1;
83
+ if (args.trim()) {
84
+ n = Number.parseInt(args.trim(), 10);
85
+ if (Number.isNaN(n) || n < 1) {
86
+ await safeSend(ctx.api, chatId, "Usage: /recent \\[N\\] — resend last N messages (default 1, max 50)");
87
+ return;
88
+ }
89
+ n = Math.min(n, 50);
90
+ }
91
+ try {
92
+ const messages = await bridge.getMessages();
93
+ // Filter to assistant text messages
94
+ const assistantTexts = [];
95
+ for (const msg of messages) {
96
+ if (msg.role !== "assistant")
97
+ continue;
98
+ const content = msg.content;
99
+ if (!Array.isArray(content))
100
+ continue;
101
+ for (const block of content) {
102
+ if (block.type === "text" && block.text?.trim()) {
103
+ assistantTexts.push(block.text.trim());
104
+ }
105
+ }
106
+ }
107
+ const recent = assistantTexts.slice(-n);
108
+ if (recent.length === 0) {
109
+ await safeSend(ctx.api, chatId, "No assistant messages found in this session.");
110
+ return;
111
+ }
112
+ for (let i = 0; i < recent.length; i++) {
113
+ let text = recent[i];
114
+ if (recent.length > 1) {
115
+ text = `📨 *Message ${i + 1}/${recent.length}*\n\n${text}`;
116
+ }
117
+ await sendLong(ctx.api, chatId, text);
118
+ }
119
+ }
120
+ catch (e) {
121
+ log(`[CMD] /recent error: ${e}`);
122
+ await safeSend(ctx.api, chatId, `❌ Failed to retrieve messages: ${e}`);
123
+ }
124
+ }
125
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session slash commands: /sessions, /resume, /recent\n */\n\nimport type { Context } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { ensureBridge } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport { setUserSession } from \"../state.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend, sendLong } from \"../util/telegram.js\";\n\nexport async function cmdSessions(ctx: Context, userState: UserState, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\tconst sessions = await bridge.listSessions();\n\tif (sessions.length === 0) {\n\t\tawait safeSend(ctx.api, chatId, \"No saved sessions. Send a message to start one.\");\n\t\treturn;\n\t}\n\n\tconst lines = [\"📂 *Recent Sessions*:\\n\"];\n\t// Show newest first, max 10\n\tfor (const s of sessions.slice(0, 10)) {\n\t\tconst date = new Date(s.modified);\n\t\tconst ts =\n\t\t\tdate.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" }) +\n\t\t\t\" \" +\n\t\t\tdate.toLocaleTimeString(\"en-US\", { hour: \"2-digit\", minute: \"2-digit\", hour12: false });\n\t\tconst sid = s.id.slice(0, 8);\n\t\tconst preview = s.firstMessage.slice(0, 60);\n\t\tlines.push(`\\`${sid}\\` (${ts})\\n ${preview}`);\n\t}\n\n\tlines.push(\"\\nUse /resume <id> to resume a session.\");\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdResume(ctx: Context, userState: UserState, args: string, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tif (!args.trim()) {\n\t\tawait safeSend(ctx.api, chatId, \"Usage: /resume <session\\\\_id>\\nUse /sessions to list available sessions.\");\n\t\treturn;\n\t}\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\tconst partialId = args.trim();\n\tconst sessions = await bridge.listSessions();\n\tconst matches = sessions.filter((s) => s.id.startsWith(partialId));\n\n\tif (matches.length === 0) {\n\t\tawait safeSend(ctx.api, chatId, `No session found matching \\`${partialId}\\``);\n\t\treturn;\n\t}\n\tif (matches.length > 1) {\n\t\tawait safeSend(ctx.api, chatId, `Multiple sessions match \\`${partialId}\\` — be more specific.`);\n\t\treturn;\n\t}\n\n\tconst session = matches[0];\n\tconst switched = await bridge.switchSession(session.path);\n\tif (switched) {\n\t\tsetUserSession(ctx.from!.id, session.path);\n\t\tawait safeSend(\n\t\t\tctx.api,\n\t\t\tchatId,\n\t\t\t`✅ Resumed session \\`${session.id.slice(0, 8)}...\\`\\nUse /recent to see recent messages.`,\n\t\t);\n\t} else {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to switch to session \\`${session.id.slice(0, 8)}...\\``);\n\t}\n}\n\nexport async function cmdRecent(ctx: Context, userState: UserState, args: string, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\t// Parse N (default 1)\n\tlet n = 1;\n\tif (args.trim()) {\n\t\tn = Number.parseInt(args.trim(), 10);\n\t\tif (Number.isNaN(n) || n < 1) {\n\t\t\tawait safeSend(ctx.api, chatId, \"Usage: /recent \\\\[N\\\\] — resend last N messages (default 1, max 50)\");\n\t\t\treturn;\n\t\t}\n\t\tn = Math.min(n, 50);\n\t}\n\n\ttry {\n\t\tconst messages = await bridge.getMessages();\n\t\t// Filter to assistant text messages\n\t\tconst assistantTexts: string[] = [];\n\t\tfor (const msg of messages) {\n\t\t\tif (msg.role !== \"assistant\") continue;\n\t\t\tconst content = (msg as any).content;\n\t\t\tif (!Array.isArray(content)) continue;\n\t\t\tfor (const block of content) {\n\t\t\t\tif (block.type === \"text\" && block.text?.trim()) {\n\t\t\t\t\tassistantTexts.push(block.text.trim());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst recent = assistantTexts.slice(-n);\n\t\tif (recent.length === 0) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No assistant messages found in this session.\");\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = 0; i < recent.length; i++) {\n\t\t\tlet text = recent[i];\n\t\t\tif (recent.length > 1) {\n\t\t\t\ttext = `📨 *Message ${i + 1}/${recent.length}*\\n\\n${text}`;\n\t\t\t}\n\t\t\tawait sendLong(ctx.api, chatId, text);\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /recent error: ${e}`);\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to retrieve messages: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Skills slash command: /skills — list available skills from the agent.
3
+ * Also refreshes the Telegram command menu with skill-specific commands.
4
+ */
5
+ import type { Bot, Context } from "grammy";
6
+ import type { Config } from "../config.js";
7
+ import type { UserState } from "../types.js";
8
+ export declare function setSkillsBot(bot: Bot): void;
9
+ export declare function cmdSkills(ctx: Context, userState: UserState, config: Config): Promise<void>;
10
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAG3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAE3C;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCjG","sourcesContent":["/**\n * Skills slash command: /skills — list available skills from the agent.\n * Also refreshes the Telegram command menu with skill-specific commands.\n */\n\nimport type { Bot, Context } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { ensureBridge } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { safeSend } from \"../util/telegram.js\";\nimport { refreshCommandsWithSkills } from \"./refresh.js\";\n\n/** Store bot reference so /skills can refresh command menu */\nlet _bot: Bot | null = null;\nexport function setSkillsBot(bot: Bot): void {\n\t_bot = bot;\n}\n\nexport async function cmdSkills(ctx: Context, userState: UserState, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst commands = await bridge.getCommands();\n\t\tconst skills = commands.filter((c: any) => c.source === \"skill\");\n\n\t\tif (skills.length === 0) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No skills available in the current session.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"🛠 *Available Skills*\\n\"];\n\t\tfor (const skill of skills) {\n\t\t\tconst name = skill.name.replace(\"skill:\", \"\");\n\t\t\tconst safeName = name.replace(/-/g, \"_\");\n\t\t\tconst desc = skill.description ? ` — ${skill.description.slice(0, 100)}` : \"\";\n\t\t\tlines.push(`• /skill\\\\_${safeName}${desc}`);\n\t\t}\n\t\tlines.push(\"\\n_Tap a command above or invoke via conversation_\");\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\n\t\t// Also refresh the command menu so skills appear in autocomplete\n\t\tif (_bot) {\n\t\t\tawait refreshCommandsWithSkills(_bot, bridge);\n\t\t}\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to list skills: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Skills slash command: /skills — list available skills from the agent.
3
+ * Also refreshes the Telegram command menu with skill-specific commands.
4
+ */
5
+ import { ensureBridge } from "../bridge-lifecycle.js";
6
+ import { safeSend } from "../util/telegram.js";
7
+ import { refreshCommandsWithSkills } from "./refresh.js";
8
+ /** Store bot reference so /skills can refresh command menu */
9
+ let _bot = null;
10
+ export function setSkillsBot(bot) {
11
+ _bot = bot;
12
+ }
13
+ export async function cmdSkills(ctx, userState, config) {
14
+ const chatId = ctx.chat.id;
15
+ let bridge;
16
+ try {
17
+ bridge = await ensureBridge(config, userState);
18
+ }
19
+ catch (e) {
20
+ await safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);
21
+ return;
22
+ }
23
+ try {
24
+ const commands = await bridge.getCommands();
25
+ const skills = commands.filter((c) => c.source === "skill");
26
+ if (skills.length === 0) {
27
+ await safeSend(ctx.api, chatId, "No skills available in the current session.");
28
+ return;
29
+ }
30
+ const lines = ["🛠 *Available Skills*\n"];
31
+ for (const skill of skills) {
32
+ const name = skill.name.replace("skill:", "");
33
+ const safeName = name.replace(/-/g, "_");
34
+ const desc = skill.description ? ` — ${skill.description.slice(0, 100)}` : "";
35
+ lines.push(`• /skill\\_${safeName}${desc}`);
36
+ }
37
+ lines.push("\n_Tap a command above or invoke via conversation_");
38
+ await safeSend(ctx.api, chatId, lines.join("\n"));
39
+ // Also refresh the command menu so skills appear in autocomplete
40
+ if (_bot) {
41
+ await refreshCommandsWithSkills(_bot, bridge);
42
+ }
43
+ }
44
+ catch (e) {
45
+ await safeSend(ctx.api, chatId, `❌ Failed to list skills: ${e}`);
46
+ }
47
+ }
48
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD,8DAA8D;AAC9D,IAAI,IAAI,GAAe,IAAI,CAAC;AAC5B,MAAM,UAAU,YAAY,CAAC,GAAQ,EAAQ;IAC5C,IAAI,GAAG,GAAG,CAAC;AAAA,CACX;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAY,EAAE,SAAoB,EAAE,MAAc,EAAiB;IAClG,MAAM,MAAM,GAAG,GAAG,CAAC,IAAK,CAAC,EAAE,CAAC;IAE5B,IAAI,MAAmB,CAAC;IACxB,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,8BAA4B,CAAC,EAAE,CAAC,CAAC;QACjE,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;QAEjE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,6CAA6C,CAAC,CAAC;YAC/E,OAAO;QACR,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,2BAAwB,CAAC,CAAC;QACzC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,QAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,KAAK,CAAC,IAAI,CAAC,gBAAc,QAAQ,GAAG,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QAEjE,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAElD,iEAAiE;QACjE,IAAI,IAAI,EAAE,CAAC;YACV,MAAM,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,8BAA4B,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;AAAA,CACD","sourcesContent":["/**\n * Skills slash command: /skills — list available skills from the agent.\n * Also refreshes the Telegram command menu with skill-specific commands.\n */\n\nimport type { Bot, Context } from \"grammy\";\nimport type { AgentBridge } from \"../agent-bridge.js\";\nimport { ensureBridge } from \"../bridge-lifecycle.js\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { safeSend } from \"../util/telegram.js\";\nimport { refreshCommandsWithSkills } from \"./refresh.js\";\n\n/** Store bot reference so /skills can refresh command menu */\nlet _bot: Bot | null = null;\nexport function setSkillsBot(bot: Bot): void {\n\t_bot = bot;\n}\n\nexport async function cmdSkills(ctx: Context, userState: UserState, config: Config): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\n\tlet bridge: AgentBridge;\n\ttry {\n\t\tbridge = await ensureBridge(config, userState);\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to start agent: ${e}`);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst commands = await bridge.getCommands();\n\t\tconst skills = commands.filter((c: any) => c.source === \"skill\");\n\n\t\tif (skills.length === 0) {\n\t\t\tawait safeSend(ctx.api, chatId, \"No skills available in the current session.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst lines = [\"🛠 *Available Skills*\\n\"];\n\t\tfor (const skill of skills) {\n\t\t\tconst name = skill.name.replace(\"skill:\", \"\");\n\t\t\tconst safeName = name.replace(/-/g, \"_\");\n\t\t\tconst desc = skill.description ? ` — ${skill.description.slice(0, 100)}` : \"\";\n\t\t\tlines.push(`• /skill\\\\_${safeName}${desc}`);\n\t\t}\n\t\tlines.push(\"\\n_Tap a command above or invoke via conversation_\");\n\n\t\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n\n\t\t// Also refresh the command menu so skills appear in autocomplete\n\t\tif (_bot) {\n\t\t\tawait refreshCommandsWithSkills(_bot, bridge);\n\t\t}\n\t} catch (e) {\n\t\tawait safeSend(ctx.api, chatId, `❌ Failed to list skills: ${e}`);\n\t}\n}\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Configuration loaded from environment variables.
3
+ * All secrets must come from env vars — never hardcode tokens.
4
+ *
5
+ * If TELEGRAM_BOT_TOKEN is not already set, we auto-load from
6
+ * ~/.dreb/secrets/telegram.env (the same file the systemd service uses).
7
+ * This way `node dist/index.js` works without manual env setup.
8
+ */
9
+ export interface Config {
10
+ /** Telegram Bot API token */
11
+ botToken: string;
12
+ /** Authorized Telegram user IDs */
13
+ allowedUserIds: number[];
14
+ /** Working directory for dreb sessions */
15
+ workingDir: string;
16
+ /** Path to the dreb CLI binary */
17
+ drebPath: string;
18
+ /** Systemd service name for /restart */
19
+ serviceName: string;
20
+ /** Provider to use for dreb */
21
+ provider?: string;
22
+ /** Model to use for dreb */
23
+ model?: string;
24
+ }
25
+ /** Default path to the secrets env file */
26
+ export declare const DEFAULT_SECRETS_FILE: string;
27
+ /** Default path to shared provider API keys */
28
+ export declare const DEFAULT_PROVIDERS_FILE: string;
29
+ export declare function loadConfig(secretsFile?: string): Config;
30
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,MAAM;IACtB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,QAAsD,CAAC;AAExF,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB,QAAuD,CAAC;AAgC3F,wBAAgB,UAAU,CAAC,WAAW,GAAE,MAA6B,GAAG,MAAM,CAsC7E","sourcesContent":["/**\n * Configuration loaded from environment variables.\n * All secrets must come from env vars — never hardcode tokens.\n *\n * If TELEGRAM_BOT_TOKEN is not already set, we auto-load from\n * ~/.dreb/secrets/telegram.env (the same file the systemd service uses).\n * This way `node dist/index.js` works without manual env setup.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface Config {\n\t/** Telegram Bot API token */\n\tbotToken: string;\n\t/** Authorized Telegram user IDs */\n\tallowedUserIds: number[];\n\t/** Working directory for dreb sessions */\n\tworkingDir: string;\n\t/** Path to the dreb CLI binary */\n\tdrebPath: string;\n\t/** Systemd service name for /restart */\n\tserviceName: string;\n\t/** Provider to use for dreb */\n\tprovider?: string;\n\t/** Model to use for dreb */\n\tmodel?: string;\n}\n\n/** Default path to the secrets env file */\nexport const DEFAULT_SECRETS_FILE = join(homedir(), \".dreb\", \"secrets\", \"telegram.env\");\n\n/** Default path to shared provider API keys */\nexport const DEFAULT_PROVIDERS_FILE = join(homedir(), \".dreb\", \"secrets\", \"providers.env\");\n\n/**\n * Load KEY=VALUE pairs from a file into process.env (without overwriting).\n * Handles quoting, comments, and empty lines.\n */\nfunction loadEnvFile(path: string): void {\n\tif (!existsSync(path)) return;\n\n\tconst content = readFileSync(path, \"utf-8\");\n\tfor (const line of content.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n\t\tconst eqIndex = trimmed.indexOf(\"=\");\n\t\tif (eqIndex === -1) continue;\n\n\t\tconst key = trimmed.slice(0, eqIndex).trim();\n\t\tlet value = trimmed.slice(eqIndex + 1).trim();\n\n\t\t// Strip surrounding quotes\n\t\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t}\n\n\t\t// Don't overwrite existing env vars (explicit env takes priority)\n\t\tif (!(key in process.env)) {\n\t\t\tprocess.env[key] = value;\n\t\t}\n\t}\n}\n\nexport function loadConfig(secretsFile: string = DEFAULT_SECRETS_FILE): Config {\n\t// Auto-load provider API keys (shared with dreb CLI)\n\tloadEnvFile(DEFAULT_PROVIDERS_FILE);\n\n\t// Auto-load telegram-specific secrets\n\tloadEnvFile(secretsFile);\n\n\tconst botToken = process.env.TELEGRAM_BOT_TOKEN;\n\tif (!botToken) {\n\t\tthrow new Error(\n\t\t\t`TELEGRAM_BOT_TOKEN not set.\\n\\n` +\n\t\t\t\t`Either:\\n` +\n\t\t\t\t` 1. Set the environment variable directly, or\\n` +\n\t\t\t\t` 2. Create ${secretsFile} with:\\n\\n` +\n\t\t\t\t` TELEGRAM_BOT_TOKEN=your-token-here\\n` +\n\t\t\t\t` ALLOWED_USER_IDS=your-user-id-here\\n\\n` +\n\t\t\t\t` Then: chmod 600 ${secretsFile}`,\n\t\t);\n\t}\n\n\tconst allowedUserIds = (process.env.ALLOWED_USER_IDS || \"\")\n\t\t.split(\",\")\n\t\t.filter((id) => id.trim())\n\t\t.map((id) => {\n\t\t\tconst n = Number.parseInt(id.trim(), 10);\n\t\t\tif (Number.isNaN(n)) throw new Error(`Invalid user ID: ${id}`);\n\t\t\treturn n;\n\t\t});\n\n\treturn {\n\t\tbotToken,\n\t\tallowedUserIds,\n\t\tworkingDir: process.env.DREB_WORKING_DIR || process.env.HOME || \"/\",\n\t\tdrebPath: process.env.DREB_PATH || \"dreb\",\n\t\tserviceName: process.env.DREB_TELEGRAM_SERVICE || \"dreb-telegram\",\n\t\tprovider: process.env.DREB_PROVIDER,\n\t\tmodel: process.env.DREB_MODEL,\n\t};\n}\n"]}