telegram-claude-mcp 1.3.0 ā 1.4.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/README.md +286 -0
- package/bin/setup.js +336 -0
- package/package.json +5 -3
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# telegram-claude-mcp
|
|
2
|
+
|
|
3
|
+
Get Telegram notifications from Claude Code with interactive permission buttons.
|
|
4
|
+
|
|
5
|
+
When Claude needs permission to run a command or access a file, you'll get a Telegram message with Allow/Deny buttons. When Claude finishes working, you can reply with more instructions to continue.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx telegram-claude-setup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then:
|
|
14
|
+
1. Create a Telegram bot via [@BotFather](https://t.me/BotFather)
|
|
15
|
+
2. Get your chat ID (see instructions below)
|
|
16
|
+
3. Edit `~/.claude/settings.json` with your bot token and chat ID
|
|
17
|
+
4. Restart Claude Code
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Permission buttons** - Allow/Deny tool usage from Telegram
|
|
22
|
+
- **Interactive stop** - Reply to continue Claude's work after it stops
|
|
23
|
+
- **Notifications** - Get notified about Claude events
|
|
24
|
+
- **Multi-session** - Run multiple Claude instances with message tagging
|
|
25
|
+
|
|
26
|
+
## Manual Installation
|
|
27
|
+
|
|
28
|
+
If you prefer not to use npm, follow these steps:
|
|
29
|
+
|
|
30
|
+
### 1. Create Hook Scripts
|
|
31
|
+
|
|
32
|
+
Create directory `~/.claude/hooks/` and add these scripts:
|
|
33
|
+
|
|
34
|
+
**~/.claude/hooks/permission-hook.sh**
|
|
35
|
+
```bash
|
|
36
|
+
#!/bin/bash
|
|
37
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
38
|
+
|
|
39
|
+
find_active_session() {
|
|
40
|
+
local latest_file=""
|
|
41
|
+
local latest_time=0
|
|
42
|
+
[ -d "$SESSION_DIR" ] || return
|
|
43
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
44
|
+
[ -e "$info_file" ] || continue
|
|
45
|
+
local pid
|
|
46
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
47
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
48
|
+
local file_time
|
|
49
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
50
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
51
|
+
latest_time=$file_time
|
|
52
|
+
latest_file=$info_file
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
echo "$latest_file"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
INFO_FILE=$(find_active_session)
|
|
60
|
+
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
61
|
+
|
|
62
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
63
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
64
|
+
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/permission"
|
|
65
|
+
|
|
66
|
+
INPUT=$(cat)
|
|
67
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
68
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')
|
|
69
|
+
|
|
70
|
+
PAYLOAD=$(jq -n --arg tool_name "$TOOL_NAME" --argjson tool_input "$TOOL_INPUT" \
|
|
71
|
+
'{tool_name: $tool_name, tool_input: $tool_input}')
|
|
72
|
+
|
|
73
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 600)
|
|
74
|
+
[ $? -eq 0 ] && echo "$RESPONSE"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**~/.claude/hooks/stop-hook.sh**
|
|
78
|
+
```bash
|
|
79
|
+
#!/bin/bash
|
|
80
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
81
|
+
|
|
82
|
+
find_active_session() {
|
|
83
|
+
local latest_file=""
|
|
84
|
+
local latest_time=0
|
|
85
|
+
[ -d "$SESSION_DIR" ] || return
|
|
86
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
87
|
+
[ -e "$info_file" ] || continue
|
|
88
|
+
local pid
|
|
89
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
90
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
91
|
+
local file_time
|
|
92
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
93
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
94
|
+
latest_time=$file_time
|
|
95
|
+
latest_file=$info_file
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
echo "$latest_file"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
INPUT=$(cat)
|
|
103
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
104
|
+
[ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0
|
|
105
|
+
|
|
106
|
+
INFO_FILE=$(find_active_session)
|
|
107
|
+
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
108
|
+
|
|
109
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
110
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
111
|
+
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/stop"
|
|
112
|
+
|
|
113
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
114
|
+
PAYLOAD=$(jq -n --arg transcript_path "$TRANSCRIPT_PATH" '{transcript_path: $transcript_path}')
|
|
115
|
+
|
|
116
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 300)
|
|
117
|
+
if [ $? -eq 0 ]; then
|
|
118
|
+
DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
|
|
119
|
+
[ "$DECISION" = "block" ] && echo "$RESPONSE"
|
|
120
|
+
fi
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**~/.claude/hooks/notify-hook.sh**
|
|
124
|
+
```bash
|
|
125
|
+
#!/bin/bash
|
|
126
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
127
|
+
|
|
128
|
+
find_active_session() {
|
|
129
|
+
local latest_file=""
|
|
130
|
+
local latest_time=0
|
|
131
|
+
[ -d "$SESSION_DIR" ] || return
|
|
132
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
133
|
+
[ -e "$info_file" ] || continue
|
|
134
|
+
local pid
|
|
135
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
136
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
137
|
+
local file_time
|
|
138
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
139
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
140
|
+
latest_time=$file_time
|
|
141
|
+
latest_file=$info_file
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
done
|
|
145
|
+
echo "$latest_file"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
INFO_FILE=$(find_active_session)
|
|
149
|
+
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
150
|
+
|
|
151
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
152
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
153
|
+
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/notify"
|
|
154
|
+
|
|
155
|
+
INPUT=$(cat)
|
|
156
|
+
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification"')
|
|
157
|
+
PAYLOAD=$(jq -n --arg type "notification" --arg message "$MESSAGE" '{type: $type, message: $message}')
|
|
158
|
+
|
|
159
|
+
curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 10 >/dev/null 2>&1
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Make them executable:
|
|
163
|
+
```bash
|
|
164
|
+
chmod +x ~/.claude/hooks/*.sh
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 2. Configure Claude Settings
|
|
168
|
+
|
|
169
|
+
Add to `~/.claude/settings.json`:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"hooks": {
|
|
174
|
+
"PermissionRequest": [
|
|
175
|
+
{
|
|
176
|
+
"matcher": "*",
|
|
177
|
+
"hooks": [{ "type": "command", "command": "~/.claude/hooks/permission-hook.sh" }]
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
"Stop": [
|
|
181
|
+
{
|
|
182
|
+
"matcher": "*",
|
|
183
|
+
"hooks": [{ "type": "command", "command": "~/.claude/hooks/stop-hook.sh" }]
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"Notification": [
|
|
187
|
+
{
|
|
188
|
+
"matcher": "*",
|
|
189
|
+
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-hook.sh" }]
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"mcpServers": {
|
|
194
|
+
"telegram": {
|
|
195
|
+
"command": "npx",
|
|
196
|
+
"args": ["-y", "telegram-claude-mcp"],
|
|
197
|
+
"env": {
|
|
198
|
+
"TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
|
|
199
|
+
"TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
|
|
200
|
+
"SESSION_NAME": "default"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 3. Create Telegram Bot
|
|
208
|
+
|
|
209
|
+
1. Open Telegram and message [@BotFather](https://t.me/BotFather)
|
|
210
|
+
2. Send `/newbot` and follow prompts
|
|
211
|
+
3. Copy the bot token
|
|
212
|
+
|
|
213
|
+
### 4. Get Your Chat ID
|
|
214
|
+
|
|
215
|
+
1. Start a chat with your new bot
|
|
216
|
+
2. Send any message to it
|
|
217
|
+
3. Visit: `https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates`
|
|
218
|
+
4. Find `"chat":{"id": YOUR_CHAT_ID}` in the response
|
|
219
|
+
|
|
220
|
+
### 5. Update Settings
|
|
221
|
+
|
|
222
|
+
Replace `YOUR_BOT_TOKEN` and `YOUR_CHAT_ID` in `~/.claude/settings.json`
|
|
223
|
+
|
|
224
|
+
### 6. Restart Claude Code
|
|
225
|
+
|
|
226
|
+
## Environment Variables
|
|
227
|
+
|
|
228
|
+
| Variable | Description | Required |
|
|
229
|
+
|----------|-------------|----------|
|
|
230
|
+
| `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather | Yes |
|
|
231
|
+
| `TELEGRAM_CHAT_ID` | Your Telegram chat ID | Yes |
|
|
232
|
+
| `SESSION_NAME` | Session identifier (for multi-instance) | No (default: "default") |
|
|
233
|
+
| `SESSION_PORT` | Preferred HTTP port | No (default: 3333, auto-finds available) |
|
|
234
|
+
|
|
235
|
+
## How It Works
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
Claude Code Hook Scripts telegram-claude-mcp
|
|
239
|
+
| | |
|
|
240
|
+
|-- Permission needed -------->| |
|
|
241
|
+
| |-- POST /permission --------->|
|
|
242
|
+
| | |-- Send to Telegram
|
|
243
|
+
| | |<- User clicks Allow
|
|
244
|
+
| |<-------- {allow} ------------|
|
|
245
|
+
|<-------- Allow --------------| |
|
|
246
|
+
| | |
|
|
247
|
+
|-- Work complete, stops ----->| |
|
|
248
|
+
| |-- POST /stop --------------->|
|
|
249
|
+
| | |-- Send to Telegram
|
|
250
|
+
| | |<- User replies
|
|
251
|
+
| |<-- {block, "do X next"} -----|
|
|
252
|
+
|<-- Continue with "do X" -----| |
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Multiple Sessions
|
|
256
|
+
|
|
257
|
+
Run multiple Claude instances with different session names:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"env": {
|
|
262
|
+
"SESSION_NAME": "project-a"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Messages are tagged with `[project-a]` so you know which instance sent them.
|
|
268
|
+
|
|
269
|
+
## Troubleshooting
|
|
270
|
+
|
|
271
|
+
**Buttons appear but don't work:**
|
|
272
|
+
- Check for stale MCP processes: `ps aux | grep telegram-claude`
|
|
273
|
+
- Kill old processes and restart Claude
|
|
274
|
+
|
|
275
|
+
**No messages in Telegram:**
|
|
276
|
+
- Verify bot token and chat ID are correct
|
|
277
|
+
- Ensure you've started a chat with your bot
|
|
278
|
+
- Check Claude's MCP server logs
|
|
279
|
+
|
|
280
|
+
**Permission hook not firing:**
|
|
281
|
+
- Verify hook scripts are executable: `ls -la ~/.claude/hooks/`
|
|
282
|
+
- Check Claude settings have hooks configured
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
MIT
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Setup script for telegram-claude-mcp hooks integration
|
|
5
|
+
*
|
|
6
|
+
* Usage: npx telegram-claude-mcp setup
|
|
7
|
+
*
|
|
8
|
+
* This will:
|
|
9
|
+
* 1. Copy hook scripts to ~/.claude/hooks/
|
|
10
|
+
* 2. Update Claude settings with hooks configuration
|
|
11
|
+
* 3. Display instructions for completing setup
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, chmodSync } from 'fs';
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { homedir } from 'os';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
|
|
21
|
+
const CLAUDE_DIR = join(homedir(), '.claude');
|
|
22
|
+
const HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
|
|
23
|
+
const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
|
|
24
|
+
|
|
25
|
+
// Hook scripts content
|
|
26
|
+
const PERMISSION_HOOK = `#!/bin/bash
|
|
27
|
+
#
|
|
28
|
+
# Claude Code Permission Hook - forwards permission requests to Telegram
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
32
|
+
|
|
33
|
+
find_active_session() {
|
|
34
|
+
local latest_file=""
|
|
35
|
+
local latest_time=0
|
|
36
|
+
|
|
37
|
+
[ -d "$SESSION_DIR" ] || return
|
|
38
|
+
|
|
39
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
40
|
+
[ -e "$info_file" ] || continue
|
|
41
|
+
|
|
42
|
+
local pid
|
|
43
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
44
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
45
|
+
local file_time
|
|
46
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
47
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
48
|
+
latest_time=$file_time
|
|
49
|
+
latest_file=$info_file
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
echo "$latest_file"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
INFO_FILE=$(find_active_session)
|
|
58
|
+
|
|
59
|
+
if [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ]; then
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
64
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
65
|
+
HOOK_URL="http://\${HOOK_HOST}:\${HOOK_PORT}/permission"
|
|
66
|
+
|
|
67
|
+
INPUT=$(cat)
|
|
68
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
69
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
70
|
+
|
|
71
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
72
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName // .name // empty')
|
|
73
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.toolInput // .input // .arguments // {}')
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
PAYLOAD=$(jq -n \\
|
|
77
|
+
--arg tool_name "$TOOL_NAME" \\
|
|
78
|
+
--argjson tool_input "$TOOL_INPUT" \\
|
|
79
|
+
'{tool_name: $tool_name, tool_input: $tool_input}')
|
|
80
|
+
|
|
81
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
82
|
+
-H "Content-Type: application/json" \\
|
|
83
|
+
-d "$PAYLOAD" \\
|
|
84
|
+
--max-time 600)
|
|
85
|
+
|
|
86
|
+
[ $? -eq 0 ] && echo "$RESPONSE"
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
const STOP_HOOK = `#!/bin/bash
|
|
90
|
+
#
|
|
91
|
+
# Claude Code Interactive Stop Hook - sends stop notification and waits for reply
|
|
92
|
+
#
|
|
93
|
+
|
|
94
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
95
|
+
|
|
96
|
+
find_active_session() {
|
|
97
|
+
local latest_file=""
|
|
98
|
+
local latest_time=0
|
|
99
|
+
|
|
100
|
+
[ -d "$SESSION_DIR" ] || return
|
|
101
|
+
|
|
102
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
103
|
+
[ -e "$info_file" ] || continue
|
|
104
|
+
|
|
105
|
+
local pid
|
|
106
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
107
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
108
|
+
local file_time
|
|
109
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
110
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
111
|
+
latest_time=$file_time
|
|
112
|
+
latest_file=$info_file
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
echo "$latest_file"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
INPUT=$(cat)
|
|
121
|
+
|
|
122
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
123
|
+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|
124
|
+
exit 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
INFO_FILE=$(find_active_session)
|
|
128
|
+
|
|
129
|
+
if [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ]; then
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
134
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
135
|
+
HOOK_URL="http://\${HOOK_HOST}:\${HOOK_PORT}/stop"
|
|
136
|
+
|
|
137
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
138
|
+
|
|
139
|
+
PAYLOAD=$(jq -n \\
|
|
140
|
+
--arg transcript_path "$TRANSCRIPT_PATH" \\
|
|
141
|
+
'{transcript_path: $transcript_path}')
|
|
142
|
+
|
|
143
|
+
RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
144
|
+
-H "Content-Type: application/json" \\
|
|
145
|
+
-d "$PAYLOAD" \\
|
|
146
|
+
--max-time 300)
|
|
147
|
+
|
|
148
|
+
if [ $? -eq 0 ]; then
|
|
149
|
+
DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
|
|
150
|
+
REASON=$(echo "$RESPONSE" | jq -r '.reason // empty')
|
|
151
|
+
|
|
152
|
+
if [ "$DECISION" = "block" ] && [ -n "$REASON" ]; then
|
|
153
|
+
echo "$RESPONSE"
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
const NOTIFY_HOOK = `#!/bin/bash
|
|
159
|
+
#
|
|
160
|
+
# Claude Code Notification Hook - sends notifications to Telegram
|
|
161
|
+
#
|
|
162
|
+
|
|
163
|
+
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
164
|
+
|
|
165
|
+
find_active_session() {
|
|
166
|
+
local latest_file=""
|
|
167
|
+
local latest_time=0
|
|
168
|
+
|
|
169
|
+
[ -d "$SESSION_DIR" ] || return
|
|
170
|
+
|
|
171
|
+
for info_file in "$SESSION_DIR"/*.info; do
|
|
172
|
+
[ -e "$info_file" ] || continue
|
|
173
|
+
|
|
174
|
+
local pid
|
|
175
|
+
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
176
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
177
|
+
local file_time
|
|
178
|
+
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
179
|
+
if [ "$file_time" -gt "$latest_time" ]; then
|
|
180
|
+
latest_time=$file_time
|
|
181
|
+
latest_file=$info_file
|
|
182
|
+
fi
|
|
183
|
+
fi
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
echo "$latest_file"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
INFO_FILE=$(find_active_session)
|
|
190
|
+
|
|
191
|
+
if [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ]; then
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
196
|
+
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
197
|
+
HOOK_URL="http://\${HOOK_HOST}:\${HOOK_PORT}/notify"
|
|
198
|
+
|
|
199
|
+
INPUT=$(cat)
|
|
200
|
+
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification from Claude"')
|
|
201
|
+
|
|
202
|
+
PAYLOAD=$(jq -n \\
|
|
203
|
+
--arg type "notification" \\
|
|
204
|
+
--arg message "$MESSAGE" \\
|
|
205
|
+
'{type: $type, message: $message}')
|
|
206
|
+
|
|
207
|
+
curl -s -X POST "$HOOK_URL" \\
|
|
208
|
+
-H "Content-Type: application/json" \\
|
|
209
|
+
-d "$PAYLOAD" \\
|
|
210
|
+
--max-time 10 > /dev/null 2>&1
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
// Hooks configuration for Claude settings
|
|
214
|
+
const HOOKS_CONFIG = {
|
|
215
|
+
PermissionRequest: [
|
|
216
|
+
{
|
|
217
|
+
matcher: '*',
|
|
218
|
+
hooks: [
|
|
219
|
+
{
|
|
220
|
+
type: 'command',
|
|
221
|
+
command: '~/.claude/hooks/permission-hook.sh'
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
Stop: [
|
|
227
|
+
{
|
|
228
|
+
matcher: '*',
|
|
229
|
+
hooks: [
|
|
230
|
+
{
|
|
231
|
+
type: 'command',
|
|
232
|
+
command: '~/.claude/hooks/stop-hook.sh'
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
Notification: [
|
|
238
|
+
{
|
|
239
|
+
matcher: '*',
|
|
240
|
+
hooks: [
|
|
241
|
+
{
|
|
242
|
+
type: 'command',
|
|
243
|
+
command: '~/.claude/hooks/notify-hook.sh'
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
function setup() {
|
|
251
|
+
console.log('\nš± telegram-claude-mcp Setup\n');
|
|
252
|
+
console.log('This will configure Telegram notifications for Claude Code.\n');
|
|
253
|
+
|
|
254
|
+
// Create hooks directory
|
|
255
|
+
if (!existsSync(HOOKS_DIR)) {
|
|
256
|
+
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
257
|
+
console.log('ā Created ~/.claude/hooks/');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Write hook scripts
|
|
261
|
+
const permissionHookPath = join(HOOKS_DIR, 'permission-hook.sh');
|
|
262
|
+
writeFileSync(permissionHookPath, PERMISSION_HOOK);
|
|
263
|
+
chmodSync(permissionHookPath, 0o755);
|
|
264
|
+
console.log('ā Installed permission-hook.sh');
|
|
265
|
+
|
|
266
|
+
const stopHookPath = join(HOOKS_DIR, 'stop-hook.sh');
|
|
267
|
+
writeFileSync(stopHookPath, STOP_HOOK);
|
|
268
|
+
chmodSync(stopHookPath, 0o755);
|
|
269
|
+
console.log('ā Installed stop-hook.sh');
|
|
270
|
+
|
|
271
|
+
const notifyHookPath = join(HOOKS_DIR, 'notify-hook.sh');
|
|
272
|
+
writeFileSync(notifyHookPath, NOTIFY_HOOK);
|
|
273
|
+
chmodSync(notifyHookPath, 0o755);
|
|
274
|
+
console.log('ā Installed notify-hook.sh');
|
|
275
|
+
|
|
276
|
+
// Update Claude settings
|
|
277
|
+
let settings = {};
|
|
278
|
+
if (existsSync(SETTINGS_FILE)) {
|
|
279
|
+
try {
|
|
280
|
+
settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf-8'));
|
|
281
|
+
} catch {
|
|
282
|
+
console.log('ā Could not parse existing settings.json, creating new one');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Merge hooks config
|
|
287
|
+
settings.hooks = { ...settings.hooks, ...HOOKS_CONFIG };
|
|
288
|
+
|
|
289
|
+
// Add MCP server config if not present
|
|
290
|
+
if (!settings.mcpServers) {
|
|
291
|
+
settings.mcpServers = {};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!settings.mcpServers.telegram) {
|
|
295
|
+
settings.mcpServers.telegram = {
|
|
296
|
+
command: 'npx',
|
|
297
|
+
args: ['-y', 'telegram-claude-mcp'],
|
|
298
|
+
env: {
|
|
299
|
+
TELEGRAM_BOT_TOKEN: 'YOUR_BOT_TOKEN',
|
|
300
|
+
TELEGRAM_CHAT_ID: 'YOUR_CHAT_ID',
|
|
301
|
+
SESSION_NAME: 'default'
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
console.log('ā Added telegram MCP server config (needs bot token)');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
308
|
+
console.log('ā Updated ~/.claude/settings.json with hooks');
|
|
309
|
+
|
|
310
|
+
// Print next steps
|
|
311
|
+
console.log('\n' + 'ā'.repeat(50));
|
|
312
|
+
console.log('\nš Next Steps:\n');
|
|
313
|
+
|
|
314
|
+
console.log('1. Create a Telegram bot:');
|
|
315
|
+
console.log(' - Open Telegram and message @BotFather');
|
|
316
|
+
console.log(' - Send /newbot and follow the prompts');
|
|
317
|
+
console.log(' - Copy the bot token\n');
|
|
318
|
+
|
|
319
|
+
console.log('2. Get your Chat ID:');
|
|
320
|
+
console.log(' - Start a chat with your new bot');
|
|
321
|
+
console.log(' - Send any message');
|
|
322
|
+
console.log(' - Visit: https://api.telegram.org/bot<TOKEN>/getUpdates');
|
|
323
|
+
console.log(' - Find "chat":{"id": YOUR_CHAT_ID}\n');
|
|
324
|
+
|
|
325
|
+
console.log('3. Update ~/.claude/settings.json:');
|
|
326
|
+
console.log(' - Set TELEGRAM_BOT_TOKEN to your bot token');
|
|
327
|
+
console.log(' - Set TELEGRAM_CHAT_ID to your chat ID\n');
|
|
328
|
+
|
|
329
|
+
console.log('4. Restart Claude Code\n');
|
|
330
|
+
|
|
331
|
+
console.log('ā'.repeat(50));
|
|
332
|
+
console.log('\n⨠Setup complete! Configure your bot token and chat ID to finish.\n');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Run setup
|
|
336
|
+
setup();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telegram-claude-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "MCP server that lets Claude message you on Telegram with hooks support",
|
|
5
5
|
"author": "Geravant",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
"type": "module",
|
|
22
22
|
"main": "src/index.ts",
|
|
23
23
|
"bin": {
|
|
24
|
-
"telegram-claude-mcp": "./bin/run.js"
|
|
24
|
+
"telegram-claude-mcp": "./bin/run.js",
|
|
25
|
+
"telegram-claude-setup": "./bin/setup.js"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"src",
|
|
28
29
|
"bin",
|
|
29
|
-
"hooks"
|
|
30
|
+
"hooks",
|
|
31
|
+
"README.md"
|
|
30
32
|
],
|
|
31
33
|
"scripts": {
|
|
32
34
|
"start": "node --import tsx src/index.ts",
|