@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.
@@ -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
- In any Claude Code session, activate Telegram with the slash command:
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.0.0",
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": { "node": ">=18" },
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
- * Instance label is auto-generated from cwd + ppid.
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 = env.TELEGRAM_BOT_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
- " Run: npx @thxmxx/telegram-mcp init\n"
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 = process.cwd().split("/").pop() || "claude";
53
- const shortId = randomBytes(2).toString("hex"); // e.g. "a3f2"
54
- const INSTANCE = `${folder}#${shortId}`;
55
- const HDR = `\`[${INSTANCE}]\``;
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) => { rl.close(); resolve(line.trim()); });
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(`${question}\n${numbered}\nChoose (1-${options.length})`).then((v) => {
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}`, { parse_mode: "Markdown" });
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 is shown on the terminal — whoever answers first wins.",
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}`, { parse_mode: "Markdown" });
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(CHAT_ID, `${HDR} ✅ Answered from terminal: *${value}*`, { parse_mode: "Markdown" });
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.array(z.string()).min(2).max(10).describe("Options to present (2–10)"),
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) => [{ text: opt, callback_data: 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})_`, { parse_mode: "Markdown" });
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 = createInterface({ input, output });
20
+ const rl = createInterface({ input, output });
21
21
  const ask = (q) => rl.question(q);
22
22
 
23
- const BOLD = "\x1b[1m";
23
+ const BOLD = "\x1b[1m";
24
24
  const GREEN = "\x1b[32m";
25
- const CYAN = "\x1b[36m";
26
- const DIM = "\x1b[2m";
25
+ const CYAN = "\x1b[36m";
26
+ const DIM = "\x1b[2m";
27
27
  const RESET = "\x1b[0m";
28
28
 
29
- const ok = (msg) => console.log(`${GREEN}✔${RESET} ${msg}`);
30
- const die = (msg) => { console.error(`\x1b[31m✘${RESET} ${msg}`); rl.close(); exit(1); };
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"], { encoding: "utf8" }).trim();
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("claude", [
75
- "mcp", "add", "--scope", "user", // global, persists across all projects
76
- "telegram-mcp",
77
- "-e", `TELEGRAM_BOT_TOKEN=${token}`,
78
- "-e", `TELEGRAM_CHAT_ID=${chatId}`,
79
- "--", "node", serverPath,
80
- ], { stdio: "inherit" });
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("claude", [
85
- "mcp", "add",
86
- "telegram-mcp",
87
- "-e", `TELEGRAM_BOT_TOKEN=${token}`,
88
- "-e", `TELEGRAM_CHAT_ID=${chatId}`,
89
- "--", "node", serverPath,
90
- ], { stdio: "inherit" });
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
- " Update Claude Code: https://docs.claude.ai/claude-code\n\n" +
95
- " Manual config:\n" +
96
- ` TELEGRAM_BOT_TOKEN=${token}\n` +
97
- ` TELEGRAM_CHAT_ID=${chatId}\n` +
98
- ` command: node ${serverPath}`
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(commandPath, `# use-telegram
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 the telegram_notify, telegram_ask and telegram_choose tools so you
118
- can be notified and asked questions on your phone while Claude works.
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
- The instance name is derived automatically from the current folder and
121
- session ID, so messages on Telegram always show which session is talking.
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
- When this command is active:
131
- - Use telegram_notify for progress updates and completed tasks
132
- - Use telegram_ask when you need a free-form answer from the user
133
- - Use telegram_choose when the user must pick from known options
134
- - Always prefix messages with the auto-generated instance label
135
- - Mirror every question to the terminal too whoever answers first wins
136
- - After any long-running task, send a completion summary via telegram_notify
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
- Generate the instance label as: \`{folder-name}#{short-session-id}\`
141
- Example: \`backend#a3f2\`, \`frontend#9c11\`
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 the same label for the entire session.
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
- Restart Claude Code. From now on, in any session:
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