telegram-claude-mcp 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +234 -0
- package/README.md +249 -58
- package/bin/daemon-ctl.js +207 -0
- package/bin/daemon.js +20 -0
- package/bin/proxy.js +22 -0
- package/bin/setup.js +90 -63
- package/hooks-v2/notify-hook.sh +32 -0
- package/hooks-v2/permission-hook.sh +43 -0
- package/hooks-v2/stop-hook.sh +45 -0
- package/package.json +16 -5
- package/src/daemon/index.ts +415 -0
- package/src/daemon/session-manager.ts +173 -0
- package/src/daemon/telegram-multi.ts +611 -0
- package/src/proxy/index.ts +429 -0
- package/src/shared/protocol.ts +146 -0
- package/src/telegram.ts +85 -71
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Daemon Control CLI
|
|
5
|
+
*
|
|
6
|
+
* Manage the telegram-claude-daemon:
|
|
7
|
+
* daemon-ctl start - Start the daemon
|
|
8
|
+
* daemon-ctl stop - Stop the daemon
|
|
9
|
+
* daemon-ctl restart - Restart the daemon
|
|
10
|
+
* daemon-ctl status - Check daemon status
|
|
11
|
+
* daemon-ctl logs - Show daemon logs (if running with pm2 or similar)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn, execSync } from 'child_process';
|
|
15
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { dirname, join } from 'path';
|
|
18
|
+
|
|
19
|
+
const DAEMON_PID_FILE = '/tmp/telegram-claude-daemon.pid';
|
|
20
|
+
const DAEMON_SOCKET_PATH = '/tmp/telegram-claude-daemon.sock';
|
|
21
|
+
const DAEMON_HTTP_PORT = 3333;
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
const daemonScript = join(__dirname, '..', 'dist', 'daemon', 'index.js');
|
|
26
|
+
const daemonSrc = join(__dirname, '..', 'src', 'daemon', 'index.ts');
|
|
27
|
+
|
|
28
|
+
function getPid() {
|
|
29
|
+
if (existsSync(DAEMON_PID_FILE)) {
|
|
30
|
+
try {
|
|
31
|
+
const pid = parseInt(readFileSync(DAEMON_PID_FILE, 'utf-8').trim(), 10);
|
|
32
|
+
// Check if process is alive
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 0);
|
|
35
|
+
return pid;
|
|
36
|
+
} catch {
|
|
37
|
+
// Process not running, clean up stale files
|
|
38
|
+
cleanup();
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cleanup() {
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(DAEMON_PID_FILE)) unlinkSync(DAEMON_PID_FILE);
|
|
51
|
+
if (existsSync(DAEMON_SOCKET_PATH)) unlinkSync(DAEMON_SOCKET_PATH);
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getStatus() {
|
|
56
|
+
const pid = getPid();
|
|
57
|
+
if (!pid) {
|
|
58
|
+
return { running: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(`http://localhost:${DAEMON_HTTP_PORT}/status`);
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return { running: true, pid, ...data };
|
|
65
|
+
} catch {
|
|
66
|
+
return { running: true, pid, sessions: [] };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function start() {
|
|
71
|
+
const pid = getPid();
|
|
72
|
+
if (pid) {
|
|
73
|
+
console.log(`Daemon already running (PID: ${pid})`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Determine which script to run
|
|
78
|
+
let script = daemonScript;
|
|
79
|
+
let runner = 'node';
|
|
80
|
+
|
|
81
|
+
if (!existsSync(daemonScript)) {
|
|
82
|
+
if (existsSync(daemonSrc)) {
|
|
83
|
+
script = daemonSrc;
|
|
84
|
+
runner = 'bun';
|
|
85
|
+
} else {
|
|
86
|
+
console.error('Error: Daemon script not found. Run `npm run build` first.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log('Starting telegram-claude-daemon...');
|
|
92
|
+
|
|
93
|
+
const child = spawn(runner, [script], {
|
|
94
|
+
detached: true,
|
|
95
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
96
|
+
env: process.env,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
child.unref();
|
|
100
|
+
|
|
101
|
+
// Wait for daemon to start
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
103
|
+
|
|
104
|
+
const newPid = getPid();
|
|
105
|
+
if (newPid) {
|
|
106
|
+
console.log(`Daemon started (PID: ${newPid})`);
|
|
107
|
+
console.log(` Socket: ${DAEMON_SOCKET_PATH}`);
|
|
108
|
+
console.log(` HTTP: http://localhost:${DAEMON_HTTP_PORT}`);
|
|
109
|
+
} else {
|
|
110
|
+
console.error('Failed to start daemon');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function stop() {
|
|
116
|
+
const pid = getPid();
|
|
117
|
+
if (!pid) {
|
|
118
|
+
console.log('Daemon is not running');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`Stopping daemon (PID: ${pid})...`);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
process.kill(pid, 'SIGTERM');
|
|
126
|
+
|
|
127
|
+
// Wait for process to exit
|
|
128
|
+
let attempts = 0;
|
|
129
|
+
while (attempts < 10) {
|
|
130
|
+
try {
|
|
131
|
+
process.kill(pid, 0);
|
|
132
|
+
execSync('sleep 0.5');
|
|
133
|
+
attempts++;
|
|
134
|
+
} catch {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
cleanup();
|
|
140
|
+
console.log('Daemon stopped');
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error('Failed to stop daemon:', err.message);
|
|
143
|
+
cleanup();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function status() {
|
|
148
|
+
const info = await getStatus();
|
|
149
|
+
|
|
150
|
+
if (!info.running) {
|
|
151
|
+
console.log('Daemon: not running');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('Daemon: running');
|
|
156
|
+
console.log(` PID: ${info.pid}`);
|
|
157
|
+
console.log(` Version: ${info.version || 'unknown'}`);
|
|
158
|
+
console.log(` Active Sessions: ${info.activeSessions || 0}`);
|
|
159
|
+
|
|
160
|
+
if (info.sessions && info.sessions.length > 0) {
|
|
161
|
+
console.log(' Sessions:');
|
|
162
|
+
for (const session of info.sessions) {
|
|
163
|
+
console.log(` - ${session.sessionName} (${session.sessionId})`);
|
|
164
|
+
console.log(` Connected: ${session.connectedAt}`);
|
|
165
|
+
console.log(` Last Activity: ${session.lastActivity}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function main() {
|
|
171
|
+
const command = process.argv[2];
|
|
172
|
+
|
|
173
|
+
switch (command) {
|
|
174
|
+
case 'start':
|
|
175
|
+
await start();
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case 'stop':
|
|
179
|
+
stop();
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case 'restart':
|
|
183
|
+
stop();
|
|
184
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
185
|
+
await start();
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'status':
|
|
189
|
+
await status();
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
default:
|
|
193
|
+
console.log('Usage: daemon-ctl <command>');
|
|
194
|
+
console.log('');
|
|
195
|
+
console.log('Commands:');
|
|
196
|
+
console.log(' start - Start the daemon');
|
|
197
|
+
console.log(' stop - Stop the daemon');
|
|
198
|
+
console.log(' restart - Restart the daemon');
|
|
199
|
+
console.log(' status - Check daemon status');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
main().catch((err) => {
|
|
205
|
+
console.error('Error:', err.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
package/bin/daemon.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Telegram Claude Daemon launcher
|
|
5
|
+
*
|
|
6
|
+
* Usage: telegram-claude-daemon
|
|
7
|
+
*
|
|
8
|
+
* Required environment variables:
|
|
9
|
+
* TELEGRAM_BOT_TOKEN - Your Telegram bot token
|
|
10
|
+
* TELEGRAM_CHAT_ID - Your Telegram chat ID
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { register } from 'node:module';
|
|
14
|
+
import { pathToFileURL } from 'node:url';
|
|
15
|
+
|
|
16
|
+
// Register tsx loader for TypeScript support
|
|
17
|
+
register('tsx', pathToFileURL('./'));
|
|
18
|
+
|
|
19
|
+
// Import and run the daemon
|
|
20
|
+
import('../src/daemon/index.ts');
|
package/bin/proxy.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Telegram Claude MCP Proxy launcher
|
|
5
|
+
*
|
|
6
|
+
* Usage: telegram-claude-proxy
|
|
7
|
+
*
|
|
8
|
+
* This connects to a running telegram-claude-daemon via Unix socket.
|
|
9
|
+
* Make sure to start the daemon first: telegram-claude-ctl start
|
|
10
|
+
*
|
|
11
|
+
* Optional environment variables:
|
|
12
|
+
* SESSION_NAME - Session identifier (default: auto-detected from CWD)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { register } from 'node:module';
|
|
16
|
+
import { pathToFileURL } from 'node:url';
|
|
17
|
+
|
|
18
|
+
// Register tsx loader for TypeScript support
|
|
19
|
+
register('tsx', pathToFileURL('./'));
|
|
20
|
+
|
|
21
|
+
// Import and run the proxy
|
|
22
|
+
import('../src/proxy/index.ts');
|
package/bin/setup.js
CHANGED
|
@@ -97,19 +97,31 @@ RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
|
97
97
|
const STOP_HOOK = `#!/bin/bash
|
|
98
98
|
#
|
|
99
99
|
# Claude Code Interactive Stop Hook - sends stop notification and waits for reply
|
|
100
|
+
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
|
|
100
101
|
#
|
|
101
102
|
|
|
102
103
|
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
find_session() {
|
|
106
|
+
if [ -n "$SESSION_NAME" ]; then
|
|
107
|
+
local specific_file="$SESSION_DIR/\${SESSION_NAME}.info"
|
|
108
|
+
if [ -f "$specific_file" ]; then
|
|
109
|
+
local pid
|
|
110
|
+
pid=$(jq -r '.pid // empty' "$specific_file" 2>/dev/null)
|
|
111
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
112
|
+
echo "$specific_file"
|
|
113
|
+
return
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
return
|
|
117
|
+
fi
|
|
118
|
+
|
|
105
119
|
local latest_file=""
|
|
106
120
|
local latest_time=0
|
|
107
|
-
|
|
108
121
|
[ -d "$SESSION_DIR" ] || return
|
|
109
122
|
|
|
110
123
|
for info_file in "$SESSION_DIR"/*.info; do
|
|
111
124
|
[ -e "$info_file" ] || continue
|
|
112
|
-
|
|
113
125
|
local pid
|
|
114
126
|
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
115
127
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
@@ -121,22 +133,16 @@ find_active_session() {
|
|
|
121
133
|
fi
|
|
122
134
|
fi
|
|
123
135
|
done
|
|
124
|
-
|
|
125
136
|
echo "$latest_file"
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
INPUT=$(cat)
|
|
129
140
|
|
|
130
141
|
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
131
|
-
|
|
132
|
-
exit 0
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
INFO_FILE=$(find_active_session)
|
|
142
|
+
[ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
fi
|
|
144
|
+
INFO_FILE=$(find_session)
|
|
145
|
+
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
140
146
|
|
|
141
147
|
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
142
148
|
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
@@ -156,29 +162,38 @@ RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
|
156
162
|
if [ $? -eq 0 ]; then
|
|
157
163
|
DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
|
|
158
164
|
REASON=$(echo "$RESPONSE" | jq -r '.reason // empty')
|
|
159
|
-
|
|
160
|
-
if [ "$DECISION" = "block" ] && [ -n "$REASON" ]; then
|
|
161
|
-
echo "$RESPONSE"
|
|
162
|
-
fi
|
|
165
|
+
[ "$DECISION" = "block" ] && [ -n "$REASON" ] && echo "$RESPONSE"
|
|
163
166
|
fi
|
|
164
167
|
`;
|
|
165
168
|
|
|
166
169
|
const NOTIFY_HOOK = `#!/bin/bash
|
|
167
170
|
#
|
|
168
171
|
# Claude Code Notification Hook - sends notifications to Telegram
|
|
172
|
+
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
|
|
169
173
|
#
|
|
170
174
|
|
|
171
175
|
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
172
176
|
|
|
173
|
-
|
|
177
|
+
find_session() {
|
|
178
|
+
if [ -n "$SESSION_NAME" ]; then
|
|
179
|
+
local specific_file="$SESSION_DIR/\${SESSION_NAME}.info"
|
|
180
|
+
if [ -f "$specific_file" ]; then
|
|
181
|
+
local pid
|
|
182
|
+
pid=$(jq -r '.pid // empty' "$specific_file" 2>/dev/null)
|
|
183
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
184
|
+
echo "$specific_file"
|
|
185
|
+
return
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
return
|
|
189
|
+
fi
|
|
190
|
+
|
|
174
191
|
local latest_file=""
|
|
175
192
|
local latest_time=0
|
|
176
|
-
|
|
177
193
|
[ -d "$SESSION_DIR" ] || return
|
|
178
194
|
|
|
179
195
|
for info_file in "$SESSION_DIR"/*.info; do
|
|
180
196
|
[ -e "$info_file" ] || continue
|
|
181
|
-
|
|
182
197
|
local pid
|
|
183
198
|
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
184
199
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
@@ -190,15 +205,11 @@ find_active_session() {
|
|
|
190
205
|
fi
|
|
191
206
|
fi
|
|
192
207
|
done
|
|
193
|
-
|
|
194
208
|
echo "$latest_file"
|
|
195
209
|
}
|
|
196
210
|
|
|
197
|
-
INFO_FILE=$(
|
|
198
|
-
|
|
199
|
-
if [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ]; then
|
|
200
|
-
exit 0
|
|
201
|
-
fi
|
|
211
|
+
INFO_FILE=$(find_session)
|
|
212
|
+
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
202
213
|
|
|
203
214
|
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
204
215
|
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
@@ -218,42 +229,46 @@ curl -s -X POST "$HOOK_URL" \\
|
|
|
218
229
|
--max-time 10 > /dev/null 2>&1
|
|
219
230
|
`;
|
|
220
231
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
232
|
+
// Generate hooks configuration for Claude settings
|
|
233
|
+
// sessionName parameter enables multi-session support
|
|
234
|
+
function generateHooksConfig(sessionName = 'default') {
|
|
235
|
+
const envPrefix = `SESSION_NAME=${sessionName}`;
|
|
236
|
+
return {
|
|
237
|
+
PermissionRequest: [
|
|
238
|
+
{
|
|
239
|
+
matcher: '*',
|
|
240
|
+
hooks: [
|
|
241
|
+
{
|
|
242
|
+
type: 'command',
|
|
243
|
+
command: `${envPrefix} ~/.claude/hooks/permission-hook.sh`
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
Stop: [
|
|
249
|
+
{
|
|
250
|
+
matcher: '*',
|
|
251
|
+
hooks: [
|
|
252
|
+
{
|
|
253
|
+
type: 'command',
|
|
254
|
+
command: `${envPrefix} ~/.claude/hooks/stop-hook.sh`
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
],
|
|
259
|
+
Notification: [
|
|
260
|
+
{
|
|
261
|
+
matcher: '*',
|
|
262
|
+
hooks: [
|
|
263
|
+
{
|
|
264
|
+
type: 'command',
|
|
265
|
+
command: `${envPrefix} ~/.claude/hooks/notify-hook.sh`
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
257
272
|
|
|
258
273
|
function setup() {
|
|
259
274
|
console.log('\n📱 telegram-claude-mcp Setup\n');
|
|
@@ -291,8 +306,8 @@ function setup() {
|
|
|
291
306
|
}
|
|
292
307
|
}
|
|
293
308
|
|
|
294
|
-
// Merge hooks config
|
|
295
|
-
settings.hooks = { ...settings.hooks, ...
|
|
309
|
+
// Merge hooks config (default session)
|
|
310
|
+
settings.hooks = { ...settings.hooks, ...generateHooksConfig('default') };
|
|
296
311
|
|
|
297
312
|
// Add MCP server config if not present
|
|
298
313
|
if (!settings.mcpServers) {
|
|
@@ -337,6 +352,18 @@ function setup() {
|
|
|
337
352
|
console.log('4. Restart Claude Code\n');
|
|
338
353
|
|
|
339
354
|
console.log('─'.repeat(50));
|
|
355
|
+
console.log('\n📋 Multi-Session Setup (optional):\n');
|
|
356
|
+
console.log('If you have multiple Claude configs (e.g., ~/.claude and ~/.claude-personal),');
|
|
357
|
+
console.log('each needs its own SESSION_NAME to avoid message routing conflicts.\n');
|
|
358
|
+
console.log('For each additional config, update its settings.json:');
|
|
359
|
+
console.log(' 1. Change SESSION_NAME in mcpServers.telegram.env');
|
|
360
|
+
console.log(' 2. Update hook commands to use that SESSION_NAME:\n');
|
|
361
|
+
console.log(' "command": "SESSION_NAME=personal ~/.claude/hooks/permission-hook.sh"\n');
|
|
362
|
+
console.log('Example for ~/.claude-personal/settings.json:');
|
|
363
|
+
console.log(' SESSION_NAME: "personal" (in mcpServers.telegram.env)');
|
|
364
|
+
console.log(' Hook commands: "SESSION_NAME=personal ~/.claude/hooks/..."');
|
|
365
|
+
|
|
366
|
+
console.log('\n' + '─'.repeat(50));
|
|
340
367
|
console.log('\n✨ Setup complete! Configure your bot token and chat ID to finish.\n');
|
|
341
368
|
}
|
|
342
369
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Notify hook for telegram-claude-daemon (v2)
|
|
3
|
+
# Uses single daemon port instead of session discovery
|
|
4
|
+
|
|
5
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
6
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
7
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/notify"
|
|
8
|
+
|
|
9
|
+
# Read input from stdin
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification"')
|
|
12
|
+
|
|
13
|
+
# Build payload
|
|
14
|
+
if [ -n "$SESSION_NAME" ]; then
|
|
15
|
+
PAYLOAD=$(jq -n \
|
|
16
|
+
--arg type "notification" \
|
|
17
|
+
--arg message "$MESSAGE" \
|
|
18
|
+
--arg session_name "$SESSION_NAME" \
|
|
19
|
+
'{type: $type, message: $message, session_name: $session_name}')
|
|
20
|
+
else
|
|
21
|
+
PAYLOAD=$(jq -n \
|
|
22
|
+
--arg type "notification" \
|
|
23
|
+
--arg message "$MESSAGE" \
|
|
24
|
+
'{type: $type, message: $message}')
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Send to daemon (non-blocking, 10 second timeout)
|
|
28
|
+
curl -s -X POST "$HOOK_URL" \
|
|
29
|
+
-H "Content-Type: application/json" \
|
|
30
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
31
|
+
-d "$PAYLOAD" \
|
|
32
|
+
--max-time 10 >/dev/null 2>&1
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Permission hook for telegram-claude-daemon (v2)
|
|
3
|
+
# Uses single daemon port instead of session discovery
|
|
4
|
+
|
|
5
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
6
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
7
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/permission"
|
|
8
|
+
|
|
9
|
+
# Read input from stdin
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract tool name and input
|
|
13
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
14
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')
|
|
15
|
+
|
|
16
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Build payload with session name if available
|
|
21
|
+
if [ -n "$SESSION_NAME" ]; then
|
|
22
|
+
PAYLOAD=$(jq -n \
|
|
23
|
+
--arg tool_name "$TOOL_NAME" \
|
|
24
|
+
--argjson tool_input "$TOOL_INPUT" \
|
|
25
|
+
--arg session_name "$SESSION_NAME" \
|
|
26
|
+
'{tool_name: $tool_name, tool_input: $tool_input, session_name: $session_name}')
|
|
27
|
+
else
|
|
28
|
+
PAYLOAD=$(jq -n \
|
|
29
|
+
--arg tool_name "$TOOL_NAME" \
|
|
30
|
+
--argjson tool_input "$TOOL_INPUT" \
|
|
31
|
+
'{tool_name: $tool_name, tool_input: $tool_input}')
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Send to daemon (10 minute timeout)
|
|
35
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" \
|
|
36
|
+
-H "Content-Type: application/json" \
|
|
37
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
38
|
+
-d "$PAYLOAD" \
|
|
39
|
+
--max-time 600 2>/dev/null)
|
|
40
|
+
|
|
41
|
+
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
|
|
42
|
+
echo "$RESPONSE"
|
|
43
|
+
fi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop hook for telegram-claude-daemon (v2)
|
|
3
|
+
# Uses single daemon port instead of session discovery
|
|
4
|
+
|
|
5
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
6
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
7
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/stop"
|
|
8
|
+
|
|
9
|
+
# Read input from stdin
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# Check if stop hook is already active (prevent recursion)
|
|
13
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
14
|
+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Extract transcript path
|
|
19
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
20
|
+
|
|
21
|
+
# Build payload
|
|
22
|
+
if [ -n "$SESSION_NAME" ]; then
|
|
23
|
+
PAYLOAD=$(jq -n \
|
|
24
|
+
--arg transcript_path "$TRANSCRIPT_PATH" \
|
|
25
|
+
--arg session_name "$SESSION_NAME" \
|
|
26
|
+
'{transcript_path: $transcript_path, session_name: $session_name}')
|
|
27
|
+
else
|
|
28
|
+
PAYLOAD=$(jq -n \
|
|
29
|
+
--arg transcript_path "$TRANSCRIPT_PATH" \
|
|
30
|
+
'{transcript_path: $transcript_path}')
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Send to daemon (5 minute timeout)
|
|
34
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" \
|
|
35
|
+
-H "Content-Type: application/json" \
|
|
36
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
37
|
+
-d "$PAYLOAD" \
|
|
38
|
+
--max-time 300 2>/dev/null)
|
|
39
|
+
|
|
40
|
+
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
|
|
41
|
+
DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
|
|
42
|
+
if [ "$DECISION" = "block" ]; then
|
|
43
|
+
echo "$RESPONSE"
|
|
44
|
+
fi
|
|
45
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telegram-claude-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "MCP server that lets Claude message you on Telegram with hooks support",
|
|
5
5
|
"author": "Geravant",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,23 +16,34 @@
|
|
|
16
16
|
"bot",
|
|
17
17
|
"messaging",
|
|
18
18
|
"hooks",
|
|
19
|
-
"permissions"
|
|
19
|
+
"permissions",
|
|
20
|
+
"daemon"
|
|
20
21
|
],
|
|
21
22
|
"type": "module",
|
|
22
23
|
"main": "src/index.ts",
|
|
23
24
|
"bin": {
|
|
24
25
|
"telegram-claude-mcp": "./bin/run.js",
|
|
25
|
-
"telegram-claude-setup": "./bin/setup.js"
|
|
26
|
+
"telegram-claude-setup": "./bin/setup.js",
|
|
27
|
+
"telegram-claude-daemon": "./bin/daemon.js",
|
|
28
|
+
"telegram-claude-proxy": "./bin/proxy.js",
|
|
29
|
+
"telegram-claude-ctl": "./bin/daemon-ctl.js"
|
|
26
30
|
},
|
|
27
31
|
"files": [
|
|
28
32
|
"src",
|
|
29
33
|
"bin",
|
|
30
34
|
"hooks",
|
|
31
|
-
"
|
|
35
|
+
"hooks-v2",
|
|
36
|
+
"README.md",
|
|
37
|
+
"ARCHITECTURE.md"
|
|
32
38
|
],
|
|
33
39
|
"scripts": {
|
|
34
40
|
"start": "node --import tsx src/index.ts",
|
|
35
|
-
"dev": "node --watch --import tsx src/index.ts"
|
|
41
|
+
"dev": "node --watch --import tsx src/index.ts",
|
|
42
|
+
"daemon": "node --import tsx src/daemon/index.ts",
|
|
43
|
+
"proxy": "node --import tsx src/proxy/index.ts",
|
|
44
|
+
"daemon:start": "node bin/daemon-ctl.js start",
|
|
45
|
+
"daemon:stop": "node bin/daemon-ctl.js stop",
|
|
46
|
+
"daemon:status": "node bin/daemon-ctl.js status"
|
|
36
47
|
},
|
|
37
48
|
"dependencies": {
|
|
38
49
|
"@modelcontextprotocol/sdk": "^1.0.4",
|