@gonzih/cc-tg 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.
- package/README.md +66 -0
- package/dist/bot.d.ts +23 -0
- package/dist/bot.js +138 -0
- package/dist/claude.d.ts +42 -0
- package/dist/claude.js +118 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +67 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# cc-tg
|
|
2
|
+
|
|
3
|
+
Claude Code Telegram bot. Chat with Claude Code from Telegram.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
**Step 1** — create a Telegram bot via [@BotFather](https://t.me/BotFather), get your token.
|
|
8
|
+
|
|
9
|
+
**Step 2** — run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
TELEGRAM_BOT_TOKEN=your_bot_token CLAUDE_CODE_TOKEN=your_claude_token npx @gonzih/cc-tg
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That's it. Open your bot in Telegram and start chatting.
|
|
16
|
+
|
|
17
|
+
## Environment variables
|
|
18
|
+
|
|
19
|
+
| Variable | Required | Description |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `TELEGRAM_BOT_TOKEN` | yes | From @BotFather |
|
|
22
|
+
| `CLAUDE_CODE_TOKEN` | yes* | Claude Code OAuth token |
|
|
23
|
+
| `ANTHROPIC_API_KEY` | yes* | Alternative to CLAUDE_CODE_TOKEN |
|
|
24
|
+
| `ALLOWED_USER_IDS` | no | Comma-separated Telegram user IDs. Leave empty to allow anyone |
|
|
25
|
+
| `CWD` | no | Working directory for Claude Code. Defaults to current directory |
|
|
26
|
+
|
|
27
|
+
*One of CLAUDE_CODE_TOKEN or ANTHROPIC_API_KEY is required.
|
|
28
|
+
|
|
29
|
+
## How to get your Telegram user ID
|
|
30
|
+
|
|
31
|
+
Message [@userinfobot](https://t.me/userinfobot) on Telegram — it replies with your ID.
|
|
32
|
+
|
|
33
|
+
## Bot commands
|
|
34
|
+
|
|
35
|
+
| Command | Action |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `/start` | Reset session |
|
|
38
|
+
| `/reset` | Reset session |
|
|
39
|
+
| `/status` | Check if session is active |
|
|
40
|
+
| Any text | Sent directly to Claude Code |
|
|
41
|
+
|
|
42
|
+
## How it works
|
|
43
|
+
|
|
44
|
+
Spawns a `claude` CLI subprocess per chat session using the same stream-JSON protocol as the [ce_ce](https://github.com/ityonemo/ce_ce) Elixir library. Each Telegram chat gets its own isolated Claude Code session. Messages stream back in real time, debounced into Telegram messages.
|
|
45
|
+
|
|
46
|
+
## Run persistently (systemd)
|
|
47
|
+
|
|
48
|
+
```ini
|
|
49
|
+
[Unit]
|
|
50
|
+
Description=cc-tg Claude Code Telegram bot
|
|
51
|
+
|
|
52
|
+
[Service]
|
|
53
|
+
Environment=TELEGRAM_BOT_TOKEN=xxx
|
|
54
|
+
Environment=CLAUDE_CODE_TOKEN=yyy
|
|
55
|
+
Environment=ALLOWED_USER_IDS=123456789
|
|
56
|
+
ExecStart=npx @gonzih/cc-tg
|
|
57
|
+
Restart=always
|
|
58
|
+
|
|
59
|
+
[Install]
|
|
60
|
+
WantedBy=multi-user.target
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
- Node.js 18+
|
|
66
|
+
- `claude` CLI installed and in PATH (`npm install -g @anthropic-ai/claude-code`)
|
package/dist/bot.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bot that routes messages to/from a Claude Code subprocess.
|
|
3
|
+
* One ClaudeProcess per chat_id — sessions are isolated per user.
|
|
4
|
+
*/
|
|
5
|
+
export interface BotOptions {
|
|
6
|
+
telegramToken: string;
|
|
7
|
+
claudeToken?: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
allowedUserIds?: number[];
|
|
10
|
+
}
|
|
11
|
+
export declare class CcTgBot {
|
|
12
|
+
private bot;
|
|
13
|
+
private sessions;
|
|
14
|
+
private opts;
|
|
15
|
+
constructor(opts: BotOptions);
|
|
16
|
+
private isAllowed;
|
|
17
|
+
private handleTelegram;
|
|
18
|
+
private getOrCreateSession;
|
|
19
|
+
private handleClaudeMessage;
|
|
20
|
+
private flushPending;
|
|
21
|
+
private killSession;
|
|
22
|
+
stop(): void;
|
|
23
|
+
}
|
package/dist/bot.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bot that routes messages to/from a Claude Code subprocess.
|
|
3
|
+
* One ClaudeProcess per chat_id — sessions are isolated per user.
|
|
4
|
+
*/
|
|
5
|
+
import TelegramBot from "node-telegram-bot-api";
|
|
6
|
+
import { ClaudeProcess, extractText } from "./claude.js";
|
|
7
|
+
const FLUSH_DELAY_MS = 800; // debounce streaming chunks into one Telegram message
|
|
8
|
+
export class CcTgBot {
|
|
9
|
+
bot;
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
opts;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
this.bot = new TelegramBot(opts.telegramToken, { polling: true });
|
|
15
|
+
this.bot.on("message", (msg) => this.handleTelegram(msg));
|
|
16
|
+
this.bot.on("polling_error", (err) => console.error("[tg]", err.message));
|
|
17
|
+
console.log("cc-tg bot started");
|
|
18
|
+
}
|
|
19
|
+
isAllowed(userId) {
|
|
20
|
+
if (!this.opts.allowedUserIds?.length)
|
|
21
|
+
return true;
|
|
22
|
+
return this.opts.allowedUserIds.includes(userId);
|
|
23
|
+
}
|
|
24
|
+
async handleTelegram(msg) {
|
|
25
|
+
const chatId = msg.chat.id;
|
|
26
|
+
const userId = msg.from?.id ?? chatId;
|
|
27
|
+
const text = msg.text?.trim();
|
|
28
|
+
if (!text)
|
|
29
|
+
return;
|
|
30
|
+
if (!this.isAllowed(userId)) {
|
|
31
|
+
await this.bot.sendMessage(chatId, "Not authorized.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// /start or /reset — kill existing session and ack
|
|
35
|
+
if (text === "/start" || text === "/reset") {
|
|
36
|
+
this.killSession(chatId);
|
|
37
|
+
await this.bot.sendMessage(chatId, "Session reset. Send a message to start.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// /status
|
|
41
|
+
if (text === "/status") {
|
|
42
|
+
const has = this.sessions.has(chatId);
|
|
43
|
+
await this.bot.sendMessage(chatId, has ? "Session active." : "No active session.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const session = this.getOrCreateSession(chatId);
|
|
47
|
+
try {
|
|
48
|
+
session.claude.sendPrompt(text);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
await this.bot.sendMessage(chatId, `Error sending to Claude: ${err.message}`);
|
|
52
|
+
this.killSession(chatId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
getOrCreateSession(chatId) {
|
|
56
|
+
const existing = this.sessions.get(chatId);
|
|
57
|
+
if (existing && !existing.claude.exited)
|
|
58
|
+
return existing;
|
|
59
|
+
const claude = new ClaudeProcess({
|
|
60
|
+
cwd: this.opts.cwd,
|
|
61
|
+
token: this.opts.claudeToken,
|
|
62
|
+
});
|
|
63
|
+
const session = {
|
|
64
|
+
claude,
|
|
65
|
+
pendingText: "",
|
|
66
|
+
flushTimer: null,
|
|
67
|
+
};
|
|
68
|
+
claude.on("message", (msg) => this.handleClaudeMessage(chatId, session, msg));
|
|
69
|
+
claude.on("stderr", (data) => {
|
|
70
|
+
// Only surface non-noise stderr
|
|
71
|
+
if (data.includes("Error") || data.includes("error")) {
|
|
72
|
+
console.error(`[claude:${chatId}]`, data.trim());
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
claude.on("exit", (code) => {
|
|
76
|
+
console.log(`[claude:${chatId}] exited with code ${code}`);
|
|
77
|
+
this.sessions.delete(chatId);
|
|
78
|
+
});
|
|
79
|
+
claude.on("error", (err) => {
|
|
80
|
+
console.error(`[claude:${chatId}] process error:`, err.message);
|
|
81
|
+
this.bot.sendMessage(chatId, `Claude process error: ${err.message}`).catch(() => { });
|
|
82
|
+
this.sessions.delete(chatId);
|
|
83
|
+
});
|
|
84
|
+
this.sessions.set(chatId, session);
|
|
85
|
+
return session;
|
|
86
|
+
}
|
|
87
|
+
handleClaudeMessage(chatId, session, msg) {
|
|
88
|
+
if (msg.type !== "assistant" && msg.type !== "result")
|
|
89
|
+
return;
|
|
90
|
+
const text = extractText(msg);
|
|
91
|
+
if (!text)
|
|
92
|
+
return;
|
|
93
|
+
// Accumulate text and debounce — Claude streams chunks rapidly
|
|
94
|
+
session.pendingText += text;
|
|
95
|
+
if (session.flushTimer)
|
|
96
|
+
clearTimeout(session.flushTimer);
|
|
97
|
+
session.flushTimer = setTimeout(() => this.flushPending(chatId, session), FLUSH_DELAY_MS);
|
|
98
|
+
}
|
|
99
|
+
flushPending(chatId, session) {
|
|
100
|
+
const text = session.pendingText.trim();
|
|
101
|
+
session.pendingText = "";
|
|
102
|
+
session.flushTimer = null;
|
|
103
|
+
if (!text)
|
|
104
|
+
return;
|
|
105
|
+
// Telegram max message length is 4096 chars — split if needed
|
|
106
|
+
const chunks = splitMessage(text);
|
|
107
|
+
for (const chunk of chunks) {
|
|
108
|
+
this.bot.sendMessage(chatId, chunk, { parse_mode: "Markdown" }).catch(() => {
|
|
109
|
+
// Markdown parse failed — retry as plain text
|
|
110
|
+
this.bot.sendMessage(chatId, chunk).catch((err) => console.error(`[tg:${chatId}] send failed:`, err.message));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
killSession(chatId) {
|
|
115
|
+
const session = this.sessions.get(chatId);
|
|
116
|
+
if (session) {
|
|
117
|
+
session.claude.kill();
|
|
118
|
+
this.sessions.delete(chatId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
stop() {
|
|
122
|
+
this.bot.stopPolling();
|
|
123
|
+
for (const [chatId] of this.sessions) {
|
|
124
|
+
this.killSession(chatId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function splitMessage(text, maxLen = 4096) {
|
|
129
|
+
if (text.length <= maxLen)
|
|
130
|
+
return [text];
|
|
131
|
+
const chunks = [];
|
|
132
|
+
let i = 0;
|
|
133
|
+
while (i < text.length) {
|
|
134
|
+
chunks.push(text.slice(i, i + maxLen));
|
|
135
|
+
i += maxLen;
|
|
136
|
+
}
|
|
137
|
+
return chunks;
|
|
138
|
+
}
|
package/dist/claude.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code subprocess wrapper.
|
|
3
|
+
* Mirrors ce_ce's mechanism: spawn `claude` CLI with stream-json I/O,
|
|
4
|
+
* pipe prompts in, parse streaming JSON messages out.
|
|
5
|
+
*/
|
|
6
|
+
import { EventEmitter } from "events";
|
|
7
|
+
export type MessageType = "system" | "assistant" | "user" | "result";
|
|
8
|
+
export interface ClaudeMessage {
|
|
9
|
+
type: MessageType;
|
|
10
|
+
session_id?: string;
|
|
11
|
+
uuid?: string;
|
|
12
|
+
payload: Record<string, unknown>;
|
|
13
|
+
raw: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface ClaudeOptions {
|
|
16
|
+
cwd?: string;
|
|
17
|
+
systemPrompt?: string;
|
|
18
|
+
/** CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY value */
|
|
19
|
+
token?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare interface ClaudeProcess {
|
|
22
|
+
on(event: "message", listener: (msg: ClaudeMessage) => void): this;
|
|
23
|
+
on(event: "error", listener: (err: Error) => void): this;
|
|
24
|
+
on(event: "exit", listener: (code: number | null) => void): this;
|
|
25
|
+
on(event: "stderr", listener: (data: string) => void): this;
|
|
26
|
+
}
|
|
27
|
+
export declare class ClaudeProcess extends EventEmitter {
|
|
28
|
+
private proc;
|
|
29
|
+
private buffer;
|
|
30
|
+
private _exited;
|
|
31
|
+
constructor(opts?: ClaudeOptions);
|
|
32
|
+
sendPrompt(text: string): void;
|
|
33
|
+
kill(): void;
|
|
34
|
+
get exited(): boolean;
|
|
35
|
+
private drainBuffer;
|
|
36
|
+
private parseMessage;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract the text content from an assistant message payload.
|
|
40
|
+
* Handles both simple string content and content-block arrays.
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractText(msg: ClaudeMessage): string;
|
package/dist/claude.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code subprocess wrapper.
|
|
3
|
+
* Mirrors ce_ce's mechanism: spawn `claude` CLI with stream-json I/O,
|
|
4
|
+
* pipe prompts in, parse streaming JSON messages out.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
export class ClaudeProcess extends EventEmitter {
|
|
9
|
+
proc;
|
|
10
|
+
buffer = "";
|
|
11
|
+
_exited = false;
|
|
12
|
+
constructor(opts = {}) {
|
|
13
|
+
super();
|
|
14
|
+
const args = [
|
|
15
|
+
"--continue",
|
|
16
|
+
"--output-format", "stream-json",
|
|
17
|
+
"--input-format", "stream-json",
|
|
18
|
+
"--print",
|
|
19
|
+
"--verbose",
|
|
20
|
+
];
|
|
21
|
+
if (opts.systemPrompt) {
|
|
22
|
+
args.push("--system-prompt", opts.systemPrompt);
|
|
23
|
+
}
|
|
24
|
+
const env = { ...process.env };
|
|
25
|
+
if (opts.token) {
|
|
26
|
+
// Try as OAuth token first; Claude Code accepts both env vars
|
|
27
|
+
env.CLAUDE_CODE_OAUTH_TOKEN = opts.token;
|
|
28
|
+
}
|
|
29
|
+
this.proc = spawn("claude", args, {
|
|
30
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
31
|
+
env,
|
|
32
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
this.proc.stdout.on("data", (chunk) => {
|
|
35
|
+
this.buffer += chunk.toString();
|
|
36
|
+
this.drainBuffer();
|
|
37
|
+
});
|
|
38
|
+
this.proc.stderr.on("data", (chunk) => {
|
|
39
|
+
this.emit("stderr", chunk.toString());
|
|
40
|
+
});
|
|
41
|
+
this.proc.on("exit", (code) => {
|
|
42
|
+
this._exited = true;
|
|
43
|
+
this.emit("exit", code);
|
|
44
|
+
});
|
|
45
|
+
this.proc.on("error", (err) => {
|
|
46
|
+
this.emit("error", err);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
sendPrompt(text) {
|
|
50
|
+
if (this._exited)
|
|
51
|
+
throw new Error("Claude process has exited");
|
|
52
|
+
const payload = JSON.stringify({
|
|
53
|
+
type: "user",
|
|
54
|
+
message: { role: "user", content: text },
|
|
55
|
+
});
|
|
56
|
+
this.proc.stdin.write(payload + "\n");
|
|
57
|
+
}
|
|
58
|
+
kill() {
|
|
59
|
+
this.proc.kill();
|
|
60
|
+
}
|
|
61
|
+
get exited() {
|
|
62
|
+
return this._exited;
|
|
63
|
+
}
|
|
64
|
+
drainBuffer() {
|
|
65
|
+
const lines = this.buffer.split("\n");
|
|
66
|
+
// Last element may be incomplete — keep it
|
|
67
|
+
this.buffer = lines.pop() ?? "";
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (!line.trim())
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
const raw = JSON.parse(line);
|
|
73
|
+
const msg = this.parseMessage(raw);
|
|
74
|
+
if (msg)
|
|
75
|
+
this.emit("message", msg);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Non-JSON line (startup noise etc.) — ignore
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
parseMessage(raw) {
|
|
83
|
+
const type = raw.type;
|
|
84
|
+
if (!type)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
type,
|
|
88
|
+
session_id: raw.session_id,
|
|
89
|
+
uuid: raw.uuid,
|
|
90
|
+
payload: raw,
|
|
91
|
+
raw,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract the text content from an assistant message payload.
|
|
97
|
+
* Handles both simple string content and content-block arrays.
|
|
98
|
+
*/
|
|
99
|
+
export function extractText(msg) {
|
|
100
|
+
const message = msg.payload.message;
|
|
101
|
+
if (!message) {
|
|
102
|
+
// result message type
|
|
103
|
+
if (msg.type === "result") {
|
|
104
|
+
return msg.payload.result ?? "";
|
|
105
|
+
}
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
const content = message.content;
|
|
109
|
+
if (typeof content === "string")
|
|
110
|
+
return content;
|
|
111
|
+
if (Array.isArray(content)) {
|
|
112
|
+
return content
|
|
113
|
+
.filter((b) => b.type === "text")
|
|
114
|
+
.map((b) => b.text)
|
|
115
|
+
.join("");
|
|
116
|
+
}
|
|
117
|
+
return "";
|
|
118
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cc-tg — Claude Code Telegram bot
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @gonzih/cc-tg
|
|
7
|
+
*
|
|
8
|
+
* Required env:
|
|
9
|
+
* TELEGRAM_BOT_TOKEN — from @BotFather
|
|
10
|
+
* CLAUDE_CODE_TOKEN — your Claude Code OAuth token (or ANTHROPIC_API_KEY)
|
|
11
|
+
*
|
|
12
|
+
* Optional env:
|
|
13
|
+
* ALLOWED_USER_IDS — comma-separated Telegram user IDs (leave empty to allow all)
|
|
14
|
+
* CWD — working directory for Claude Code (default: process.cwd())
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cc-tg — Claude Code Telegram bot
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @gonzih/cc-tg
|
|
7
|
+
*
|
|
8
|
+
* Required env:
|
|
9
|
+
* TELEGRAM_BOT_TOKEN — from @BotFather
|
|
10
|
+
* CLAUDE_CODE_TOKEN — your Claude Code OAuth token (or ANTHROPIC_API_KEY)
|
|
11
|
+
*
|
|
12
|
+
* Optional env:
|
|
13
|
+
* ALLOWED_USER_IDS — comma-separated Telegram user IDs (leave empty to allow all)
|
|
14
|
+
* CWD — working directory for Claude Code (default: process.cwd())
|
|
15
|
+
*/
|
|
16
|
+
import { CcTgBot } from "./bot.js";
|
|
17
|
+
function required(name) {
|
|
18
|
+
const val = process.env[name];
|
|
19
|
+
if (!val) {
|
|
20
|
+
console.error(`
|
|
21
|
+
ERROR: ${name} is not set.
|
|
22
|
+
|
|
23
|
+
cc-tg requires:
|
|
24
|
+
TELEGRAM_BOT_TOKEN — get one from @BotFather on Telegram
|
|
25
|
+
CLAUDE_CODE_TOKEN — your Claude Code OAuth token
|
|
26
|
+
|
|
27
|
+
Set them and run again:
|
|
28
|
+
TELEGRAM_BOT_TOKEN=xxx CLAUDE_CODE_TOKEN=yyy npx @gonzih/cc-tg
|
|
29
|
+
|
|
30
|
+
Or add to your shell profile / .env file.
|
|
31
|
+
`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return val;
|
|
35
|
+
}
|
|
36
|
+
const telegramToken = required("TELEGRAM_BOT_TOKEN");
|
|
37
|
+
// Accept either CLAUDE_CODE_TOKEN or ANTHROPIC_API_KEY
|
|
38
|
+
const claudeToken = process.env.CLAUDE_CODE_TOKEN ??
|
|
39
|
+
process.env.ANTHROPIC_API_KEY;
|
|
40
|
+
if (!claudeToken) {
|
|
41
|
+
console.error(`
|
|
42
|
+
ERROR: Neither CLAUDE_CODE_TOKEN nor ANTHROPIC_API_KEY is set.
|
|
43
|
+
|
|
44
|
+
Set one and run again:
|
|
45
|
+
TELEGRAM_BOT_TOKEN=xxx CLAUDE_CODE_TOKEN=yyy npx @gonzih/cc-tg
|
|
46
|
+
`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const allowedUserIds = process.env.ALLOWED_USER_IDS
|
|
50
|
+
? process.env.ALLOWED_USER_IDS.split(",").map((s) => parseInt(s.trim(), 10)).filter(Boolean)
|
|
51
|
+
: [];
|
|
52
|
+
const cwd = process.env.CWD ?? process.cwd();
|
|
53
|
+
const bot = new CcTgBot({
|
|
54
|
+
telegramToken,
|
|
55
|
+
claudeToken,
|
|
56
|
+
cwd,
|
|
57
|
+
allowedUserIds,
|
|
58
|
+
});
|
|
59
|
+
process.on("SIGINT", () => {
|
|
60
|
+
console.log("\nShutting down...");
|
|
61
|
+
bot.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
});
|
|
64
|
+
process.on("SIGTERM", () => {
|
|
65
|
+
bot.stop();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gonzih/cc-tg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cc-tg": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "node --loader ts-node/esm src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"node-telegram-bot-api": "^0.66.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.0.0",
|
|
22
|
+
"@types/node-telegram-bot-api": "^0.64.0",
|
|
23
|
+
"typescript": "^5.5.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/Gonzih/cc-tg.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/Gonzih/cc-tg",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Gonzih/cc-tg/issues"
|
|
32
|
+
},
|
|
33
|
+
"keywords": ["claude", "claude-code", "telegram", "bot", "ai"],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|