claude-telegram 0.1.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 (46) hide show
  1. package/README.md +283 -0
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +140 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/src/activity.d.ts +17 -0
  7. package/dist/src/activity.d.ts.map +1 -0
  8. package/dist/src/activity.js +91 -0
  9. package/dist/src/activity.js.map +1 -0
  10. package/dist/src/bot.d.ts +16 -0
  11. package/dist/src/bot.d.ts.map +1 -0
  12. package/dist/src/bot.js +385 -0
  13. package/dist/src/bot.js.map +1 -0
  14. package/dist/src/claude.d.ts +18 -0
  15. package/dist/src/claude.d.ts.map +1 -0
  16. package/dist/src/claude.js +195 -0
  17. package/dist/src/claude.js.map +1 -0
  18. package/dist/src/config.d.ts +6 -0
  19. package/dist/src/config.d.ts.map +1 -0
  20. package/dist/src/config.js +89 -0
  21. package/dist/src/config.js.map +1 -0
  22. package/dist/src/index.d.ts +6 -0
  23. package/dist/src/index.d.ts.map +1 -0
  24. package/dist/src/index.js +4 -0
  25. package/dist/src/index.js.map +1 -0
  26. package/dist/src/modules.d.ts +60 -0
  27. package/dist/src/modules.d.ts.map +1 -0
  28. package/dist/src/modules.js +77 -0
  29. package/dist/src/modules.js.map +1 -0
  30. package/dist/src/sender.d.ts +7 -0
  31. package/dist/src/sender.d.ts.map +1 -0
  32. package/dist/src/sender.js +89 -0
  33. package/dist/src/sender.js.map +1 -0
  34. package/dist/src/session.d.ts +25 -0
  35. package/dist/src/session.d.ts.map +1 -0
  36. package/dist/src/session.js +64 -0
  37. package/dist/src/session.js.map +1 -0
  38. package/dist/src/shutdown.d.ts +22 -0
  39. package/dist/src/shutdown.d.ts.map +1 -0
  40. package/dist/src/shutdown.js +80 -0
  41. package/dist/src/shutdown.js.map +1 -0
  42. package/dist/src/types.d.ts +59 -0
  43. package/dist/src/types.d.ts.map +1 -0
  44. package/dist/src/types.js +2 -0
  45. package/dist/src/types.js.map +1 -0
  46. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,283 @@
1
+ # claude-telegram
2
+
3
+ A simple and modular Telegram orchestrator on top of Claude Code CLI.
4
+
5
+ One npm package that connects a Telegram bot to Claude Code via `--resume` sessions, with whitelist access control and live activity status.
6
+
7
+ ## claude-telegram vs OpenClaw
8
+
9
+ | | claude-telegram | [OpenClaw](https://github.com/openclaw/openclaw) |
10
+ |---|---|---|
11
+ | Core | ~1500 LOC, one dependency (grammY) | 50+ integrations, large codebase |
12
+ | Approach | Minimal orchestrator — Claude Code does the work | Full-featured AI assistant platform |
13
+ | Extensibility | Module system — add anything you need | Built-in, growing feature set |
14
+ | Control | You own the code, easy to audit and modify | Community-driven, fast-moving |
15
+ | Setup | `npx claude-telegram start` | Multi-step setup |
16
+
17
+ > Both are valid choices. claude-telegram is for those who prefer a small, predictable core that they extend themselves.
18
+
19
+ ## Installation
20
+
21
+ **1. Get a server** — a VPS, a home PC, anything with internet access.
22
+
23
+ **2. Install Claude Code:**
24
+ ```bash
25
+ curl -fsSL https://claude.ai/install.sh | bash
26
+ ```
27
+
28
+ **3. Give Claude the link and ask it to install:**
29
+ ```bash
30
+ claude "install claude-telegram from github.com/bluzir/claude-telegram and set it up for my Telegram bot"
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ mkdir my-agent && cd my-agent
37
+ echo "You are a helpful assistant." > CLAUDE.md
38
+
39
+ cat > claude-telegram.yaml << 'EOF'
40
+ token: ${MY_BOT_TOKEN}
41
+ workspace: .
42
+ whitelist: [YOUR_USER_ID]
43
+ permission_mode: acceptEdits
44
+ EOF
45
+
46
+ export MY_BOT_TOKEN="123456:ABC-DEF..."
47
+ npx claude-telegram start
48
+ ```
49
+
50
+ Don't know your Telegram user ID? Run `npx claude-telegram whoami` and send a message to the bot.
51
+
52
+ ## Config
53
+
54
+ Create `claude-telegram.yaml` in your project root:
55
+
56
+ ```yaml
57
+ token: ${TELEGRAM_BOT_TOKEN} # env var interpolation
58
+ workspace: /path/to/workspace # cwd for Claude CLI
59
+
60
+ # Who can use the bot (Telegram user IDs)
61
+ # Empty list = NO ONE (secure by default)
62
+ whitelist:
63
+ - 16643982
64
+
65
+ # What Claude can do
66
+ # default | acceptEdits | bypassPermissions
67
+ permission_mode: acceptEdits
68
+
69
+ # --- Optional ---
70
+ # claude_path: /usr/local/bin/claude # default: "claude" from PATH
71
+ # timeout: 300 # seconds, default: 300
72
+ # model: sonnet # model override
73
+ # system_prompt: "Reply in Russian" # injected into every call
74
+ # add_dirs: # additional dirs for Claude
75
+ # - /path/to/shared/data
76
+
77
+ # modules: # optional plugin modules (loaded at startup)
78
+ # - import: ./modules/voice.mjs # resolved relative to `workspace`
79
+ # options:
80
+ # provider: openai
81
+ # - import: claude-telegram-whoop-module
82
+ # options:
83
+ # client_id: ${WHOOP_CLIENT_ID}
84
+ ```
85
+
86
+ Environment variables are interpolated with `${VAR_NAME}` syntax.
87
+
88
+ ## Workspace
89
+
90
+ The `workspace` field in config is just the working directory (`cwd`) for Claude CLI. It can be any directory — claude-telegram doesn't care what's inside. Claude Code will use whatever `CLAUDE.md`, `.claude/agents/`, `.claude/skills/`, and other project files it finds there, exactly as it would in the terminal.
91
+
92
+ ### Simple setup
93
+
94
+ The simplest workspace is a directory with just a `CLAUDE.md`:
95
+
96
+ ```
97
+ my-agent/
98
+ ├── CLAUDE.md # Instructions for Claude
99
+ ├── claude-telegram.yaml # Bot config
100
+ └── data/ # Runtime data (auto-created)
101
+ ```
102
+
103
+ ### Using an existing project
104
+
105
+ Any project that works with Claude Code works with claude-telegram — just point `workspace` at it:
106
+
107
+ ```bash
108
+ # Clone any project that has CLAUDE.md / .claude/ setup
109
+ git clone https://github.com/bluzir/claude-pipe
110
+ ```
111
+
112
+ ```yaml
113
+ # claude-telegram.yaml
114
+ token: ${MY_BOT_TOKEN}
115
+ workspace: ./claude-pipe/examples/research-pipeline
116
+ whitelist: [YOUR_USER_ID]
117
+ permission_mode: acceptEdits
118
+ ```
119
+
120
+ Claude will see the project's `CLAUDE.md`, agents, skills, commands, MCP servers — everything. You don't need to copy or restructure anything.
121
+
122
+ ### Multi-agent setup
123
+
124
+ Run multiple bots, each pointing to its own project:
125
+
126
+ ```yaml
127
+ # researcher.yaml
128
+ token: ${RESEARCHER_BOT_TOKEN}
129
+ workspace: ./research-pipeline
130
+ whitelist: [YOUR_USER_ID]
131
+ permission_mode: acceptEdits
132
+ timeout: 600
133
+ system_prompt: "You are a research agent. Be thorough and cite sources."
134
+ add_dirs:
135
+ - ./shared
136
+ ```
137
+
138
+ ```yaml
139
+ # assistant.yaml
140
+ token: ${ASSISTANT_BOT_TOKEN}
141
+ workspace: ./assistant
142
+ whitelist: [YOUR_USER_ID]
143
+ permission_mode: acceptEdits
144
+ ```
145
+
146
+ ```bash
147
+ npx claude-telegram start --config researcher.yaml
148
+ npx claude-telegram start --config assistant.yaml
149
+ ```
150
+
151
+ Each bot gets its own workspace, sessions, and Telegram token. They share nothing unless you explicitly use `add_dirs`.
152
+
153
+ ## Modules
154
+
155
+ You can extend the bot without bloating the core by adding optional modules that register extra handlers (voice/video, API integrations, etc.).
156
+
157
+ Module import rules:
158
+ - Relative paths are resolved against `workspace`
159
+ - Anything else is treated as a package specifier and resolved by Node
160
+ - Modules must export a default object or a default factory function
161
+
162
+ Minimal module example (`{workspace}/modules/hello.mjs`):
163
+
164
+ ```js
165
+ export default function createModule() {
166
+ return {
167
+ name: "hello",
168
+ commands: [{ command: "/hi", description: "Say hi" }],
169
+ register({ bot, dispatchToClaude }) {
170
+ bot.command("hi", async (ctx) => {
171
+ await dispatchToClaude(ctx, "Say hi in one sentence.");
172
+ });
173
+ },
174
+ };
175
+ }
176
+ ```
177
+
178
+ ### Hooks (memory, security, post-processing)
179
+
180
+ Modules can also hook into the request pipeline:
181
+
182
+ - `beforeClaude(ctx, message)` — deny or transform the user's message before it is sent to Claude
183
+ - `afterClaude(ctx, result)` — observe/transform Claude result before it is sent back to Telegram
184
+
185
+ Security example (deny messages containing a secret keyword):
186
+
187
+ ```js
188
+ export default function createModule() {
189
+ return {
190
+ name: "security",
191
+ async beforeClaude(ctx, message) {
192
+ if (message.includes("OPENAI_API_KEY")) {
193
+ return { action: "deny", reply: "Denied: looks like a secret." };
194
+ }
195
+ return { action: "continue" };
196
+ },
197
+ };
198
+ }
199
+ ```
200
+
201
+ Memory-ish example (prepend extra context):
202
+
203
+ ```js
204
+ export default function createModule() {
205
+ return {
206
+ name: "memory",
207
+ async beforeClaude(ctx, message) {
208
+ const extraContext = "Context: you are talking to the same user as before.";
209
+ return { action: "continue", message: `${extraContext}\n\n${message}` };
210
+ },
211
+ async afterClaude(ctx, result) {
212
+ // Store result.output somewhere if you want (file/db/vector store).
213
+ return result;
214
+ },
215
+ };
216
+ }
217
+ ```
218
+
219
+ ## CLI
220
+
221
+ ```bash
222
+ npx claude-telegram start # uses claude-telegram.yaml in CWD
223
+ npx claude-telegram start --config ./my.yaml # custom config path
224
+ npx claude-telegram check # validate config + claude CLI
225
+ npx claude-telegram whoami # get your Telegram user ID
226
+ ```
227
+
228
+ ## Commands
229
+
230
+ | Command | Description |
231
+ |---------|-------------|
232
+ | `/start` | Welcome message |
233
+ | `/cancel` | Stop current request |
234
+ | `/clear` | Reset conversation (new session) |
235
+ | `/help` | Show available commands |
236
+
237
+ ## How It Works
238
+
239
+ - Each user gets a persistent Claude session via `--resume <sessionId>`
240
+ - Sessions survive bot restarts (stored in `~/.claude/` by Claude CLI)
241
+ - Session mapping stored in `{workspace}/data/.claude-telegram/sessions.json`
242
+ - Bot responds in private chats only (ignores group/supergroup/channel)
243
+ - One message at a time per user (concurrent messages get "Still working..." reply)
244
+ - Live activity status shows what Claude is doing (reading, editing, searching, etc.)
245
+ - Messages are split at 3800 chars and formatted as Telegram MarkdownV2
246
+
247
+ ## Programmatic API
248
+
249
+ ```typescript
250
+ import { createBot, loadConfig } from "claude-telegram";
251
+
252
+ // From config file
253
+ const config = loadConfig("./claude-telegram.yaml");
254
+
255
+ // Or build config directly
256
+ const bot = createBot({
257
+ token: process.env.BOT_TOKEN!,
258
+ workspace: "/path/to/workspace",
259
+ whitelist: [16643982],
260
+ permissionMode: "acceptEdits",
261
+ claudePath: "claude",
262
+ timeout: 300,
263
+ });
264
+
265
+ await bot.start();
266
+ ```
267
+
268
+ ## What's NOT Included
269
+
270
+ This is intentionally minimal. Not included:
271
+
272
+ - Voice/photo messages
273
+ - Multi-bot routing or gateway
274
+ - Approval system (use Claude CLI's `--permission-mode`)
275
+ - Budget tracking
276
+ - Web dashboard
277
+ - Queue system (one message at a time, extras are rejected)
278
+
279
+ Any of these can be added as a [module](#modules) without touching the core.
280
+
281
+ ## License
282
+
283
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { loadConfig } from "../src/config.js";
4
+ import { startBot } from "../src/bot.js";
5
+ import { Bot } from "grammy";
6
+ const args = process.argv.slice(2);
7
+ const command = args[0];
8
+ function getConfigPath() {
9
+ const configIdx = args.indexOf("--config");
10
+ if (configIdx !== -1 && args[configIdx + 1]) {
11
+ return args[configIdx + 1];
12
+ }
13
+ return undefined;
14
+ }
15
+ async function cmdStart() {
16
+ const config = loadConfig(getConfigPath());
17
+ await startBot(config);
18
+ }
19
+ function cmdCheck() {
20
+ console.log("[check] Validating config...");
21
+ let config;
22
+ try {
23
+ config = loadConfig(getConfigPath());
24
+ console.log(` ✓ Config loaded`);
25
+ console.log(` ✓ Workspace: ${config.workspace}`);
26
+ console.log(` ✓ Whitelist: ${config.whitelist.length} user(s)`);
27
+ console.log(` ✓ Permission mode: ${config.permissionMode}`);
28
+ }
29
+ catch (err) {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ console.error(` ✗ Config error: ${msg}`);
32
+ process.exit(1);
33
+ }
34
+ // Check Claude CLI
35
+ try {
36
+ const version = execFileSync(config.claudePath, ["--version"], {
37
+ encoding: "utf-8",
38
+ }).trim();
39
+ console.log(` ✓ Claude CLI: ${version}`);
40
+ }
41
+ catch {
42
+ console.error(` ✗ Claude CLI not found or not executable: ${config.claudePath}`);
43
+ process.exit(1);
44
+ }
45
+ // Sanity-check required flags used by this package (no API calls).
46
+ try {
47
+ const help = execFileSync(config.claudePath, ["--help"], {
48
+ encoding: "utf-8",
49
+ });
50
+ const required = [
51
+ "--output-format",
52
+ "stream-json",
53
+ "--permission-mode",
54
+ "--resume",
55
+ "--session-id",
56
+ ];
57
+ const missing = required.filter((s) => !help.includes(s));
58
+ if (missing.length > 0) {
59
+ console.error(` ✗ Claude CLI is missing required flags: ${missing.join(", ")}`);
60
+ process.exit(1);
61
+ }
62
+ console.log(" ✓ Claude CLI flags look compatible");
63
+ }
64
+ catch {
65
+ console.error(" ✗ Failed to validate Claude CLI help output");
66
+ process.exit(1);
67
+ }
68
+ console.log("\nAll checks passed.");
69
+ }
70
+ async function cmdWhoami() {
71
+ // Determine token: from --config or env
72
+ let token;
73
+ try {
74
+ const config = loadConfig(getConfigPath());
75
+ token = config.token;
76
+ }
77
+ catch {
78
+ // If no config, try env directly
79
+ token = process.env.TELEGRAM_BOT_TOKEN;
80
+ }
81
+ if (!token) {
82
+ console.error("No bot token found. Provide a config file or set TELEGRAM_BOT_TOKEN.");
83
+ process.exit(1);
84
+ }
85
+ const bot = new Bot(token);
86
+ bot.on("message", async (ctx) => {
87
+ if (ctx.chat?.type !== "private") {
88
+ try {
89
+ await ctx.reply("Please message me in a private chat.");
90
+ }
91
+ catch {
92
+ // Ignore
93
+ }
94
+ return;
95
+ }
96
+ const userId = ctx.from?.id;
97
+ const username = ctx.from?.username || "(no username)";
98
+ const name = [ctx.from?.first_name, ctx.from?.last_name].filter(Boolean).join(" ") ||
99
+ "(no name)";
100
+ await ctx.reply(`Your Telegram info:\n\n` +
101
+ `User ID: ${userId}\n` +
102
+ `Username: @${username}\n` +
103
+ `Name: ${name}\n\n` +
104
+ `Add ${userId} to your whitelist config.`);
105
+ });
106
+ console.log("[whoami] Bot started. Send any message to get your user ID.");
107
+ console.log("[whoami] Press Ctrl+C to stop.\n");
108
+ await bot.start();
109
+ }
110
+ // --- Main ---
111
+ switch (command) {
112
+ case "start":
113
+ cmdStart().catch((err) => {
114
+ console.error("Fatal:", err instanceof Error ? err.message : err);
115
+ process.exit(1);
116
+ });
117
+ break;
118
+ case "check":
119
+ cmdCheck();
120
+ break;
121
+ case "whoami":
122
+ cmdWhoami().catch((err) => {
123
+ console.error("Fatal:", err instanceof Error ? err.message : err);
124
+ process.exit(1);
125
+ });
126
+ break;
127
+ default:
128
+ console.log(`claude-telegram — Telegram bot for Claude Code CLI
129
+
130
+ Usage:
131
+ claude-telegram start [--config path] Start the bot
132
+ claude-telegram check [--config path] Validate config & dependencies
133
+ claude-telegram whoami Get your Telegram user ID
134
+ `);
135
+ if (command && command !== "help" && command !== "--help") {
136
+ process.exit(1);
137
+ }
138
+ break;
139
+ }
140
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Bot } from "grammy";
2
+ import type { StreamJsonEvent } from "./types.js";
3
+ interface ActivityStatusOptions {
4
+ api: Bot["api"];
5
+ chatId: number;
6
+ messageId: number;
7
+ }
8
+ /**
9
+ * Create an activity status updater that edits a Telegram message
10
+ * with current Claude activity and elapsed time.
11
+ */
12
+ export declare function createActivityStatus(options: ActivityStatusOptions): {
13
+ onEvent: (event: StreamJsonEvent) => void;
14
+ stop: () => void;
15
+ };
16
+ export {};
17
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/activity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,YAAY,CAAC;AAsD/D,UAAU,qBAAqB;IAC7B,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB;qBA0BzC,eAAe;;EAkBxC"}
@@ -0,0 +1,91 @@
1
+ const MIN_UPDATE_INTERVAL_MS = 3_000;
2
+ const TOOL_LABELS = {
3
+ Read: "reading",
4
+ Edit: "editing",
5
+ Write: "writing",
6
+ Bash: "command",
7
+ Grep: "searching",
8
+ Glob: "searching",
9
+ WebFetch: "web",
10
+ WebSearch: "web",
11
+ Task: "subagent",
12
+ };
13
+ const ACTIVITY_DISPLAY = {
14
+ thinking: "💭 Thinking",
15
+ reading: "📖 Reading",
16
+ editing: "✏️ Editing",
17
+ writing: "📝 Writing",
18
+ searching: "🔍 Searching",
19
+ command: "🔧 Running command",
20
+ web: "🌐 Web lookup",
21
+ subagent: "🧩 Sub-agent",
22
+ mcp: "🔌 MCP tool",
23
+ };
24
+ function formatElapsed(ms) {
25
+ const totalSec = Math.floor(ms / 1000);
26
+ const min = Math.floor(totalSec / 60);
27
+ const sec = totalSec % 60;
28
+ return `${min}:${String(sec).padStart(2, "0")}`;
29
+ }
30
+ /**
31
+ * Detect activity from a stream-json event.
32
+ */
33
+ function detectActivity(event) {
34
+ if (event.type === "assistant" && event.message?.content) {
35
+ for (const block of event.message.content) {
36
+ if (block.type === "tool_use" && block.name) {
37
+ // Check known tools
38
+ const key = TOOL_LABELS[block.name];
39
+ if (key)
40
+ return key;
41
+ // MCP tools (mcp__*)
42
+ if (block.name.startsWith("mcp__"))
43
+ return "mcp";
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Create an activity status updater that edits a Telegram message
51
+ * with current Claude activity and elapsed time.
52
+ */
53
+ export function createActivityStatus(options) {
54
+ const { api, chatId, messageId } = options;
55
+ const startTime = Date.now();
56
+ let currentLabel = ACTIVITY_DISPLAY.thinking;
57
+ let lastSentText = "";
58
+ let stopped = false;
59
+ const timer = setInterval(sendUpdate, MIN_UPDATE_INTERVAL_MS);
60
+ async function sendUpdate() {
61
+ if (stopped)
62
+ return;
63
+ const elapsed = formatElapsed(Date.now() - startTime);
64
+ const text = `${currentLabel} ⏱ ${elapsed}`;
65
+ if (text === lastSentText)
66
+ return;
67
+ try {
68
+ await api.editMessageText(chatId, messageId, text);
69
+ lastSentText = text;
70
+ }
71
+ catch {
72
+ // Silently ignore edit failures (rate limit, message deleted, etc.)
73
+ }
74
+ }
75
+ function onEvent(event) {
76
+ if (stopped)
77
+ return;
78
+ const key = detectActivity(event);
79
+ if (key) {
80
+ currentLabel = ACTIVITY_DISPLAY[key];
81
+ }
82
+ }
83
+ function stop() {
84
+ stopped = true;
85
+ clearInterval(timer);
86
+ }
87
+ // Send first update immediately
88
+ void sendUpdate();
89
+ return { onEvent, stop };
90
+ }
91
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/activity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Bot } from "grammy";
2
+ import type { BotConfig } from "./types.js";
3
+ import { type BotModule, type ModuleContext } from "./modules.js";
4
+ export interface CreateBotOptions {
5
+ modules?: BotModule[];
6
+ onModuleContext?: (ctx: ModuleContext) => void;
7
+ }
8
+ /**
9
+ * Create and configure a Grammy bot connected to Claude CLI.
10
+ */
11
+ export declare function createBot(config: BotConfig, options?: CreateBotOptions): Bot;
12
+ /**
13
+ * Start the bot with graceful shutdown handling.
14
+ */
15
+ export declare function startBot(config: BotConfig): Promise<void>;
16
+ //# sourceMappingURL=bot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../src/bot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAgB,MAAM,QAAQ,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,YAAY,CAAC;AAM1D,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD;AAsBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,gBAAqB,GAAG,GAAG,CA0WhF;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiD/D"}