@thxmxx/telegram-mcp 1.0.0 → 1.2.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/.github/workflows/publish.yaml +68 -0
- package/README.md +10 -2
- package/package.json +8 -2
- package/src/index.js +129 -23
- package/src/setup.js +89 -43
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
types: [closed]
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
if: github.event_name == 'push' || github.event.pull_request.merged == true
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write # needed to push the version bump commit
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: 20
|
|
27
|
+
registry-url: https://registry.npmjs.org
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: npm install
|
|
31
|
+
|
|
32
|
+
- name: Determine version bump
|
|
33
|
+
id: bump
|
|
34
|
+
run: |
|
|
35
|
+
# Get the commit message that triggered this run
|
|
36
|
+
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
37
|
+
MSG="${{ github.event.pull_request.title }}"
|
|
38
|
+
else
|
|
39
|
+
MSG="$(git log -1 --pretty=%s)"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
echo "Commit message: $MSG"
|
|
43
|
+
|
|
44
|
+
# Detect breaking change
|
|
45
|
+
if echo "$MSG" | grep -qiE "BREAKING CHANGE|!:"; then
|
|
46
|
+
echo "type=major" >> $GITHUB_OUTPUT
|
|
47
|
+
# feat → minor
|
|
48
|
+
elif echo "$MSG" | grep -qiE "^feat(\(.+\))?:"; then
|
|
49
|
+
echo "type=minor" >> $GITHUB_OUTPUT
|
|
50
|
+
# everything else → patch
|
|
51
|
+
else
|
|
52
|
+
echo "type=patch" >> $GITHUB_OUTPUT
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
- name: Bump version
|
|
56
|
+
run: |
|
|
57
|
+
git config user.name "github-actions[bot]"
|
|
58
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
59
|
+
npm version ${{ steps.bump.outputs.type }} --no-git-tag-version
|
|
60
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
61
|
+
git add package.json
|
|
62
|
+
git commit -m "chore: bump version to $VERSION [skip ci]"
|
|
63
|
+
git push
|
|
64
|
+
|
|
65
|
+
- name: Publish
|
|
66
|
+
run: npm publish --access public --provenance
|
|
67
|
+
env:
|
|
68
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
CHANGED
|
@@ -12,7 +12,15 @@ The wizard asks for your Telegram bot token and user ID, registers the MCP serve
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Start Claude Code without permission prompts so it can work autonomously:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
claude --dangerously-skip-permissions
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> ⚠️ This flag disables all tool confirmation prompts — file writes, shell commands, everything. Use only on your own machine for personal workflows.
|
|
22
|
+
|
|
23
|
+
Then activate Telegram in any session:
|
|
16
24
|
|
|
17
25
|
```
|
|
18
26
|
/use-telegram
|
|
@@ -85,4 +93,4 @@ On first use, Claude Code will ask you to approve the three tools this MCP serve
|
|
|
85
93
|
|
|
86
94
|
## License
|
|
87
95
|
|
|
88
|
-
MIT
|
|
96
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thxmxx/telegram-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Telegram bridge for Claude Code — notify, ask and choose via your phone",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/thxmxx/telegram-mcp"
|
|
9
|
+
},
|
|
6
10
|
"bin": {
|
|
7
11
|
"telegram-mcp": "./src/cli.js"
|
|
8
12
|
},
|
|
@@ -11,6 +15,8 @@
|
|
|
11
15
|
"node-telegram-bot-api": "^0.66.0",
|
|
12
16
|
"dotenv": "^16.0.0"
|
|
13
17
|
},
|
|
14
|
-
"engines": {
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
15
21
|
"license": "MIT"
|
|
16
22
|
}
|
package/src/index.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* telegram_notify — send a message (fire and forget)
|
|
7
7
|
* telegram_ask — ask a question, wait for text reply
|
|
8
8
|
* telegram_choose — show buttons, wait for a tap
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* No per-project configuration needed.
|
|
9
|
+
* telegram_listen — wait for user to address this instance by name,
|
|
10
|
+
* returns the next instruction so Claude can keep working
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -36,35 +35,36 @@ if (existsSync(envPath)) {
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
const TOKEN
|
|
38
|
+
const TOKEN = env.TELEGRAM_BOT_TOKEN;
|
|
40
39
|
const CHAT_ID = env.TELEGRAM_CHAT_ID;
|
|
41
40
|
|
|
42
41
|
if (!TOKEN || !CHAT_ID) {
|
|
43
42
|
process.stderr.write(
|
|
44
43
|
"[telegram-mcp] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID.\n" +
|
|
45
|
-
|
|
44
|
+
" Run: npx @thxmxx/telegram-mcp init\n",
|
|
46
45
|
);
|
|
47
46
|
exit(1);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// ── Auto instance label: folder#shortid ───────────────────────────────────────
|
|
51
50
|
|
|
52
|
-
const folder
|
|
53
|
-
const shortId
|
|
54
|
-
const INSTANCE
|
|
55
|
-
const HDR
|
|
51
|
+
const folder = process.cwd().split("/").pop() || "claude";
|
|
52
|
+
const shortId = randomBytes(2).toString("hex");
|
|
53
|
+
const INSTANCE = `${folder}#${shortId}`;
|
|
54
|
+
const HDR = `\`[${INSTANCE}]\``;
|
|
56
55
|
|
|
57
56
|
// ── Telegram client ───────────────────────────────────────────────────────────
|
|
58
57
|
|
|
59
58
|
const bot = new TelegramBot(TOKEN, { polling: true });
|
|
60
59
|
|
|
60
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
61
62
|
function waitForReply(timeoutMs = 300_000) {
|
|
62
63
|
return new Promise((resolve, reject) => {
|
|
63
64
|
const timer = setTimeout(() => {
|
|
64
65
|
bot.removeListener("message", handler);
|
|
65
66
|
reject(new Error("Timed out (5 min)"));
|
|
66
67
|
}, timeoutMs);
|
|
67
|
-
|
|
68
68
|
function handler(msg) {
|
|
69
69
|
if (String(msg.chat.id) !== String(CHAT_ID)) return;
|
|
70
70
|
if (msg.text?.startsWith("/")) return;
|
|
@@ -82,7 +82,6 @@ function waitForCallback(timeoutMs = 300_000) {
|
|
|
82
82
|
bot.removeListener("callback_query", handler);
|
|
83
83
|
reject(new Error("Timed out (5 min)"));
|
|
84
84
|
}, timeoutMs);
|
|
85
|
-
|
|
86
85
|
function handler(query) {
|
|
87
86
|
if (String(query.from.id) !== String(CHAT_ID)) return;
|
|
88
87
|
clearTimeout(timer);
|
|
@@ -94,11 +93,54 @@ function waitForCallback(timeoutMs = 300_000) {
|
|
|
94
93
|
});
|
|
95
94
|
}
|
|
96
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Wait for a message addressed to THIS instance.
|
|
98
|
+
* Format: "@instance-label <instruction>"
|
|
99
|
+
* e.g. "@backend#a3f2 now refactor the auth module"
|
|
100
|
+
*
|
|
101
|
+
* Ignores messages addressed to other instances silently.
|
|
102
|
+
* Times out after `timeoutMs` (default: 1 hour).
|
|
103
|
+
*/
|
|
104
|
+
function waitForAddressedMessage(timeoutMs = 3_600_000) {
|
|
105
|
+
const mention = `@${INSTANCE}`.toLowerCase();
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const timer = setTimeout(() => {
|
|
109
|
+
bot.removeListener("message", handler);
|
|
110
|
+
reject(new Error(`No message addressed to ${INSTANCE} within timeout`));
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
|
|
113
|
+
function handler(msg) {
|
|
114
|
+
if (String(msg.chat.id) !== String(CHAT_ID)) return;
|
|
115
|
+
if (!msg.text) return;
|
|
116
|
+
|
|
117
|
+
const text = msg.text.trim();
|
|
118
|
+
const lower = text.toLowerCase();
|
|
119
|
+
|
|
120
|
+
// Message must start with @instance-label
|
|
121
|
+
if (!lower.startsWith(mention)) return;
|
|
122
|
+
|
|
123
|
+
// Extract the instruction after the mention
|
|
124
|
+
const instruction = text.slice(mention.length).trim();
|
|
125
|
+
if (!instruction) return;
|
|
126
|
+
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
bot.removeListener("message", handler);
|
|
129
|
+
resolve(instruction);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
bot.on("message", handler);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
function terminalPrompt(question) {
|
|
98
137
|
return new Promise((resolve) => {
|
|
99
138
|
const rl = createInterface({ input, output, terminal: true });
|
|
100
139
|
process.stderr.write(`\n[${INSTANCE}] ${question}\n> `);
|
|
101
|
-
rl.once("line", (line) => {
|
|
140
|
+
rl.once("line", (line) => {
|
|
141
|
+
rl.close();
|
|
142
|
+
resolve(line.trim());
|
|
143
|
+
});
|
|
102
144
|
});
|
|
103
145
|
}
|
|
104
146
|
|
|
@@ -113,7 +155,9 @@ function raceCallback(question, options) {
|
|
|
113
155
|
const numbered = options.map((o, i) => ` ${i + 1}. ${o}`).join("\n");
|
|
114
156
|
return Promise.race([
|
|
115
157
|
waitForCallback(),
|
|
116
|
-
terminalPrompt(
|
|
158
|
+
terminalPrompt(
|
|
159
|
+
`${question}\n${numbered}\nChoose (1-${options.length})`,
|
|
160
|
+
).then((v) => {
|
|
117
161
|
const idx = parseInt(v, 10) - 1;
|
|
118
162
|
return { source: "terminal", value: options[idx] ?? v };
|
|
119
163
|
}),
|
|
@@ -124,52 +168,111 @@ function raceCallback(question, options) {
|
|
|
124
168
|
|
|
125
169
|
const server = new McpServer({ name: "telegram-mcp", version: "1.0.0" });
|
|
126
170
|
|
|
171
|
+
// ── telegram_notify ───────────────────────────────────────────────────────────
|
|
172
|
+
|
|
127
173
|
server.tool(
|
|
128
174
|
"telegram_notify",
|
|
129
175
|
"Send a Telegram notification to the user. Use for progress updates and task completions. Does NOT wait for a reply.",
|
|
130
176
|
{ message: z.string().describe("The message to send") },
|
|
131
177
|
async ({ message }) => {
|
|
132
|
-
await bot.sendMessage(CHAT_ID, `${HDR} ${message}`, {
|
|
178
|
+
await bot.sendMessage(CHAT_ID, `${HDR} ${message}`, {
|
|
179
|
+
parse_mode: "Markdown",
|
|
180
|
+
});
|
|
133
181
|
process.stderr.write(`[${INSTANCE}] notify: ${message}\n`);
|
|
134
182
|
return { content: [{ type: "text", text: "Sent." }] };
|
|
135
|
-
}
|
|
183
|
+
},
|
|
136
184
|
);
|
|
137
185
|
|
|
186
|
+
// ── telegram_ask ──────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
138
188
|
server.tool(
|
|
139
189
|
"telegram_ask",
|
|
140
|
-
"Ask the user a free-form question via Telegram and wait for their reply. The same question
|
|
190
|
+
"Ask the user a free-form question via Telegram and wait for their reply. The same question appears on the terminal — whoever answers first wins.",
|
|
141
191
|
{ question: z.string().describe("The question to ask") },
|
|
142
192
|
async ({ question }) => {
|
|
143
|
-
await bot.sendMessage(CHAT_ID, `${HDR} ❓ ${question}`, {
|
|
193
|
+
await bot.sendMessage(CHAT_ID, `${HDR} ❓ ${question}`, {
|
|
194
|
+
parse_mode: "Markdown",
|
|
195
|
+
});
|
|
144
196
|
const { source, value } = await raceReply(question);
|
|
145
197
|
if (source === "terminal") {
|
|
146
|
-
await bot.sendMessage(
|
|
198
|
+
await bot.sendMessage(
|
|
199
|
+
CHAT_ID,
|
|
200
|
+
`${HDR} ✅ Answered from terminal: *${value}*`,
|
|
201
|
+
{ parse_mode: "Markdown" },
|
|
202
|
+
);
|
|
147
203
|
}
|
|
148
204
|
process.stderr.write(`[${INSTANCE}] ask (${source}): ${value}\n`);
|
|
149
205
|
return { content: [{ type: "text", text: value }] };
|
|
150
|
-
}
|
|
206
|
+
},
|
|
151
207
|
);
|
|
152
208
|
|
|
209
|
+
// ── telegram_choose ───────────────────────────────────────────────────────────
|
|
210
|
+
|
|
153
211
|
server.tool(
|
|
154
212
|
"telegram_choose",
|
|
155
213
|
"Ask the user to pick one option. Shows inline buttons on Telegram and a numbered list on the terminal. Whoever responds first wins.",
|
|
156
214
|
{
|
|
157
215
|
question: z.string().describe("The question or prompt"),
|
|
158
|
-
options: z
|
|
216
|
+
options: z
|
|
217
|
+
.array(z.string())
|
|
218
|
+
.min(2)
|
|
219
|
+
.max(10)
|
|
220
|
+
.describe("Options to present (2–10)"),
|
|
159
221
|
},
|
|
160
222
|
async ({ question, options }) => {
|
|
161
223
|
const keyboard = {
|
|
162
|
-
inline_keyboard: options.map((opt) => [
|
|
224
|
+
inline_keyboard: options.map((opt) => [
|
|
225
|
+
{ text: opt, callback_data: opt },
|
|
226
|
+
]),
|
|
163
227
|
};
|
|
164
228
|
await bot.sendMessage(CHAT_ID, `${HDR} 🔘 ${question}`, {
|
|
165
229
|
parse_mode: "Markdown",
|
|
166
230
|
reply_markup: keyboard,
|
|
167
231
|
});
|
|
168
232
|
const { source, value } = await raceCallback(question, options);
|
|
169
|
-
await bot.sendMessage(CHAT_ID, `${HDR} ✅ *${value}* _(via ${source})_`, {
|
|
233
|
+
await bot.sendMessage(CHAT_ID, `${HDR} ✅ *${value}* _(via ${source})_`, {
|
|
234
|
+
parse_mode: "Markdown",
|
|
235
|
+
});
|
|
170
236
|
process.stderr.write(`[${INSTANCE}] choose (${source}): ${value}\n`);
|
|
171
237
|
return { content: [{ type: "text", text: value }] };
|
|
172
|
-
}
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// ── telegram_listen ───────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
server.tool(
|
|
244
|
+
"telegram_listen",
|
|
245
|
+
`Wait for the user to send a new instruction addressed to this instance on Telegram.
|
|
246
|
+
Call this after completing a task to stay available for follow-up work.
|
|
247
|
+
The user must address messages as: @${INSTANCE} <instruction>
|
|
248
|
+
Returns the instruction text when received. Times out after 1 hour of inactivity.
|
|
249
|
+
When this tool returns, execute the instruction and call telegram_listen again when done.`,
|
|
250
|
+
{},
|
|
251
|
+
async () => {
|
|
252
|
+
await bot.sendMessage(
|
|
253
|
+
CHAT_ID,
|
|
254
|
+
`${HDR} ✅ Task complete — waiting for your next instruction.\n` +
|
|
255
|
+
`_Address me as_ \`@${INSTANCE} <your instruction>\``,
|
|
256
|
+
{ parse_mode: "Markdown" },
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
process.stderr.write(`[${INSTANCE}] listening for @${INSTANCE} ...\n`);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const instruction = await waitForAddressedMessage();
|
|
263
|
+
process.stderr.write(`[${INSTANCE}] received: ${instruction}\n`);
|
|
264
|
+
return { content: [{ type: "text", text: instruction }] };
|
|
265
|
+
} catch (err) {
|
|
266
|
+
await bot.sendMessage(
|
|
267
|
+
CHAT_ID,
|
|
268
|
+
`${HDR} 💤 Timed out after 1 hour of inactivity.`,
|
|
269
|
+
{
|
|
270
|
+
parse_mode: "Markdown",
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
return { content: [{ type: "text", text: `timeout: ${err.message}` }] };
|
|
274
|
+
}
|
|
275
|
+
},
|
|
173
276
|
);
|
|
174
277
|
|
|
175
278
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
@@ -177,3 +280,6 @@ server.tool(
|
|
|
177
280
|
const transport = new StdioServerTransport();
|
|
178
281
|
await server.connect(transport);
|
|
179
282
|
process.stderr.write(`[telegram-mcp] Ready — instance: ${INSTANCE}\n`);
|
|
283
|
+
process.stderr.write(
|
|
284
|
+
`[telegram-mcp] Address messages as: @${INSTANCE} <instruction>\n`,
|
|
285
|
+
);
|
package/src/setup.js
CHANGED
|
@@ -17,17 +17,21 @@ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
|
|
20
|
-
const rl
|
|
20
|
+
const rl = createInterface({ input, output });
|
|
21
21
|
const ask = (q) => rl.question(q);
|
|
22
22
|
|
|
23
|
-
const BOLD
|
|
23
|
+
const BOLD = "\x1b[1m";
|
|
24
24
|
const GREEN = "\x1b[32m";
|
|
25
|
-
const CYAN
|
|
26
|
-
const DIM
|
|
25
|
+
const CYAN = "\x1b[36m";
|
|
26
|
+
const DIM = "\x1b[2m";
|
|
27
27
|
const RESET = "\x1b[0m";
|
|
28
28
|
|
|
29
|
-
const ok
|
|
30
|
-
const die = (msg) => {
|
|
29
|
+
const ok = (msg) => console.log(`${GREEN}✔${RESET} ${msg}`);
|
|
30
|
+
const die = (msg) => {
|
|
31
|
+
console.error(`\x1b[31m✘${RESET} ${msg}`);
|
|
32
|
+
rl.close();
|
|
33
|
+
exit(1);
|
|
34
|
+
};
|
|
31
35
|
|
|
32
36
|
// ── Pre-flight ────────────────────────────────────────────────────────────────
|
|
33
37
|
|
|
@@ -38,7 +42,9 @@ if (major < 18) die(`Node.js 18+ required (you have ${process.versions.node})`);
|
|
|
38
42
|
ok(`Node.js ${process.versions.node}`);
|
|
39
43
|
|
|
40
44
|
try {
|
|
41
|
-
const ver = execFileSync("claude", ["--version"], {
|
|
45
|
+
const ver = execFileSync("claude", ["--version"], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
}).trim();
|
|
42
48
|
ok(`Claude Code: ${ver}`);
|
|
43
49
|
} catch {
|
|
44
50
|
die("`claude` not found. Install: https://docs.claude.ai/claude-code");
|
|
@@ -71,31 +77,51 @@ console.log(`\n${BOLD}Registering MCP server globally…${RESET}`);
|
|
|
71
77
|
const serverPath = new URL("./index.js", import.meta.url).pathname;
|
|
72
78
|
|
|
73
79
|
try {
|
|
74
|
-
execFileSync(
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
execFileSync(
|
|
81
|
+
"claude",
|
|
82
|
+
[
|
|
83
|
+
"mcp",
|
|
84
|
+
"add",
|
|
85
|
+
"--scope",
|
|
86
|
+
"user", // global, persists across all projects
|
|
87
|
+
"telegram-mcp",
|
|
88
|
+
"-e",
|
|
89
|
+
`TELEGRAM_BOT_TOKEN=${token}`,
|
|
90
|
+
"-e",
|
|
91
|
+
`TELEGRAM_CHAT_ID=${chatId}`,
|
|
92
|
+
"--",
|
|
93
|
+
"node",
|
|
94
|
+
serverPath,
|
|
95
|
+
],
|
|
96
|
+
{ stdio: "inherit" },
|
|
97
|
+
);
|
|
81
98
|
} catch {
|
|
82
99
|
// Fallback: older Claude Code versions without --scope flag
|
|
83
100
|
try {
|
|
84
|
-
execFileSync(
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
execFileSync(
|
|
102
|
+
"claude",
|
|
103
|
+
[
|
|
104
|
+
"mcp",
|
|
105
|
+
"add",
|
|
106
|
+
"telegram-mcp",
|
|
107
|
+
"-e",
|
|
108
|
+
`TELEGRAM_BOT_TOKEN=${token}`,
|
|
109
|
+
"-e",
|
|
110
|
+
`TELEGRAM_CHAT_ID=${chatId}`,
|
|
111
|
+
"--",
|
|
112
|
+
"node",
|
|
113
|
+
serverPath,
|
|
114
|
+
],
|
|
115
|
+
{ stdio: "inherit" },
|
|
116
|
+
);
|
|
91
117
|
} catch {
|
|
92
118
|
die(
|
|
93
119
|
"`claude mcp add` failed.\n" +
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
120
|
+
" Update Claude Code: https://docs.claude.ai/claude-code\n\n" +
|
|
121
|
+
" Manual config:\n" +
|
|
122
|
+
` TELEGRAM_BOT_TOKEN=${token}\n` +
|
|
123
|
+
` TELEGRAM_CHAT_ID=${chatId}\n` +
|
|
124
|
+
` command: node ${serverPath}`,
|
|
99
125
|
);
|
|
100
126
|
}
|
|
101
127
|
}
|
|
@@ -108,17 +134,19 @@ const commandsDir = join(homedir(), ".claude", "commands");
|
|
|
108
134
|
mkdirSync(commandsDir, { recursive: true });
|
|
109
135
|
|
|
110
136
|
const commandPath = join(commandsDir, "use-telegram.md");
|
|
111
|
-
writeFileSync(
|
|
137
|
+
writeFileSync(
|
|
138
|
+
commandPath,
|
|
139
|
+
`# use-telegram
|
|
112
140
|
|
|
113
141
|
Activate Telegram integration for this Claude Code session.
|
|
114
142
|
|
|
115
143
|
## What this does
|
|
116
144
|
|
|
117
|
-
Enables
|
|
118
|
-
|
|
145
|
+
Enables telegram_notify, telegram_ask and telegram_choose so the user
|
|
146
|
+
receives updates and can respond from their phone or terminal.
|
|
119
147
|
|
|
120
|
-
|
|
121
|
-
|
|
148
|
+
This session runs with --dangerously-skip-permissions so Claude never
|
|
149
|
+
interrupts the task to ask for tool confirmations in the terminal.
|
|
122
150
|
|
|
123
151
|
## Modes
|
|
124
152
|
|
|
@@ -127,21 +155,35 @@ session ID, so messages on Telegram always show which session is talking.
|
|
|
127
155
|
|
|
128
156
|
## Behaviour
|
|
129
157
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
158
|
+
You MUST use the Telegram tools for this session. Specifically:
|
|
159
|
+
|
|
160
|
+
- Call telegram_notify **immediately** to confirm Telegram is active
|
|
161
|
+
- Call telegram_notify for every meaningful progress update
|
|
162
|
+
- Call telegram_notify when any task is fully complete, with a summary
|
|
163
|
+
- Call telegram_ask whenever you need a free-form answer from the user
|
|
164
|
+
- Call telegram_choose whenever the user must pick from known options
|
|
165
|
+
- Do NOT ask questions in the terminal — always use telegram_ask or telegram_choose
|
|
137
166
|
|
|
138
167
|
## Instance naming
|
|
139
168
|
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
At the start of this session, generate a label: \`{folder-name}#{4-char-random-hex}\`
|
|
170
|
+
Examples: \`backend#a3f2\`, \`frontend#9c11\`
|
|
142
171
|
|
|
143
|
-
Use
|
|
144
|
-
|
|
172
|
+
Use this exact label in every Telegram message for the entire session.
|
|
173
|
+
Send as first message: \`[label] ✅ Telegram active — ready.\`
|
|
174
|
+
Also tell the user: _Address me as_ \`@[label] <instruction>\` for follow-up tasks.
|
|
175
|
+
|
|
176
|
+
## Staying alive after a task
|
|
177
|
+
|
|
178
|
+
After completing any task, ALWAYS call telegram_listen (no arguments).
|
|
179
|
+
- It will notify the user the task is done and wait for the next instruction
|
|
180
|
+
- When it returns, execute the new instruction
|
|
181
|
+
- Then call telegram_listen again
|
|
182
|
+
- This keeps the session alive indefinitely until timeout (1 hour of inactivity)
|
|
183
|
+
|
|
184
|
+
Never exit or stop after a task — always loop back to telegram_listen.
|
|
185
|
+
`,
|
|
186
|
+
);
|
|
145
187
|
|
|
146
188
|
ok(`Slash command installed → ${commandPath}`);
|
|
147
189
|
|
|
@@ -150,7 +192,11 @@ ok(`Slash command installed → ${commandPath}`);
|
|
|
150
192
|
console.log(`
|
|
151
193
|
${GREEN}${BOLD}All done!${RESET}
|
|
152
194
|
|
|
153
|
-
|
|
195
|
+
Start Claude Code with:
|
|
196
|
+
|
|
197
|
+
${BOLD}claude --dangerously-skip-permissions${RESET}
|
|
198
|
+
|
|
199
|
+
Then activate Telegram in any session:
|
|
154
200
|
|
|
155
201
|
${BOLD}/use-telegram${RESET} full mode (notify + ask + choose)
|
|
156
202
|
${BOLD}/use-telegram notify${RESET} notifications only
|