tlgr-cli 2.0.1__py3-none-any.whl

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.
Files changed (192) hide show
  1. tlgr/__init__.py +3 -0
  2. tlgr/__main__.py +6 -0
  3. tlgr/actions/__init__.py +45 -0
  4. tlgr/actions/forward.py +74 -0
  5. tlgr/actions/reply.py +32 -0
  6. tlgr/cli/__init__.py +259 -0
  7. tlgr/cli/confirm.py +55 -0
  8. tlgr/cli/errors.py +84 -0
  9. tlgr/cli/gen.py +690 -0
  10. tlgr/cli/globals.py +273 -0
  11. tlgr/cli/introspect.py +170 -0
  12. tlgr/cli/params.py +189 -0
  13. tlgr/cli/render.py +418 -0
  14. tlgr/core/__init__.py +0 -0
  15. tlgr/core/accounts.py +384 -0
  16. tlgr/core/config.py +358 -0
  17. tlgr/core/custom_tl.py +170 -0
  18. tlgr/core/errors.py +687 -0
  19. tlgr/core/eventtypes.py +1170 -0
  20. tlgr/core/identity.py +127 -0
  21. tlgr/core/launchd.py +122 -0
  22. tlgr/core/logging.py +194 -0
  23. tlgr/core/media.py +134 -0
  24. tlgr/core/output.py +251 -0
  25. tlgr/core/pagination.py +227 -0
  26. tlgr/core/paths.py +360 -0
  27. tlgr/core/peers.py +427 -0
  28. tlgr/core/process.py +138 -0
  29. tlgr/core/signing.py +38 -0
  30. tlgr/core/systemd.py +96 -0
  31. tlgr/core/telethon_compat.py +295 -0
  32. tlgr/core/text.py +211 -0
  33. tlgr/core/timefmt.py +199 -0
  34. tlgr/core/tl.py +98 -0
  35. tlgr/daemon/__init__.py +0 -0
  36. tlgr/daemon/app.py +869 -0
  37. tlgr/daemon/dispatch.py +446 -0
  38. tlgr/daemon/events.py +723 -0
  39. tlgr/daemon/files.py +431 -0
  40. tlgr/daemon/idle.py +119 -0
  41. tlgr/daemon/jobs.py +68 -0
  42. tlgr/daemon/main.py +161 -0
  43. tlgr/daemon/peercred.py +75 -0
  44. tlgr/daemon/policy.py +113 -0
  45. tlgr/daemon/preauth.py +366 -0
  46. tlgr/daemon/ratelimit.py +391 -0
  47. tlgr/daemon/server.py +24 -0
  48. tlgr/daemon/session.py +648 -0
  49. tlgr/daemon/sessions.py +274 -0
  50. tlgr/daemon/singleton.py +114 -0
  51. tlgr/daemon/stream.py +193 -0
  52. tlgr/daemon/transfers.py +219 -0
  53. tlgr/daemon/webhook.py +390 -0
  54. tlgr/data/catalog_index.json +1 -0
  55. tlgr/data/parity_waivers.toml +90 -0
  56. tlgr/filters/__init__.py +42 -0
  57. tlgr/filters/compose.py +121 -0
  58. tlgr/filters/content.py +85 -0
  59. tlgr/filters/context.py +114 -0
  60. tlgr/filters/message.py +161 -0
  61. tlgr/filters/temporal.py +87 -0
  62. tlgr/filters/user.py +36 -0
  63. tlgr/gateway/__init__.py +1 -0
  64. tlgr/gateway/config.py +161 -0
  65. tlgr/gateway/engine.py +215 -0
  66. tlgr/gateway/event.py +22 -0
  67. tlgr/jobs/__init__.py +0 -0
  68. tlgr/jobs/base.py +81 -0
  69. tlgr/jobs/client.py +37 -0
  70. tlgr/models/__init__.py +1220 -0
  71. tlgr/models/admin.py +744 -0
  72. tlgr/models/auth.py +510 -0
  73. tlgr/models/base.py +81 -0
  74. tlgr/models/bot.py +576 -0
  75. tlgr/models/business.py +265 -0
  76. tlgr/models/call.py +586 -0
  77. tlgr/models/config.py +101 -0
  78. tlgr/models/contact.py +481 -0
  79. tlgr/models/daemon.py +336 -0
  80. tlgr/models/dialog.py +626 -0
  81. tlgr/models/envelope.py +68 -0
  82. tlgr/models/error.py +30 -0
  83. tlgr/models/event.py +79 -0
  84. tlgr/models/export.py +66 -0
  85. tlgr/models/gift.py +275 -0
  86. tlgr/models/inline.py +84 -0
  87. tlgr/models/location.py +115 -0
  88. tlgr/models/media.py +507 -0
  89. tlgr/models/message.py +584 -0
  90. tlgr/models/net.py +232 -0
  91. tlgr/models/notify.py +105 -0
  92. tlgr/models/page.py +32 -0
  93. tlgr/models/payment.py +172 -0
  94. tlgr/models/peer.py +400 -0
  95. tlgr/models/poll.py +119 -0
  96. tlgr/models/premium.py +161 -0
  97. tlgr/models/privacy.py +93 -0
  98. tlgr/models/profile.py +217 -0
  99. tlgr/models/reaction.py +160 -0
  100. tlgr/models/resolve.py +175 -0
  101. tlgr/models/settings.py +103 -0
  102. tlgr/models/stars.py +101 -0
  103. tlgr/models/sticker.py +243 -0
  104. tlgr/models/story.py +467 -0
  105. tlgr/models/sync.py +105 -0
  106. tlgr/models/todo.py +36 -0
  107. tlgr/models/webapp.py +89 -0
  108. tlgr/ops/__init__.py +63 -0
  109. tlgr/ops/_admin.py +313 -0
  110. tlgr/ops/_auth.py +599 -0
  111. tlgr/ops/_bots.py +586 -0
  112. tlgr/ops/_calls.py +535 -0
  113. tlgr/ops/_common.py +160 -0
  114. tlgr/ops/_layer.py +46 -0
  115. tlgr/ops/_media.py +592 -0
  116. tlgr/ops/_params.py +212 -0
  117. tlgr/ops/_rights.py +402 -0
  118. tlgr/ops/_send.py +593 -0
  119. tlgr/ops/_serialize.py +667 -0
  120. tlgr/ops/_settings.py +306 -0
  121. tlgr/ops/_spec.py +167 -0
  122. tlgr/ops/_story.py +743 -0
  123. tlgr/ops/account.py +2604 -0
  124. tlgr/ops/agent.py +937 -0
  125. tlgr/ops/auth.py +1282 -0
  126. tlgr/ops/bot.py +4880 -0
  127. tlgr/ops/business.py +1520 -0
  128. tlgr/ops/call.py +1610 -0
  129. tlgr/ops/chat.py +4025 -0
  130. tlgr/ops/chat_admin.py +929 -0
  131. tlgr/ops/chat_extra.py +1061 -0
  132. tlgr/ops/chat_invite.py +716 -0
  133. tlgr/ops/chat_manage.py +1691 -0
  134. tlgr/ops/chat_member.py +1357 -0
  135. tlgr/ops/chat_stats.py +902 -0
  136. tlgr/ops/chat_topic.py +905 -0
  137. tlgr/ops/conference.py +791 -0
  138. tlgr/ops/config.py +1698 -0
  139. tlgr/ops/contact.py +2330 -0
  140. tlgr/ops/daemon.py +1397 -0
  141. tlgr/ops/draft.py +299 -0
  142. tlgr/ops/emoji.py +343 -0
  143. tlgr/ops/events.py +1327 -0
  144. tlgr/ops/export.py +596 -0
  145. tlgr/ops/folder.py +1322 -0
  146. tlgr/ops/gif.py +522 -0
  147. tlgr/ops/gift.py +1546 -0
  148. tlgr/ops/giveaway.py +541 -0
  149. tlgr/ops/inline.py +773 -0
  150. tlgr/ops/job.py +799 -0
  151. tlgr/ops/location.py +917 -0
  152. tlgr/ops/media.py +4495 -0
  153. tlgr/ops/message.py +3769 -0
  154. tlgr/ops/net.py +536 -0
  155. tlgr/ops/notify.py +840 -0
  156. tlgr/ops/passport.py +464 -0
  157. tlgr/ops/payment.py +907 -0
  158. tlgr/ops/poll.py +1078 -0
  159. tlgr/ops/premium.py +488 -0
  160. tlgr/ops/privacy.py +794 -0
  161. tlgr/ops/profile.py +1481 -0
  162. tlgr/ops/proxy.py +750 -0
  163. tlgr/ops/reaction.py +1475 -0
  164. tlgr/ops/resolve.py +1140 -0
  165. tlgr/ops/search.py +521 -0
  166. tlgr/ops/settings.py +1066 -0
  167. tlgr/ops/stars.py +594 -0
  168. tlgr/ops/sticker.py +1602 -0
  169. tlgr/ops/story.py +3216 -0
  170. tlgr/ops/sync.py +788 -0
  171. tlgr/ops/todo.py +514 -0
  172. tlgr/ops/user.py +1406 -0
  173. tlgr/ops/vc.py +2351 -0
  174. tlgr/ops/webapp.py +717 -0
  175. tlgr/ops/webhook.py +418 -0
  176. tlgr/parity.py +386 -0
  177. tlgr/processors/__init__.py +125 -0
  178. tlgr/processors/regex.py +26 -0
  179. tlgr/processors/text.py +56 -0
  180. tlgr/registry.py +519 -0
  181. tlgr/schema.py +173 -0
  182. tlgr/transport/__init__.py +30 -0
  183. tlgr/transport/autostart.py +293 -0
  184. tlgr/transport/client.py +805 -0
  185. tlgr/transport/ndjson.py +44 -0
  186. tlgr/version.py +31 -0
  187. tlgr_cli-2.0.1.dist-info/METADATA +957 -0
  188. tlgr_cli-2.0.1.dist-info/RECORD +192 -0
  189. tlgr_cli-2.0.1.dist-info/WHEEL +5 -0
  190. tlgr_cli-2.0.1.dist-info/entry_points.txt +2 -0
  191. tlgr_cli-2.0.1.dist-info/licenses/LICENSE +21 -0
  192. tlgr_cli-2.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,957 @@
1
+ Metadata-Version: 2.4
2
+ Name: tlgr-cli
3
+ Version: 2.0.1
4
+ Summary: Full Telegram account control CLI — agent-friendly, daemon-based, with webhook event push
5
+ Author: tlgrcli
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/tlgrcli/tlgr
8
+ Project-URL: Repository, https://github.com/tlgrcli/tlgr
9
+ Project-URL: Issues, https://github.com/tlgrcli/tlgr/issues
10
+ Keywords: telegram,cli,automation,telethon,agent,webhook
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Communications :: Chat
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: telethon~=1.44.0
26
+ Requires-Dist: click>=8.0
27
+ Requires-Dist: aiohttp>=3.9
28
+ Requires-Dist: msgspec<1.0,>=0.18
29
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
30
+ Requires-Dist: tomli-w>=1.0
31
+ Requires-Dist: pyyaml>=6.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
35
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.6; extra == "dev"
37
+ Requires-Dist: mypy>=1.11; extra == "dev"
38
+ Provides-Extra: fast
39
+ Requires-Dist: cryptg>=0.4; extra == "fast"
40
+ Provides-Extra: proxy
41
+ Requires-Dist: python-socks[asyncio]>=2.4; extra == "proxy"
42
+ Provides-Extra: media
43
+ Requires-Dist: pillow>=10.0; extra == "media"
44
+ Requires-Dist: hachoir>=3.2; extra == "media"
45
+ Provides-Extra: qr
46
+ Requires-Dist: segno>=1.6; extra == "qr"
47
+ Dynamic: license-file
48
+
49
+ # tlgr
50
+
51
+ ![GitHub Repo Banner](https://ghrb.waren.build/banner?header=tlgr%F0%9F%A7%AD&subheader=Telegram+in+your+terminal&bg=f3f4f6&color=1f2937&support=true)
52
+ <!-- Created with GitHub Repo Banner by Waren Gonzaga: https://ghrb.waren.build -->
53
+
54
+ Full Telegram account control from the terminal. Agent-friendly, daemon-based, with webhook event push.
55
+
56
+ ```
57
+ pipx install tlgr-cli
58
+ ```
59
+
60
+ The command is `tlgr`; the distribution on PyPI is `tlgr-cli`, because the
61
+ shorter name was taken by an unrelated project. `pipx` is the recommendation
62
+ because tlgr runs a long-lived daemon and wants its own environment;
63
+ `pip install tlgr-cli` into a virtualenv works the same way. To track the
64
+ development tip instead, install from the repository:
65
+
66
+ ```
67
+ pipx install git+https://github.com/tlgrcli/tlgr.git
68
+ ```
69
+
70
+ > **For agents:** logging in is a sequence of ordinary commands — `tlgr auth send-code` then `tlgr auth verify-code` — so only *reading the code* needs a person. Secrets come from `--x-env`/`--x-stdin`/`--x-file`, never argv. See [AGENT.md](AGENT.md) for the full agent reference.
71
+
72
+ > **Coming from tlgr 1.x with a running daemon?** Stop it before you upgrade — two processes on one session file is how an authorization gets revoked. [docs/UPGRADING.md](docs/UPGRADING.md) is the ten-minute cutover, including the six output shapes an agent has to adapt to.
73
+
74
+ ## Quickstart
75
+
76
+ ```bash
77
+ tlgr config init # create config files
78
+ tlgr login +15551234567 # authenticate (shortcut for account add)
79
+ tlgr daemon start # start background daemon
80
+ tlgr send @username "Hello from tlgr" # send a message
81
+ tlgr chats --limit 20 # list your chats
82
+ tlgr status # check daemon status
83
+ ```
84
+
85
+ ## CLI
86
+
87
+ Every Telegram operation available as a single command. Three output modes: human-readable tables (default), JSON (`--json`) for agents, and plain TSV (`--plain`) for piping.
88
+
89
+ ```mermaid
90
+ flowchart LR
91
+ USER["You / Agent"] --> CLI["tlgr CLI"]
92
+ CLI -->|"direct"| TG["Telegram API"]
93
+ CLI -->|"IPC socket"| DAEMON["tlgr daemon"]
94
+ DAEMON --> TG
95
+ ```
96
+
97
+ ### Shortcuts
98
+
99
+ Common operations are available as top-level commands for quick access:
100
+
101
+ ```bash
102
+ tlgr send <chat> <text> # message send
103
+ tlgr login <phone> # account add
104
+ tlgr logout <alias> # account remove
105
+ tlgr status # daemon status
106
+ tlgr chats # chat list
107
+ tlgr contacts # contact list
108
+ tlgr dl <chat> <msg_id>... # media download
109
+ tlgr up <chat> <path>... # media upload
110
+ ```
111
+
112
+ ### Messages
113
+
114
+ ```bash
115
+ tlgr message send <chat> <text> # --file, --caption, --reply-to, --silent, --parse,
116
+ # --schedule, --topic, --send-as, --split
117
+ tlgr message list <chat> # --limit, --cursor, --all, --since, --until
118
+ tlgr message get <chat> <msg_id> # full metadata
119
+ tlgr message delete <chat> <ids...>
120
+ tlgr message search <chat> <query> # --from, --media-type, --cursor
121
+ tlgr message pin <chat> <msg_id> # and: message unpin
122
+ tlgr message react <chat> <id> <emoji> # alias of `reaction add`, see Reactions
123
+ tlgr message read <chat> # --up-to
124
+ tlgr message edit <chat> <id> <text> # --typing N
125
+ tlgr message forward <from> <ids...> --to <chat>
126
+ tlgr message link <chat> <msg_id> # shareable t.me link
127
+ tlgr message entity list <text> # --parse md: what the formatting actually did
128
+ ```
129
+
130
+ `message send` supports `--typing N` / `--typing-auto` to show a realistic
131
+ "typing…" indicator before the message lands. Text over 4096 UTF-16 units is
132
+ refused unless you pass `--split`.
133
+
134
+ That is the everyday tenth of the group. It also has `preview`, `compose`,
135
+ `summarize`, `translate`, `transcribe`, `report`, `thread list`, `view get`,
136
+ `read-receipt list`, `scheduled send`, `paid set`, `fact-check set`,
137
+ `dice list`, `effect list`, `game *`, `sponsored *`, `suggested *` and
138
+ `tone *` — 43 operations in all, generated from one registry, with generated
139
+ reference in [`docs/reference/message.md`](docs/reference/message.md).
140
+
141
+ `msg` is an alias for `message` (e.g. `tlgr msg send @user "hello"`).
142
+
143
+ ### Polls, reactions, checklists and places
144
+
145
+ ```bash
146
+ tlgr poll create <chat> "Lunch?" Pizza Sushi # --quiz --correct N, --multiple,
147
+ # --public-voters, --duration 2h
148
+ tlgr poll vote <chat> <msg_id> 0 # answers are addressed by index
149
+ tlgr poll get <chat> <msg_id> # results, and why you cannot vote
150
+ tlgr poll close <chat> <msg_id> --yes
151
+
152
+ tlgr reaction add <chat> <msg_id> 👍 # keeps the reactions you already had
153
+ tlgr reaction remove <chat> <msg_id> # all of mine
154
+ tlgr reaction user list <chat> <msg_id> # who reacted, per emoji
155
+ tlgr reaction chat set <chat> --some 👍,❤ # what this chat allows (admin)
156
+
157
+ tlgr todo create <chat> "Release" "tag it" "ship it"
158
+ tlgr todo toggle <chat> <msg_id> --done 1 --undone 2
159
+
160
+ tlgr location send <chat> -- <lat> <lon>
161
+ tlgr location live start <chat> -- <lat> <lon> --period 1h
162
+ tlgr search global "release notes" # every chat, one cursor
163
+ ```
164
+
165
+ Three things worth knowing before scripting these. A poll answer is an opaque
166
+ identifier on the wire, so tlgr resolves your index against the server's own
167
+ copy — `--shuffle` cannot make index 1 mean two answers. `sendReaction` carries
168
+ your *whole* reaction set, so `reaction add` reads what is there and resends
169
+ it rather than replacing it, and `mine` in the reply is the set afterwards. A
170
+ checklist task id is never renumbered, because completions are keyed by it.
171
+
172
+ Anything that spends Stars — `reaction pay`, `search post` past its free quota
173
+ — has no default amount and refuses to run without an explicit one.
174
+
175
+ Generated reference: [`poll`](docs/reference/poll.md),
176
+ [`reaction`](docs/reference/reaction.md), [`todo`](docs/reference/todo.md),
177
+ [`location`](docs/reference/location.md), [`search`](docs/reference/search.md).
178
+
179
+ ### Drafts
180
+
181
+ Prepare a reply without sending it — you send (or discard) it later from any
182
+ Telegram client. The human-in-the-loop primitive for agents.
183
+
184
+ ```bash
185
+ tlgr draft set <chat> <text> # --reply-to, --parse
186
+ tlgr draft clear <chat> # --all --yes to clear every draft
187
+ tlgr draft list # all non-empty drafts across chats
188
+ ```
189
+
190
+ `draft set` returns the saved draft, and `draft list` reports marked chat ids
191
+ (`-100…` for channels) with `raw_id` beside them. See
192
+ [`docs/reference/draft.md`](docs/reference/draft.md).
193
+
194
+ ### Chats
195
+
196
+ ```bash
197
+ tlgr chat list # --folder, --type, --search, --unread, --pinned, --muted
198
+ tlgr inbox # shortcut: chat list (add --unread)
199
+ tlgr catchup # every unread chat with its recent messages, read-only
200
+ tlgr chat open <chat> # history AND a read receipt; --no-read to peek
201
+ tlgr chat read <chat>... # the receipt without the history; --folder, --from-file
202
+ tlgr chat unread <chat> # the undo for an accidental receipt
203
+ tlgr chat get <chat> # --full for getFullUser/getFullChat/getFullChannel
204
+ tlgr chat posters <chat> # distinct senders + message counts, walked internally
205
+ tlgr chat archive <chat>... [--undo] # one RPC for any number of peers
206
+ tlgr chat mute <chat> --for 8h # or --until, --forever, --off, --stories, --folder
207
+ tlgr chat pin <chat>... [--unpin] # --folder pins inside a chat folder; --order rewrites it
208
+ tlgr chat clear <chat> --yes # history goes, chat stays
209
+ tlgr chat delete <chat> --yes # --for-both, or --for-everyone if you own it
210
+ tlgr chat leave <chat>... --yes # --delete-history, --remove-from-folders
211
+ tlgr chat typing <chat> # --action record-audio, upload-photo, …
212
+ tlgr chat notify set <chat> --silent on
213
+ tlgr chat ttl set <chat> 1d # auto-delete timer; omit the period to read it
214
+ tlgr chat theme set <chat> --emoji 🌷
215
+ tlgr chat wallpaper set <chat> --slug pattern
216
+ tlgr chat badge get --limits # the unread badge, and the chat-list limits behind it
217
+ tlgr chat report <chat> --spam --yes
218
+ ```
219
+
220
+ Members, admins, invites and topics:
221
+
222
+ ```
223
+ tlgr chat member list <chat> --filter admins # `chat members` still works
224
+ tlgr chat member ban <chat> @spammer --purge --report --yes
225
+ tlgr chat member restrict <chat> @noisy --deny send-media --until 7d
226
+ tlgr chat admin promote <chat> @alice --rights ban-users,delete-messages
227
+ tlgr chat permission list --mask member # the canonical right names
228
+ tlgr chat permission set <chat> --deny send-stickers
229
+ tlgr chat invite create <chat> --limit 25 --expires 7d
230
+ tlgr chat request approve <chat> --all --yes
231
+ tlgr chat topic create <chat> Releases # the id `--topic` takes
232
+ tlgr chat setting set <chat> --slow-mode 30s --hidden-members on
233
+ tlgr chat create <name> --type supergroup # --members, --photo, --username
234
+ tlgr chat stats get <channel> --load-graphs
235
+ tlgr boost add <channel>
236
+ ```
237
+
238
+ `chat list` returns a page of dialogs whose peer is nested under `chat`;
239
+ `chat catchup` and `chat list` never emit a read receipt, `chat open` does on
240
+ purpose, and `chat unread` restores only your own badge. A member row keeps
241
+ its participant wrapper (status, rank, promoter, both rights masks) and every
242
+ mask is allow-polarity, so `chat permission get` round-trips straight back
243
+ into `chat permission set`. Full reference:
244
+ [`docs/reference/chat.md`](docs/reference/chat.md).
245
+
246
+ ### Folders
247
+
248
+ A chat folder is a filter, not a container, so every edit rewrites the whole
249
+ filter in one call — and `--folder <name|id>` works on `chat list`,
250
+ `chat read`, `chat mute`, `chat pin` and `chat badge get`.
251
+
252
+ ```bash
253
+ tlgr folder list --with-counts
254
+ tlgr folder create Work --groups --emoji 💼
255
+ tlgr folder add Work @alice # --pin to pin it inside the folder
256
+ tlgr folder remove Work @alice --exclude
257
+ tlgr folder reorder main Work Family
258
+ tlgr folder join t.me/addlist/SLUG # previews; --chats/--all-chats to actually join
259
+ tlgr folder share set Work --all-eligible
260
+ ```
261
+
262
+ Full reference: [`docs/reference/folder.md`](docs/reference/folder.md).
263
+
264
+ ### Contacts
265
+
266
+ ```bash
267
+ tlgr contact list # --with-status --sort last-seen --export vcard --out FILE
268
+ tlgr contact add <user|+phone> [name] # --first-name --last-name --note --share-phone
269
+ tlgr contact rename <user> # --first-name, --last-name (tags non-contacts too)
270
+ tlgr contact remove <user>... # --phone reaches numbers with no account
271
+ tlgr contact search <query> # --mine-only --global-only --recent
272
+ tlgr contact note set <user> <text> # --clear
273
+ tlgr contact status list # online / last-seen for every contact, in one call
274
+ tlgr contact birthday list
275
+ tlgr contact close-friends list|set
276
+ tlgr contact blocked list|set # --stories for the story blocklist
277
+ tlgr contact top list|set # frequent contacts, by category
278
+ tlgr contact import <file.vcf|csv> # bulk phonebook import
279
+ tlgr contact sync <file> # diff a phonebook against the server (--apply)
280
+ tlgr contact saved list # every number ever uploaded, account or not
281
+ tlgr contact share <user> --to <chat>
282
+ tlgr contact share-phone <user> # irreversible
283
+ ```
284
+
285
+ An empty `contact add` by phone is **ambiguous** — the number may have no
286
+ account, or its owner may refuse lookups by phone — and `reason` says so
287
+ rather than the reply claiming "no such user".
288
+
289
+ ### Users
290
+
291
+ ```bash
292
+ tlgr user get <user> # --full --translate-bio LANG --from-chat/--from-message
293
+ tlgr user dialog-status <user> # does THIS account have prior history with them?
294
+ tlgr user hide-stories <user>... # v1's spelling of `story hide` (--unhide)
295
+ tlgr user block <user> # --stories --report-spam --delete-history
296
+ tlgr user unblock <user>
297
+ tlgr user can-message <user>... # free | premium | paid (and the Stars price)
298
+ tlgr user chat list <user> # groups you share (--leave-all)
299
+ tlgr user link <user> # --profile --text; `me --token` for a contact token
300
+ tlgr user photo list|set <user>
301
+ tlgr user music list <user>
302
+ tlgr user personal-channel get <user>
303
+ tlgr user birthday set <user> <date>
304
+ ```
305
+
306
+ `dialog-status` distinguishes "yes", "definitively no", and "cannot tell"
307
+ (exit 13) instead of guessing. Never infer "no history" from an entity
308
+ resolution error — see AGENT.md for why.
309
+
310
+ `hide-stories` is idempotent: it reads the current flag first and reports
311
+ `already: true` without an RPC, so a bulk pass over hundreds of peers is
312
+ nearly free to repeat.
313
+
314
+ ### Resolving references
315
+
316
+ ```bash
317
+ tlgr resolve peer <ref>... # @username | id | +phone | t.me link | me
318
+ tlgr resolve username <name>
319
+ tlgr resolve phone <+number> # --offline formats and validates, no RPC
320
+ tlgr resolve link <url> # classify any t.me / tg:// link (--open)
321
+ tlgr resolve cache get # inspect the per-account peer database
322
+ ```
323
+
324
+ `resolve link` never *acts*: it says what a link is and names the command
325
+ that would follow it in `delegated_to`. A phone lookup that comes back empty
326
+ exits 13, never 5 — no account and a privacy refusal are indistinguishable.
327
+
328
+ ### Stories
329
+
330
+ ```bash
331
+ tlgr story feed list # the stories bar (--hidden, --unread-only)
332
+ tlgr story list <chat> # active; --profile, --archive, --album ID
333
+ tlgr story get <chat> <id> # --views, --link, --areas-out, --translate
334
+ tlgr story post <file>... # --caption, --privacy, --allow, --exclude,
335
+ # --period, --pin, --album, --area-url, …
336
+ tlgr story read <chat> # clears YOUR ring; --register-view to be seen
337
+ tlgr story react|reply|share <chat> <id>
338
+ tlgr story pin|unpin|hide|unhide <chat> [<id>...]
339
+ tlgr story viewer list <chat> <id> # --contacts, --q, --csv PATH
340
+ tlgr story blocklist set <user>... # "Hide my stories from"
341
+ tlgr story album create|edit|list|delete|reorder <chat> …
342
+ tlgr story can-post | stealth set | search | stats get | export | live start | watch
343
+ ```
344
+
345
+ `story read` clears your own unread ring and tells the poster nothing;
346
+ `--register-view` is what puts you in their viewer list, and it is opt-in.
347
+ `--privacy` sets the base audience and `--allow`/`--exclude` layer exceptions
348
+ on top, in that order, so "contacts, except Bob" is expressible.
349
+
350
+ ### Media, stickers, GIFs and emoji
351
+
352
+ ```bash
353
+ tlgr media get <chat> <msg_id> # kind, size, duration, ids — no download
354
+ tlgr media download <chat> <msg_id>... # --out-dir, --thumb, --range, --resume,
355
+ # --connections, --verify, --all, --read
356
+ tlgr media upload <chat> <path>... # --as, --caption, --spoiler, --album,
357
+ # --ttl, --no-send, --dedupe, --paid-stars
358
+ tlgr media list <chat> --type photo # the shared-media tabs
359
+ tlgr media search <query> # across every chat
360
+ tlgr media export <chat> # resumable archive with a manifest
361
+ tlgr media transfer list|stop|retry # the Downloads panel
362
+ tlgr media limit get # the server's own limits, never guessed
363
+
364
+ tlgr sticker set list|get|add|remove # install / uninstall a set
365
+ tlgr sticker pack create|add|edit # a pack you own (`emoji set …` too)
366
+ tlgr sticker fave|recent|search
367
+ tlgr gif list|add|remove|search|send
368
+ tlgr emoji get|list|search
369
+ ```
370
+
371
+ A file's `file_reference` expires in hours, so every command that touches
372
+ bytes re-fetches its source first; `media file-id get --source` is how a
373
+ stored id is made usable again.
374
+ ### Calls and video chats
375
+
376
+ tlgr speaks the **signalling** half of calls and carries no audio or video: it
377
+ rings, answers, hangs up, mutes, moderates, records and observes, and nobody
378
+ can hear it. Every answer says so (`"media": "none"`).
379
+
380
+ ```bash
381
+ tlgr call start @alice # --video, --check (rings nothing), --wait
382
+ tlgr call accept <call> # --ack-only marks it received (busy-lock)
383
+ tlgr call decline <call> # --reason missed|busy, --reply "can't talk"
384
+ tlgr call end <call> # --reason, --duration
385
+ tlgr call log list # the Calls tab; --missed, --with @alice
386
+ tlgr call watch # who is ringing, as NDJSON, for a notifier
387
+ ```
388
+
389
+ ```bash
390
+ tlgr vc create <chat> --yes # video chat, livestream or --rtmp
391
+ tlgr vc get <chat> # state, recording, limits, stream channels
392
+ tlgr vc participant list <chat> # --raised-hands, --video
393
+ tlgr vc mute <chat> [@alice] # --for-me; `vc unmute` = "allow to speak"
394
+ tlgr vc link <chat> --speaker # invite links; `vc rtmp get` for the key
395
+ tlgr vc watch <chat> # the only way to read the in-call chat
396
+ tlgr vc download <chat> --out live.ogg # record a livestream (it cannot play one)
397
+ ```
398
+
399
+ ```bash
400
+ tlgr conference create # a t.me/call/<slug> link, no crypto needed
401
+ tlgr conference invite <call> @alice # rings them; falls back to the link
402
+ tlgr conference decline <msg_id> # refuse, or cancel an invite you sent
403
+ ```
404
+
405
+ Conferences are end-to-end encrypted. Reading, ringing and revoking are
406
+ complete; **joining**, **removing somebody** and **sending inside** need a
407
+ signed `e2e.chain` block that tlgr cannot build — pass one with `--block` and
408
+ `--public-key`, or the command exits 2 naming what is missing.
409
+
410
+ ### Bots, inline mode, mini apps and payments
411
+
412
+ ```bash
413
+ tlgr bot get <bot> # the profile card: commands, menu, flags
414
+ tlgr bot list --owned|--similar-to|--popular-apps|--recent
415
+ tlgr bot start <bot> --param <payload> # /start with a hidden deep-link payload
416
+ tlgr bot command send <bot> start # '@botusername' added in a group
417
+ tlgr bot press <chat> <msg_id> --button 0
418
+ tlgr bot url-auth get|accept|decline # Telegram Login, inspected before granted
419
+ tlgr bot menu|permission|access|preview|affiliate|verification|token …
420
+
421
+ tlgr inline query <bot> <query> # @bot query, the bot's own paging
422
+ tlgr inline send <bot> <q> --chat <c> --pick 0
423
+ tlgr inline search gif|venue|image # the built-in bots, named by the server
424
+
425
+ tlgr webapp get|open|send|invoke|watch # mini apps; `open` prints the URL only
426
+ tlgr payment form get|receipt get|info get|card get
427
+ tlgr payment invoice export|send # asking someone else to pay
428
+ tlgr payment subscription list|set # cancel or resume a Star subscription
429
+ ```
430
+
431
+ Three things this group does *not* do, on purpose:
432
+
433
+ - **It never pays.** `sendPaymentForm`, `sendStarsForm`, `validateRequestedInfo`
434
+ and `fulfillStarsSubscription` are absent from the surface, not hidden behind
435
+ a flag. `payment form get` reports `payable_here: false` and says why.
436
+ - **It never opens a browser.** `webapp open` prints the signed mini-app URL,
437
+ which carries your init data and is a credential, not a link.
438
+ - **It never presses a button that discloses something without being told to.**
439
+ A phone number, a location, a chat or a poll each needs its own flag;
440
+ without one tlgr prints what it would send and exits 2.
441
+
442
+ ### Profile, privacy and notifications
443
+
444
+ ```bash
445
+ tlgr profile get # --no-full skips the userFull round trip
446
+ tlgr profile update # --first-name, --last-name, --bio, --birthday, --channel
447
+ tlgr profile photo set avatar.jpg # --video, --emoji ID, --photo-id ID, --fallback
448
+ tlgr profile username set ada # --check, --on/--off, --order a,b
449
+ tlgr profile status set 5301 # --until +7d, --clear
450
+ tlgr profile color set 5 # --profile, collectible:<slug>
451
+ tlgr profile presence set online # tlgr reports neither unless asked
452
+ tlgr profile link --qr # --collectible for the Fragment record
453
+
454
+ tlgr privacy get [key] # omit for every key
455
+ tlgr privacy set last-seen contacts --add-disallow @nosy
456
+ tlgr privacy global set --hide-read-marks on
457
+ tlgr privacy blocked list|set @spammer # --unblock, --stories
458
+
459
+ tlgr notify get private # or groups|channels|stories|reactions|<chat>
460
+ tlgr notify set private --mute 2h # --unmute, --sound ringtone:ID, --preview off
461
+ tlgr notify exception list|clear
462
+ tlgr notify ringtone list|set chime.ogg
463
+ ```
464
+
465
+ ### Settings, business, Premium, Stars and gifts
466
+
467
+ ```bash
468
+ tlgr settings get # every cloud key, each with what its setter accepts
469
+ tlgr settings set auto-delete 1w # sensitive-content, top-peers, browser, language…
470
+ tlgr settings theme list|install Nord
471
+ tlgr settings language list
472
+
473
+ tlgr business get # hours, location, intro, greeting, away, links, bots
474
+ tlgr business set --tz Europe/London --open 'mon-fri 09:00-18:00'
475
+ tlgr business reply add hello --text "Hi! I will reply shortly."
476
+ tlgr business bot set @mybot --reply-to --read --new-chats # no --all, by design
477
+
478
+ tlgr premium status | premium feature list --limits
479
+ tlgr stars balance get | stars transaction list --out
480
+ tlgr gift list | gift get msg:120 | gift set msg:120 --pin
481
+ tlgr giveaway get @channel 42 | giveaway code apply <slug>
482
+ ```
483
+
484
+ tlgr never spends money: the commands that would need a payment form signed
485
+ report the price and stop.
486
+
487
+ ### Accounts
488
+
489
+ ```bash
490
+ tlgr account add <phone> # starts the login; prints the verify-code line to run next
491
+ tlgr account add --bot --token-env TLGR_BOT_TOKEN --alias helper
492
+ tlgr account import <file.session> # import an existing Telethon session (no re-auth); --alias
493
+ tlgr account export <alias> --out ./work.string
494
+ tlgr account list # (* = default)
495
+ tlgr account switch <alias>
496
+ tlgr account logout <alias> # revokes the authorization on the server
497
+ tlgr account remove <alias> # local only, unless --logout
498
+ tlgr account rename <old> <new>
499
+ tlgr account info [alias]
500
+ tlgr account check [alias] # authorized / revoked / banned / frozen / offline
501
+ tlgr account sync [alias] # refresh stored profile from live Telegram
502
+ ```
503
+
504
+ `info`, `check` and `sync` take the alias positionally, but also honor the
505
+ global `-a/--account` flag; with neither, they fall back to the active
506
+ account. Session files are stored `0600` — they are full account credentials,
507
+ and so is anything `account export` produces, which is why it writes to a
508
+ `0600` file unless `--stdout` says you meant to print it.
509
+
510
+ **`logout` is not `remove`.** `logout` revokes the authorization server-side
511
+ (v1 never did, so a removed account went on showing in every other client's
512
+ Devices list) and keeps the alias so you can log back in; `remove` deletes the
513
+ local record and says, in its answer, that the server-side session is still
514
+ alive unless you passed `--logout`.
515
+
516
+ ### Logging in
517
+
518
+ ```bash
519
+ tlgr auth send-code <phone> --alias work --api-id 12345 --api-hash-env TLGR_API_HASH
520
+ tlgr auth verify-code <code> --alias work --password-env TLGR_2FA_PASSWORD
521
+ tlgr auth qr --alias work # streams tg://login tokens until one is approved
522
+ tlgr auth recover # forgot the cloud password (recovery email)
523
+ tlgr auth code list # the login code Telegram sent this account (chat 777000)
524
+ tlgr auth tos # read the Terms of Service; --accept is a separate run
525
+ ```
526
+
527
+ The pending login lives in the daemon — which owns the session file, so two
528
+ processes never open it at once — and is mirrored to
529
+ `<account>/login-state.json` at `0600`, which is what lets the two steps run
530
+ minutes apart. `auth verify-code` exits 4 when the account has a cloud
531
+ password and none was supplied: add `--password-env` and re-run the same line.
532
+
533
+ ### Sessions, passwords and connected websites
534
+
535
+ ```bash
536
+ tlgr account session list [--unconfirmed] [--pending-password]
537
+ tlgr account session terminate <hash>... | --all-others [--deny]
538
+ tlgr account session confirm <hash> # "yes, it's me"
539
+ tlgr account session set <hash> --calls off # --secret-chats, --auto-terminate 6m
540
+ tlgr account session accept-qr 'tg://login?token=…' # approve another device
541
+
542
+ tlgr account password get [--verify] # 2FA status; --password-env to reveal the recovery address
543
+ tlgr account password set --new-password-env TLGR_2FA_NEW_PASSWORD --hint "…"
544
+ tlgr account password change --password-env … --new-password-env …
545
+ tlgr account password remove --password-env … --yes
546
+
547
+ tlgr account website list # a *different* list from Devices
548
+ tlgr account website revoke <hash>... [--block-bot]
549
+ tlgr account passkey list # auditable; a CLI can never create one
550
+ tlgr account ttl set 12m # delete my account if I am away this long
551
+ ```
552
+
553
+ `account session list` derives two fields you cannot get by hand:
554
+ `deny_deadline` (after it, Telegram auto-confirms an unrecognised login for
555
+ you) and `sensitive_actions_eligible_at` (what `SESSION_TOO_FRESH_X` counts
556
+ down to). `password change` refuses when the account stores Telegram Passport
557
+ documents unless `--keep-passport` acknowledges that they will be lost — the
558
+ secure secret is encrypted under the password and tlgr does not implement the
559
+ KDF that would re-encrypt it.
560
+
561
+ ### Daemon
562
+
563
+ ```bash
564
+ tlgr daemon start # --foreground, --catch-up/--no-catch-up
565
+ tlgr daemon stop # drains in-flight work rather than killing it
566
+ tlgr daemon status # running/ready/healthy, per-account state and lag
567
+ tlgr daemon restart # --grace 10s
568
+ tlgr daemon reconnect # force a reconnect and a catch-up
569
+ tlgr daemon save-state # flush pts/qts and the entity cache now
570
+ tlgr daemon logs --follow --level warning
571
+ tlgr daemon flood list # rate-limit deadlines this install still owes
572
+ tlgr daemon dead-letter list # events no consumer could be given
573
+ tlgr daemon install # LaunchAgent on macOS, systemd --user on Linux
574
+ ```
575
+
576
+ ### Watching events
577
+
578
+ ```bash
579
+ tlgr watch # v1's default: new messages
580
+ tlgr watch --events all --account all # everything, every connected account
581
+ tlgr watch --events read,message_reactions --chat @alice
582
+ tlgr watch --since 91820 --print-cursor
583
+ tlgr events list --group message # the vocabulary --events accepts
584
+ tlgr events get message_new # payload, source constructors, sequence box
585
+ ```
586
+
587
+ Push-driven from the daemon's event bus, not polled: the daemon already holds
588
+ the update socket, so a watcher is a bounded queue on it. 114 event types
589
+ cover every `Update*` constructor the pinned Telethon can parse — a message
590
+ edit, a deletion, a read receipt, a reaction, a typing indicator and every
591
+ service message included. v1's `watch` polled `chat list` and `message list`
592
+ every two seconds and could only report new messages.
593
+
594
+ Frames are NDJSON, one per line: exactly one `meta` first, exactly one `end`
595
+ last, and events, `heartbeat`, `gap` and `lag` in between. A `gap` frame says
596
+ how many events the replay window lost — a number rather than silence.
597
+ `--results-only` prints v1's `{event_type, chat_id, data}` line shape.
598
+
599
+ ### Update state
600
+
601
+ ```bash
602
+ tlgr sync status --channels # pts/qts/seq, per-channel table, lag
603
+ tlgr sync catch-up # replay what was missed while offline
604
+ tlgr sync difference --chat @news # run getDifference by hand (read-only)
605
+ tlgr sync reset # give up on the gap and re-baseline
606
+ tlgr sync backfill @news --from-id 91800 --to-id 91900
607
+ ```
608
+
609
+ `catch-up` replays a gap; `reset` gives up on one. Neither is `chat catchup`,
610
+ which is the unread digest a human reads.
611
+
612
+ ### Network and proxies
613
+
614
+ ```bash
615
+ tlgr net status # DC, transport, proxy, latency, clock offset
616
+ tlgr net ping --probes 5
617
+ tlgr net dc list --ipv6
618
+ tlgr proxy add 'tg://proxy?server=1.2.3.4&port=443&secret=dd00' --set
619
+ tlgr proxy test --every --reorder
620
+ ```
621
+
622
+ SOCKS5, HTTP and MTProxy; `tg://proxy` and `t.me/proxy` links both parsed.
623
+ Credentials live in `~/.tlgr/proxies.json` at mode 0600 and never reach argv
624
+ or a listing. `proxy test` probes through a throwaway in-memory session, so a
625
+ probe can never become the account's update-receiving connection.
626
+
627
+ #### Protocol v2
628
+
629
+ The CLI reaches the daemon over `~/.tlgr/daemon.sock`, created `srw-------`
630
+ with the connecting peer's uid checked on every request — v1 left it
631
+ world-writable with no authentication at all.
632
+
633
+ - **The account is always explicit.** The CLI resolves it (`-a` →
634
+ `TLGR_ACCOUNT` → `[accounts] default` → active alias) and sends it. The
635
+ daemon never picks one: with two accounts configured, v1 used whichever
636
+ alias came first out of a set, so you could send from the wrong identity
637
+ with no signal.
638
+ - **Handshake, and one restart.** A client newer than the running daemon
639
+ restarts it exactly once and says so on stderr; `--no-daemon-restart`
640
+ refuses instead (exit 11). Two `tlgr` commands racing with no daemon
641
+ running produce exactly one daemon.
642
+ - **`status` distinguishes alive from working.** `running` is a live process;
643
+ `ready` is a daemon that can serve; `healthy` is one whose accounts are
644
+ actually working. An account whose connection dropped is `degraded` and its
645
+ requests answer exit 8 with a hint instead of `Cannot send requests while
646
+ disconnected`; a revoked session is `needs_login` and answers exit 4.
647
+ - **A live home is protected.** A tlgr home with a `.production` marker file
648
+ is refused unless `TLGR_ALLOW_PRODUCTION_HOME=1`: two processes sharing one
649
+ home share session files, and Telegram revokes an auth key it sees two
650
+ clients on.
651
+
652
+ ### Global Flags
653
+
654
+ ```
655
+ --json JSON to stdout (for scripting and agents)
656
+ --plain Stable TSV for piping
657
+ -a, --account TEXT Account alias to use
658
+ --results-only In JSON mode, strip envelope and emit only the primary result
659
+ --select FIELDS In JSON mode, project comma-separated fields (supports dot paths)
660
+ --enable-commands Comma-separated allowlist of enabled commands (sandboxing)
661
+ -n, --dry-run Preview destructive operations without executing
662
+ -y, --force Skip confirmations
663
+ --no-input Never prompt; fail instead (CI/agent mode)
664
+ -v, --verbose Verbose logging to stderr
665
+ --version / --help
666
+ ```
667
+
668
+ ### Environment Variables
669
+
670
+ All global flags can be set via environment variables:
671
+
672
+ | Variable | Equivalent |
673
+ |----------|------------|
674
+ | `TLGR_JSON=1` | `--json` |
675
+ | `TLGR_PLAIN=1` | `--plain` |
676
+ | `TLGR_ACCOUNT=alias` | `--account alias` |
677
+ | `TLGR_ENABLE_COMMANDS=cmd1,cmd2` | `--enable-commands cmd1,cmd2` |
678
+ | `TLGR_AUTO_JSON=1` | Auto-switch to JSON when stdout is piped (non-TTY) |
679
+
680
+ ## Webhook -- Event Push
681
+
682
+ tlgr pushes Telegram events to an external HTTP endpoint in real time. Designed for agentic interfaces like [OpenClaw](https://github.com/openclaw) where an agent receives events and calls `tlgr` CLI commands to act.
683
+
684
+ ```mermaid
685
+ flowchart LR
686
+ TG["Telegram"] --> DAEMON["tlgr daemon"]
687
+ DAEMON -->|"POST /hooks/agent"| AGENT["Your Agent"]
688
+ AGENT -->|"tlgr --json message send ..."| CLI["tlgr CLI"]
689
+ CLI --> TG
690
+ ```
691
+
692
+ Configure it with `tlgr webhook set`, which validates every event name against
693
+ the taxonomy — a name nobody recognises used to be dropped silently, so the
694
+ webhook delivered nothing and never said why:
695
+
696
+ ```bash
697
+ tlgr webhook set --url https://example.com/hooks/agent \
698
+ --events message_new,message_edited,message_deleted \
699
+ --secret-env TLGR_WEBHOOK_SECRET --enabled
700
+ tlgr webhook get # configuration and delivery health; secrets redacted
701
+ tlgr webhook test # one delivery, with the exact headers it sent
702
+ ```
703
+
704
+ Or edit `~/.tlgr/webhook.toml` directly. Deliveries carry:
705
+
706
+ | Header | Meaning |
707
+ |---|---|
708
+ | `X-Tlgr-Signature` | `sha256=<hmac of the exact body>` — verify over the bytes you received |
709
+ | `X-Tlgr-Delivery` | unique per attempt; reused on a re-drive, so it is an idempotency key |
710
+ | `X-Tlgr-Seq` | the event's per-account sequence number |
711
+ | `X-Tlgr-Event` | the event type |
712
+ | `X-Tlgr-Account` | the account alias |
713
+
714
+ Events arrive as `{"event": <envelope>, "delivery_id": "..."}`:
715
+
716
+ ```json
717
+ {
718
+ "event": {
719
+ "seq": 91824,
720
+ "ts": "2026-09-03T09:14:07Z",
721
+ "account": "main",
722
+ "type": "message_new",
723
+ "payload": { "...": "..." },
724
+ "chat_id": -1001234567890
725
+ },
726
+ "delivery_id": "0f3c…"
727
+ }
728
+ ```
729
+
730
+ A delivery that fails every attempt is dead-lettered rather than dropped;
731
+ `tlgr daemon dead-letter list` shows them and `daemon dead-letter send`
732
+ re-drives them.
733
+
734
+ ## Gateway -- Background Jobs
735
+
736
+ tlgr also ships with a deterministic, always-on Gateway that runs background jobs on your Telegram account. Define declarative pipelines in `~/.tlgr/jobs.yaml` that automatically react to incoming messages -- auto-reply, auto-forward, filter by chat type, time of day, content, and more.
737
+
738
+ ```mermaid
739
+ flowchart LR
740
+ TG["Telegram event"] --> F["Filters"]
741
+ F -->|"passed"| P["Processors"]
742
+ P --> A["Actions"]
743
+ A --> R["reply / forward / ..."]
744
+ ```
745
+
746
+ A few examples of what you can do:
747
+
748
+ ```yaml
749
+ jobs:
750
+ # Auto-reply to all private messages
751
+ - name: private-bot
752
+ account: main
753
+ filters:
754
+ chat_type: private
755
+ actions:
756
+ - reply: "shut up i'm just a bot!"
757
+
758
+ # Forward breaking news to your archive
759
+ - name: news-forward
760
+ account: main
761
+ filters:
762
+ chat_id: "@raw_feed"
763
+ types: [text, photo]
764
+ contains: [breaking]
765
+ actions:
766
+ - forward:
767
+ to: ["@clean_feed", "@archive"]
768
+ processors: [strip_formatting]
769
+
770
+ # Night-mode auto-reply
771
+ - name: night-mode
772
+ account: main
773
+ filters:
774
+ chat_type: private
775
+ time_of_day: "23:00-07:00"
776
+ actions:
777
+ - reply: "I'm sleeping. Will reply tomorrow."
778
+ ```
779
+
780
+ Filters support full AND / OR / NOT composition, 20+ built-in filter types, 7 text processors, and a registry pattern for adding your own.
781
+
782
+ For the full Gateway reference -- filters, processors, actions, composition, extensibility -- see **[Gateway documentation](tlgr/gateway/README.md)**.
783
+
784
+ ## Agent / Automation
785
+
786
+ tlgr is designed to be consumed by LLM agents and automation pipelines.
787
+
788
+ ### Machine-readable schema
789
+
790
+ ```bash
791
+ tlgr schema # full CLI schema as JSON
792
+ tlgr schema message send # schema for a specific command
793
+ ```
794
+
795
+ Agents can discover all commands, flags, positionals, types, and defaults
796
+ without parsing `--help`. For commands generated from the operation registry
797
+ the schema also carries JSON Schema (draft 2020-12) for the request *and* the
798
+ response, plus a generated example — so a call can be validated before it is
799
+ made. `tlgr agent whoami --json` reports `output_schema_version: 2`.
800
+
801
+ ### Feature parity
802
+
803
+ ```bash
804
+ tlgr agent parity # coverage of the pinned Telegram feature catalog
805
+ tlgr agent parity --json --uncovered # every gap, by priority and domain
806
+ ```
807
+
808
+ The answer to "can tlgr do X yet" without guessing. 2.0.0 covers 1788 of
809
+ 1797 catalogued behaviours and all 178 P0 ones; the nine that remain are
810
+ individually waived and each names the MTProto method this build has no
811
+ request class for. Nothing in the report is hand-maintained — it is computed
812
+ from the registry — and the same report is generated into
813
+ [`docs/reference/PARITY.md`](docs/reference/PARITY.md).
814
+
815
+ ### JSON envelope
816
+
817
+ Every command wraps its answer — there is no hand-written command left to
818
+ answer any other way:
819
+
820
+ ```json
821
+ {"ok": true, "op": "message.send", "result": {...}, "meta": {"request_id": "...", "elapsed_ms": 42}}
822
+ {"ok": false, "error": {"error": "...", "code": "RATE_LIMITED", "exit_code": 7, "wait_seconds": 30}}
823
+ ```
824
+
825
+ `--results-only` prints the inner value in either case, which is v1's shape;
826
+ `--select` projects fields by dot path. Paginated operations return
827
+ `{items, has_more, next_cursor, total}` with an opaque signed cursor.
828
+
829
+ ### Stable exit codes
830
+
831
+ ```bash
832
+ tlgr exit-codes # print the exit code table
833
+ tlgr --json agent exit-codes # as JSON
834
+ ```
835
+
836
+ | Code | Meaning |
837
+ |------|---------|
838
+ | 0 | Success |
839
+ | 1 | Generic failure |
840
+ | 2 | Usage / parse error |
841
+ | 3 | Empty results |
842
+ | 4 | Auth required |
843
+ | 5 | Not found |
844
+ | 6 | Permission denied |
845
+ | 7 | Rate limited |
846
+ | 8 | Retryable error |
847
+ | 10 | Config error |
848
+ | 11 | Daemon error |
849
+ | 12 | IPC error |
850
+ | 13 | Indeterminate (unknown — not a negative) |
851
+ | 130 | Interrupted (SIGINT) |
852
+
853
+ ### Command sandboxing
854
+
855
+ Restrict which commands an agent can run:
856
+
857
+ ```bash
858
+ tlgr --enable-commands="message,chat,schema" send @user "hi" # allowed
859
+ tlgr --enable-commands="message,chat,schema" account remove foo # blocked (exit 2)
860
+ ```
861
+
862
+ Or via environment: `TLGR_ENABLE_COMMANDS=message,chat,schema`.
863
+
864
+ For generated commands the allowlist is matched by canonical operation id and
865
+ enforced **inside the daemon**, so an alias cannot be used to get past it
866
+ (`--enable-commands message.list` permits `tlgr msg list`), and a blocked
867
+ operation exits 6. It is a usability guard, not a sandbox: anything that can
868
+ reach the socket can reach the session. See [SECURITY.md](SECURITY.md).
869
+
870
+ ### JSON transforms
871
+
872
+ ```bash
873
+ tlgr --json --results-only chat list # strip pagination/envelope, emit only the chat array
874
+ tlgr --json --select "chat.id,unread_count" chat list # project specific fields
875
+ ```
876
+
877
+ ### Auto-JSON for pipelines
878
+
879
+ Set `TLGR_AUTO_JSON=1` and tlgr automatically outputs JSON whenever stdout is piped (non-TTY), without requiring `--json`.
880
+
881
+ ### Error hints
882
+
883
+ Errors include actionable recovery hints:
884
+
885
+ ```
886
+ Error: No session found for account 'main'
887
+ Session expired. Run: tlgr account add <phone>
888
+ ```
889
+
890
+ ## Configuration
891
+
892
+ Config files live in `~/.tlgr/`:
893
+
894
+ | File | Format | Purpose |
895
+ |------|--------|---------|
896
+ | `config.toml` | TOML | App defaults, daemon, accounts |
897
+ | `jobs.yaml` | YAML | Gateway job definitions |
898
+ | `webhook.toml` | TOML | Outbound webhook push |
899
+
900
+ ```bash
901
+ tlgr config init # create defaults
902
+ tlgr config validate # check syntax + validate filter/action names
903
+ tlgr config path # print config directory
904
+ tlgr config keys # list all known config keys
905
+ tlgr config list # show current values
906
+ tlgr config get <key> # get a single value
907
+ tlgr config set <key> <value> # set a value
908
+ tlgr config unset <key> # reset to default
909
+ ```
910
+
911
+ ### config.toml
912
+
913
+ ```toml
914
+ [defaults]
915
+ output = "human"
916
+
917
+ [accounts]
918
+ default = "main"
919
+
920
+ [daemon]
921
+ auto_start = true
922
+ log_level = "info"
923
+ ```
924
+
925
+ ## Multi-Account
926
+
927
+ ```bash
928
+ tlgr account add +15551234567 --alias personal # then: tlgr auth verify-code <code> --alias personal
929
+ tlgr account add +15559876543 --alias work
930
+ tlgr -a personal message send @friend "Hi"
931
+ ```
932
+
933
+ There is no cap on accounts (official apps stop at three). `tlgr auth code
934
+ list -a personal` reads the login code Telegram delivered to *that* account's
935
+ service chat, which is how a second account can be onboarded without anyone
936
+ reading a phone.
937
+
938
+ To eliminate wrong-account mistakes in scripts and agents, enable strict
939
+ account selection — every command must then carry an explicit `-a <alias>`
940
+ (no default-account fallback; violations exit 2):
941
+
942
+ ```bash
943
+ tlgr config set require_account true # or per-invocation: TLGR_REQUIRE_ACCOUNT=1
944
+ ```
945
+
946
+ Jobs can reference different accounts:
947
+
948
+ ```yaml
949
+ jobs:
950
+ - name: work-forward
951
+ account: work
952
+ # ...
953
+ ```
954
+
955
+ ## License
956
+
957
+ See [LICENSE](LICENSE) for license details.