better-tg-cli 0.37.0 → 0.38.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-tg-cli",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Telegram for your terminal and your AI agents: read, search, send and press bot buttons on your own account. MCP server included.",
5
5
  "mcpName": "io.github.TheVilfer/better-tg-cli",
6
6
  "keywords": [
@@ -69,6 +69,7 @@
69
69
  "test:watch": "vitest",
70
70
  "pretest": "node scripts/gen-version.mjs",
71
71
  "typecheck": "tsc --noEmit",
72
+ "coverage:api": "node scripts/api-coverage.mjs --check --min 90",
72
73
  "typecheck:watch": "tsc --noEmit --watch --preserveWatchOutput"
73
74
  },
74
75
  "devDependencies": {
@@ -60,8 +60,72 @@ telegram link <chat> <msgId> # t.me link (channels and supergr
60
60
  telegram watch [chat] -t 60 -n 10 --json # live stream; always bound it with -t or -n
61
61
  telegram sync --chat <c> --output ./dir --resume # incremental markdown export
62
62
  telegram mark-read <chat> ; telegram mute <chat> -d 8h
63
+ telegram drafts ; telegram admin-log <group> ; telegram profile-photos <user>
64
+ telegram permissions <group> [user] # a member's rights, or the chat default
65
+ telegram stats <chat> [--message <id>] # full channel/group stats, or one post's stats
66
+ telegram scheduled <chat> # messages queued to send later
67
+ telegram sessions ; telegram stars ; telegram invites <chat> [--requests] # sessions, Stars, invite links/join requests
68
+ telegram read <chat> --full # every message stat (views, reactions, flags, media); also on get/search/inbox/watch
69
+ telegram pin-chat <chat> ; telegram mark-unread <chat> ; telegram delete-dialog <chat>
70
+ telegram edit-permissions <group> [user] --mute|--ban|--lift|--deny send,media [--until 1d]
71
+ telegram topic create|edit|close|reopen|pin|unpin <forum> <title|id> # forum topics
72
+ telegram api <namespace.Method> --params '<json>' # any raw TL method; full API reachability
73
+ telegram <namespace> <method> [--typed-flags] # same method as its own command (823 of them)
63
74
  ```
64
75
 
76
+ **Prefer a curated command above** when one exists: they give friendly/compact output, pagination,
77
+ multi-step logic and safety prompts (`read`, `send`, `search`, `chats`, …). Drop to the raw API only
78
+ for a method with no curated command, or when you need the exact raw TL object.
79
+
80
+ `telegram api` and the generated commands both reach 100% of the Telegram client API (JSON out) and
81
+ share one classification: reads are `Get*`/`Check*`/`Search*`/`Resolve*` (run free, go through
82
+ `telegram_read`), everything else is a write (needs `telegram write-access on`; `telegram_write` over
83
+ MCP). Paying, account deletion, session termination, 2FA/phone changes and location sharing are
84
+ **always refused** (the command still exists and refuses). Use `api` for scripted JSON params; use
85
+ the typed command for a command line.
86
+
87
+ `telegram <namespace> <method>`: the TL class name lower-cased and kebab-cased —
88
+ `messages.GetHistory` → `telegram messages get-history`, `help.GetConfig` → `telegram help get-config`;
89
+ root-level wrappers are top-level commands (`Ping` → `telegram ping`). One `--flag` per parameter
90
+ (camelCase → `--kebab`): peer/user/channel flags take an id/@user/title/`me`; `true`-flags are bare
91
+ switches; vectors take a comma list; constructor-typed params and `--json-params '<json|->'` (stdin
92
+ with `-`; typed flags win) take JSON. Paging/cache cursors (`offsetId`, `hash`, `maxId`, …) default
93
+ to 0. Discover with `telegram <ns> --help` (lists methods) and `telegram <ns> <method> --help`.
94
+
95
+ A few namespace names also name a curated command — `stats`, `stories`, `contacts`, `auth`,
96
+ `folders`, `help`. The curated command is unchanged; the generated methods attach as subcommands and
97
+ are chosen **only when the first word is exactly a method name** (`telegram stats get-broadcast-stats`
98
+ is the API method; `telegram stats "Team"` is the curated stats). A chat literally named like a
99
+ method (e.g. `get-broadcast-stats`) must be passed by its ID or @username.
100
+
101
+ **Discovery workflow (for any task not covered by a curated command):**
102
+ 1. Pick the namespace (`telegram --help` ends with the list + counts; namespace purposes in
103
+ `reference.md` → "Namespace index"). 2. `telegram <ns> --help` lists its methods, each marked
104
+ `[read]`/`[write]`/`[refused]` with a one-line description. 3. `telegram <ns> <method> --help`
105
+ shows the description, access, return type, every param (flag, TL type, required/default,
106
+ description), an example invocation, common errors and the docs link. 4. Run it. For a
107
+ machine-readable form, `telegram schema`, `telegram schema <ns>`, `telegram schema <ns> <method>`
108
+ print JSON ({command, className, access, returns, params, example, docsUrl, description}).
109
+
110
+ Every method's `--help` and `telegram schema` are plain reads (they never execute the method), so
111
+ over MCP they go through `telegram_read` even for a write or refused method
112
+ (`["account","delete-account","--help"]` is safe). Pass only the command path plus `--help`.
113
+
114
+ ```bash
115
+ telegram channels get-full-channel --channel @group # read, peer resolved like everywhere
116
+ telegram messages get-history --peer @durov --limit 20 # paging cursors default to 0
117
+ telegram messages send-reaction --peer @me --msg-id 42 \
118
+ --json-params '{"reaction":[{"className":"ReactionEmoji","emoticon":"👍"}]}' # write
119
+ telegram ping --ping-id 1 # root-level method
120
+ telegram account delete-account # exists, but always refused
121
+ ```
122
+
123
+ Errors are raw RPC errors: **FLOOD_WAIT_X** = wait X seconds then retry; `PEER_ID_INVALID` = bad
124
+ chat/user; `CHAT_ADMIN_REQUIRED` = you lack admin rights; `CHANNEL_PRIVATE` = not a member;
125
+ `AUTH_KEY_UNREGISTERED` = session gone. See `reference.md` → "Raw API", "Namespace index" for the
126
+ full flag-type table, clash rules, the `{"_chat":…}`/`{"_base64":…}` conventions and a one-line
127
+ purpose for each of the 25 namespaces.
128
+
65
129
  ## 4. JSON shapes (`--json`)
66
130
 
67
131
  | Command | Shape |
@@ -25,6 +25,44 @@ code. This file only covers behaviour that the flag list can't tell you.
25
25
  and `duration`.
26
26
  - A long post costs as many tokens as its text. Use `--max-text 200` for skimming, then
27
27
  `get <chat> <id>` for the posts that matter.
28
+ - **`--full` (alias `--stats`)** on `read`/`get`/`search`/`inbox`/`watch` adds a `full` object to
29
+ every message (and a short `👁 ↪ ❤️` suffix in compact output). Default output is unchanged
30
+ without it. `full` carries views, forwards, the complete reaction breakdown (each emoji / custom
31
+ emoji id / paid reaction with count and whether you reacted), recent reactors, comments/thread
32
+ info (`replies.comments`, `channelId`, `maxId`, `readMaxId`, recent repliers), edit date, post
33
+ author, pinned/silent/noforwards/post flags, grouped (album) id, via-bot id, TTL, the forward
34
+ header, reply-to info (ids, forum topic, quote), and rich media metadata (poll results, geo,
35
+ venue, contact, webpage, dice, invoice, file name/size/mime/duration/dimensions). Use it for data
36
+ collection — it needs no extra API calls. Over MCP, pass `--full` in `telegram_read` args.
37
+ - `drafts` lists saved-but-unsent drafts (chat title + text). `profile-photos <user>` lists a
38
+ user's profile photos; `avatar <user>` downloads the current one.
39
+ - `scheduled <chat>` lists messages queued to send later (create them with `send --schedule`); it
40
+ takes `--full` like `read`.
41
+ - `sessions` lists active login sessions (device, app, platform, IP, country, last-active; the
42
+ current one is flagged). `stars` shows the Telegram Stars balance and recent transactions.
43
+ `invites <chat>` lists a chat's exported invite links (`--revoked` for revoked ones) or its
44
+ pending join requests (`--requests`). All read-only.
45
+ - `admin-log <chat>` shows a supergroup/channel's recent admin actions (Telegram's "Recent
46
+ actions"), newest first, with `-n` and `-q <text>`. It needs admin rights in that chat.
47
+ - `permissions <chat> [user]` shows a member's status (creator/admin/member/restricted/banned/left)
48
+ and rights, or the chat's default permissions when no user is given. Supergroups and channels
49
+ only. Change rights with `edit-permissions`.
50
+ - `stats <chat>` returns full entity statistics: subscribers/members, online, admins, kicked and
51
+ banned counts, linked discussion chat, slow mode, about, creation date, verified/scam/fake/
52
+ restricted flags (+ reasons), and — where the account has rights — aggregate `activity`:
53
+ - broadcast (`stats.GetBroadcastStats`): `period`, followers and per-post/per-story
54
+ views/shares/reactions each as `{current, previous}`, enabled-notifications %, and
55
+ `recentPosts[]` (`{msgId, views, forwards, reactions}`);
56
+ - megagroup (`stats.GetMegagroupStats`): members/messages/viewers/posters `{current, previous}`
57
+ and resolved `topPosters` `{userId, name, username, messages, avgChars}`, `topAdmins`
58
+ `{userId, name, deleted, kicked, banned}`, `topInviters` `{userId, name, invitations}`.
59
+ - `--graphs` adds each graph as a parsed JSON series (`graphs[] = {name, data}` or `{name, error}`);
60
+ async graphs are loaded via `stats.LoadAsyncGraph`, and a failed graph reports its error without
61
+ failing the rest.
62
+ - `stats <chat> --message <id>` (or `--story <id>`) returns the post's own views/forwards/reactions,
63
+ `publicForwardsCount`, a `publicForwards[]` list (`{chatTitle, msgId, views, date}`, capped by
64
+ `--limit`), and — with `--graphs` — the message's view/reaction graphs.
65
+ - Missing rights or unavailable stats are reported in `activity.error` / `note`, never a hard failure.
28
66
 
29
67
  ## Output
30
68
 
@@ -64,6 +102,21 @@ code. This file only covers behaviour that the flag list can't tell you.
64
102
  everything.
65
103
  - `poll <chat> "Q" A B C` takes `--multiple`, `--quiz --correct N` (1-based) and `--public`.
66
104
  `vote <chat> <id> 2` also counts options from 1.
105
+ - `pin-chat` / `unpin-chat` pin a chat to the top of the dialog list (this is the list position,
106
+ not `pin`, which pins a message inside a chat). `mark-unread <chat>` sets the unread mark;
107
+ `--read` clears it.
108
+ - `delete-dialog <chat>` removes a conversation: it clears a private or basic-group history (with
109
+ `--revoke` it also deletes the other person's copy) and leaves a supergroup or channel. Like all
110
+ writes it needs the exact title, an @username or an ID.
111
+ - `edit-permissions <chat> [user]` sets the chat's default permissions (no user) or restricts one
112
+ member (channels.EditBanned / messages.EditChatDefaultBannedRights). `--mute` makes it read-only,
113
+ `--ban` removes the member, `--lift` clears restrictions, `--deny send,media,polls,links,…` denies
114
+ specific rights, and `--until 1h|1d|1w` makes a per-user restriction temporary. Inspect current
115
+ rights with `permissions`.
116
+ - `topic create|edit|close|reopen|pin|unpin <chat> …` manages forum topics in a group with Topics
117
+ enabled: `topic create <chat> <title>` (`--icon-color`, `--icon-emoji`), `topic edit <chat> <id>`
118
+ (`--title`, `--icon-emoji`, `--hide`/`--show`), and `topic close|reopen|pin|unpin <chat> <id>`.
119
+ List topics with `topics <chat>`.
67
120
 
68
121
  ### Bot buttons
69
122
 
@@ -102,6 +155,172 @@ Buttons that need 2FA take `--password`. `--no-wait` skips waiting for the bot's
102
155
  - `sync --resume` is incremental. Checkpoints are per chat ID in `DIR/.sync-meta.json`. A resumed
103
156
  run fetches everything after the checkpoint and ignores the default `--days` window.
104
157
 
158
+ ## Raw API (`telegram api` and `telegram <namespace> <method>`)
159
+
160
+ Two ways to reach the whole Telegram client API when no curated command fits. They share one
161
+ execution path — identical read/write classification, hard refusals and audit log — and both print
162
+ compact JSON (big integers as strings, bytes as base64). Pick `api` for scripted JSON params, the
163
+ typed per-method command for a command line.
164
+
165
+ ### `telegram api <namespace.Method> --params '<json>'`
166
+
167
+ Builds and sends any request from the schema. The method is case-insensitive (`messages.GetHistory`,
168
+ `help.GetConfig`, top-level `InvokeWithLayer`). Inside `--params`, a small convention handles the
169
+ awkward fields: `{"_chat": "<id|@user|title|me>"}` resolves a chat the way every command does (strict
170
+ under a write), `{"_base64": "…"}` / `{"_hex": "…"}` pass raw bytes, `{"_bigint": "…"}` a `long`.
171
+ Other entity-like fields also accept a bare `@username`/ID. `--params -` reads the JSON from stdin.
172
+
173
+ ### `telegram <namespace> <method>` — one typed command per TL method
174
+
175
+ Every TL schema method (all 823, generated into `src/generated/tl-commands.ts`) has its own top-level
176
+ command.
177
+
178
+ - **Naming.** The TL class name, split at the dot and kebab-cased:
179
+ `messages.GetHistory` → `telegram messages get-history`, `help.GetConfig` →
180
+ `telegram help get-config`, `channels.GetFullChannel` → `telegram channels get-full-channel`.
181
+ The 21 root-level invoke/transport wrappers have no namespace and are top-level commands:
182
+ `Ping` → `telegram ping`, `InvokeWithLayer` → `telegram invoke-with-layer`, `ReqPq` → `telegram req-pq`.
183
+ - **Clash with curated commands.** Six namespace names are also curated commands — `stats`,
184
+ `stories`, `contacts`, `auth`, `folders`, `help`. The curated command keeps its behaviour, args and
185
+ output; the generated methods attach to it as subcommands and are picked **only when the first
186
+ operand is exactly a generated method name**. So `telegram stats get-broadcast-stats --channel @c`
187
+ is the API method, while `telegram stats "Team"` and `telegram stats @c --message 5` are the curated
188
+ `stats`. A chat/user literally named like a method (e.g. a chat called `get-broadcast-stats`) must be
189
+ addressed by ID to avoid the method. A clash command's `--help` shows its curated usage plus a list
190
+ of its generated methods. (`telegram help <method>` runs a `help.*` method, while `telegram help` and
191
+ `telegram help <command>` still show help.)
192
+ - **Discovery.** `telegram --help` ends with a "Telegram API" section listing the namespaces and
193
+ their method counts (purposes in "Namespace index" below). `telegram <ns> --help` lists a
194
+ namespace's methods, each marked `[read]`/`[write]`/`[refused]` with a one-line description.
195
+ `telegram <ns> <method> --help` shows the description, access, return type, every param (flag, TL
196
+ type, required/optional/default, description), an auto-generated example, common errors and the
197
+ core.telegram.org docs link. Those descriptions are the official ones, fetched once into
198
+ `scripts/data/tl-docs-layer229.json` (40 methods new in the layer have a fallback line); never
199
+ fetched at runtime.
200
+ - **Machine-readable: `telegram schema`.** `telegram schema` prints the namespace index as JSON;
201
+ `telegram schema <ns>` lists a namespace's methods (`{command, className, access, description}`);
202
+ `telegram schema <ns> <method>` (or `telegram schema <ns>.<Method>`) prints the full method schema
203
+ `{command, className, access, refusedReason?, returns, params:[{flag,name,type,required,default,description}], example, docsUrl, description}`.
204
+ It is a pure local read (no account, no network).
205
+ - **Flags.** One `--flag` per parameter, the camelCase param name kebab-cased (`offsetId` →
206
+ `--offset-id`, `scheduleDate` → `--schedule-date`). Pass each by its TL type:
207
+
208
+ | TL type | Flag form | How to pass |
209
+ |---|---|---|
210
+ | `int`, `double` | `--limit 20` | a number |
211
+ | `long`, `int128`, `int256` | `--hash 0` | digits (sent as a big integer) |
212
+ | `string` | `--q "term"` | text |
213
+ | `Bool` | `--offline true` | `true`/`false` (or `1`/`0`) |
214
+ | `true` (flag bit, e.g. `silent`, `noWebpage`) | `--silent`, `--no-webpage` | bare switch; present = set |
215
+ | `bytes` | `--file-hash aGk=` | base64, or `hex:…` |
216
+ | `InputPeer`/`InputUser`/`InputChannel` | `--peer @durov` | id, `@username`, chat title or `me` |
217
+ | vector of a primitive | `--id 100,101,102` | comma list (or a JSON array) |
218
+ | vector of a constructor | `--reaction '[{"className":"ReactionEmoji","emoticon":"👍"}]'` | JSON array |
219
+ | constructor type (`InputMedia`, `ReplyMarkup`, `DataJSON`, …) | `--reply-to '{"className":"InputReplyToMessage","replyToMsgId":42}'` | JSON (may use `{"_chat":…}`/`{"_base64":…}` markers) |
220
+
221
+ - **`--json-params '<json|->'`** merges a JSON object for anything not worth a flag (e.g. a deep
222
+ nested constructor); typed flags override its keys, and `-` reads the JSON from stdin. Example —
223
+ a nested `replyTo` constructor as JSON, the simple fields as flags:
224
+
225
+ ```bash
226
+ telegram messages send-message --peer @me --message hi \
227
+ --json-params '{"replyTo":{"className":"InputReplyToMessage","replyToMsgId":42}}'
228
+ ```
229
+
230
+ - **Paging/cache cursors default to 0.** A required `int`/`long` named `offsetId`, `offsetDate`,
231
+ `addOffset`, `offsetRate`, `offsetTopic`, `offset`, `maxId`, `minId`, `maxDate`, `minDate` or
232
+ `hash` defaults to 0 when omitted (shown as `default 0` in `--help`), so
233
+ `telegram messages get-history --peer @durov --limit 20` just works. Only these exact numeric
234
+ names — ids (`msgId`/`id`/`userId`), `limit`, `offsetPeer` and string hashes are never defaulted.
235
+ - **Other required params** are enforced with a clear error (`Missing required option(s): --peer …`);
236
+ `randomId` is never required (teleproto generates it). Registration is lazy — only the invoked
237
+ namespace/method is built — so startup, top-level `--help` and `help-all` stay flat.
238
+
239
+ ### Read vs write and refusals (both commands)
240
+
241
+ - **Read vs write:** methods named `Get*`/`Check*`/`Search*`/`Resolve*` are reads and run without
242
+ write access (available through the MCP read tool); everything else is a write that needs
243
+ `telegram write-access on` (the `telegram_write` MCP tool), goes through the write guard, and is
244
+ logged to the audit file.
245
+ - **Always refused**, even with write access (the command still exists and refuses, exit 1; its
246
+ `--help` says "always refused"): the whole `auth.*` namespace (login/session — use `telegram auth`),
247
+ account/session takeover and 2FA/password/phone changes, data export (takeout) and Passport
248
+ identity docs, external-site login (url-auth), ownership transfer, money-withdrawal tokens and
249
+ paying / Stars-spending, and anything that shares your phone number or location (including
250
+ contact/geo media in `SendMedia`). A few `Get*` methods that actually change state (e.g.
251
+ `messages.GetMessagesViews`) are treated as writes. Read-only payments (balances, transactions) and
252
+ session *listing* (`account.GetAuthorizations`) are allowed. Full table in `docs/api-coverage.md` §4.
253
+ - Coverage of the full API is tracked in `docs/api-coverage.md` (regenerated by
254
+ `scripts/api-coverage.mjs`).
255
+
256
+ ### When to use which
257
+
258
+ - **Curated command** (`read`, `send`, `search`, `chats`, `members`, …) — the default. Friendly,
259
+ compact output, pagination, multi-step logic and safety prompts. Always prefer it when one exists.
260
+ - **`telegram <ns> <method>`** — a method with no curated command, or when you need the exact raw
261
+ TL object, from a typed command line.
262
+ - **`telegram api <ns.Method> --params`** — the same reach, when it is easier to hand it a whole JSON
263
+ body (scripts, deeply nested requests).
264
+
265
+ ### Examples
266
+
267
+ ```bash
268
+ telegram help get-config # read, no args (help.* method)
269
+ telegram channels get-full-channel --channel @group # read, peer resolved like everywhere
270
+ telegram messages get-history --peer @durov --limit 20 # read: paging cursors default to 0
271
+ telegram stats get-broadcast-stats --channel @c # a method on a clash namespace
272
+ telegram messages delete-messages --id 100,101,102 --revoke # write: vector + true-flag
273
+ telegram messages send-reaction --peer @me --msg-id 42 \
274
+ --json-params '{"reaction":[{"className":"ReactionEmoji","emoticon":"👍"}]}' # write, nested
275
+ telegram ping --ping-id 1 # root-level method
276
+ telegram account delete-account # exists, always refused (exit 1)
277
+ echo '{"id":[100,101]}' | telegram messages delete-messages --json-params - # JSON from stdin
278
+ ```
279
+
280
+ ### Namespace index
281
+
282
+ One line per TL namespace (`telegram <ns> --help` lists the methods). Prefer a curated command when
283
+ one exists; use these for everything else.
284
+
285
+ | Namespace | What it covers |
286
+ |---|---|
287
+ | `messages` | Chats & dialogs, history, sending, search, reactions, drafts, polls, scheduled, pins |
288
+ | `account` | Profile, privacy, active sessions, 2FA, notifications, usernames, business settings |
289
+ | `payments` | Stars, gifts, invoices, subscriptions (any paying method is refused) |
290
+ | `channels` | Supergroups/channels: members, admin rights, forum topics, invite links, admin log |
291
+ | `phone` | Voice/video calls & group calls (no real CLI use) |
292
+ | `bots` | Bot management and bot-only methods |
293
+ | `stories` | Stories: feeds, views, reactions, posting |
294
+ | `auth` | Login/session management — refused; use `telegram auth`/`onboard` |
295
+ | `contacts` | Contacts, blocked list, top peers, resolve usernames |
296
+ | `help` | Client config, app updates, terms, country list |
297
+ | `chatlists` | Shareable folder (chat-list) invites |
298
+ | `stickers` | Sticker-set creation and editing |
299
+ | `communities` | Community management (new in the layer) |
300
+ | `ephemeral` | Ephemeral/one-time content (new in the layer) |
301
+ | `stats` | Channel/supergroup/message/story statistics |
302
+ | `upload` | File upload/download transport (driven by send-file/download) |
303
+ | `aicompose` | AI message composing (new in the layer) |
304
+ | `smsjobs` | SMS-relay programme |
305
+ | `users` | User info / full user |
306
+ | `langpack` | Client localisation packs |
307
+ | `photos` | Profile photos |
308
+ | `premium` | Telegram Premium boosts |
309
+ | `updates` | Update state/difference (driven by `watch`) |
310
+ | `folders` | Dialog folders (archive) |
311
+ | `fragment` | Fragment collectible usernames/numbers |
312
+
313
+ Root-level methods (no namespace) are low-level transport/invoke wrappers — `telegram ping`,
314
+ `telegram invoke-with-layer`, `telegram req-pq`, … — rarely needed directly.
315
+
316
+ ### Errors
317
+
318
+ Methods return raw RPC errors. The common ones: **FLOOD_WAIT_X** — wait X seconds and retry (the
319
+ number is in the name); `PEER_ID_INVALID` — the chat/user couldn't be resolved; `CHAT_ADMIN_REQUIRED`
320
+ — you lack the needed admin right; `CHANNEL_PRIVATE` — you're not a member; `MESSAGE_ID_INVALID` — no
321
+ such message; `AUTH_KEY_UNREGISTERED`/`SESSION_REVOKED` — the session is gone (log in again). A
322
+ method's `--help` lists its own "Common errors" when the docs provide them.
323
+
105
324
  ## MCP server (`telegram mcp`)
106
325
 
107
326
  - **Tools:**
@@ -115,8 +334,17 @@ Buttons that need 2FA take `--password`. `--no-wait` skips waiting for the bot's
115
334
  - Read tool: only the read commands.
116
335
  - Write tool: everything that changes the account or writes local files (`download`, `sync`,
117
336
  `avatar`).
337
+ - `api` and the generated commands are classified per underlying method: a read goes through the
338
+ read tool, a write through the write tool, a refused method never runs — `["messages","get-history",…]`
339
+ and `["ping"]` like `["api","messages.GetHistory",…]`. On a clash namespace the method form is
340
+ classified by its method (`["stats","get-broadcast-stats",…]`) while the curated form keeps its
341
+ own class (`["stats","@chat"]` is a read); `["auth","send-code"]` is refused.
342
+ - Help is always a read: a bare help request (a command path plus `--help`/`-h`, optionally
343
+ `--json`) and `telegram schema …` go through the read tool even for a write or refused method
344
+ (`["account","delete-account","--help"]`) — help prints, it never executes the method. Add no
345
+ other args: with extra args (`["send","@x","--","--help"]`) the call is classified normally.
118
346
  - Never over MCP: `auth`, `onboard`, `logout`, `transfer-owner`, `update`, `skill`, and changing `write-access`.
119
- Showing its status is allowed.
347
+ Showing its status is allowed (a generated `auth.*` method is refused, not run).
120
348
  - **Safety:** the read tool runs with `TG_READ_ONLY=1`, which blocks every guarded write even if a
121
349
  command were misclassified.
122
350
  - **Execution:** each call is a separate CLI process with no stdin (a worker thread inside Claude