| name | tgcli |
|---|---|
| description | Use when user wants to read/search/send/analyze Telegram messages via tgcli CLI. Trigger on requests about channel/chat history, digests/news, mentions, files, topics, contacts, groups, tags, media downloads, multi-account profiles, and archive/sync status. Also covers group admin (join requests, rename, members, invite links, join/leave). For edit/delete/reactions, use telegram-mcp instead. |
Telegram CLI skill for AI agents.
Install this skill from GitHub:
npx skills add dapi/tgcli --skill tgcli --agent '*' -g -yInstall CLI:
npm install -g @dapi/tgcliAuthenticate once:
tgcli auth| Use tgcli for | Use telegram-mcp for |
|---|---|
| Read/search/archive messages | edit/delete/forward |
| Inspect/click callback buttons | reactions |
| Send text/photo/files and topic posts | advanced interactive actions |
| Forum topics listing/search | |
| Download media from messages | |
| Group admin (join requests, rename, members, invite, join/leave) | ban/kick/promote with granular permissions |
| Channel/contact tags and metadata | |
| Sync jobs and archive monitoring | |
| JSON output for automation |
- If
tgcliis not found, install it:npm install -g @dapi/tgclithen runtgcli authfor first-time login. tgcli authonly authenticates the Telegram session. Usetgcli sync --onceortgcli sync --followto seed/archive dialogs.- Always add
--jsonfor agent workflows. - Add
--timeout 30sby default; use--timeout 90sfor heavy live reads. - Prefer explicit
--source archive|live|bothinstead of relying on defaults. --source archivereads only the local archive, including when no result is found. Use--source liveto query Telegram or--source bothto combine them.- With
tgcli serverortgcli sync --followrunning, archive message reads, cached tags, andsync jobs listuse read-only SQLite. Live reads and commands that change the store use the owner's private local socket even if MCP is disabled. Without an owner, the CLI claims the store itself for those commands. A live owner whose socket is unavailable must be fixed or stopped before retrying an owner-dependent command; the CLI does not open a second session. - When Telegram rate-limits the server's startup dialog refresh, the server remains available but pauses pending sync work.
tgcli sync status --jsonshowsdialogRefreshDeferred: true; retry the refresh later withtgcli sync --oncethrough the same owner. Do not repeatedly retry duringFLOOD_WAIT. - If a live command reports
UNKNOWN_RESULT, do not retry a send or other non-idempotent change blindly. Use the returnedrequestIdwithtgcli owner request <requestId> --jsonwhile the same owner is running; inspect the chat or archive before retrying if the request is still running or its outcome is unknown. --timeoutcancels the request's Telegram RPCs and uploads on the owner, freeing its live slot. It cannot retract a mutation already transmitted to Telegram: its outcome may remainunknownwith codeUNKNOWN_RESULT. Use a longer explicit timeout for large files. Background sync jobs continue independently of the request that queued them.- Never use
--since; tgcli uses--afterand--beforewith ISO timestamps. - Never use
tgcli sync --chat ...; top-levelsynconly runs workers via--once/--follow. - To sync a specific chat:
tgcli channels sync --chat <id|@username> --enableand/ortgcli sync jobs add --chat <id|@username>, then runtgcli sync --onceortgcli sync --follow. - If command shape is uncertain, verify it first with
tgcli <command> --helpinstead of guessing flags. - Select named accounts with the global
--account <id|alias|phone>option before the command, or withTGCLI_ACCOUNT. Selection precedence is--account→TGCLI_ACCOUNT→default. - The
defaultprofile keeps the legacy store and existing authorization. Never runauth logout, move a session, or copy session files merely because a named profile is not visible. - Named profiles have physically isolated config, session, messages database, locks, downloads, sync jobs, and service state. Always use the same account selector for auth, reads, writes, sync, and service commands belonging to that profile.
- If account commands unexpectedly show no profiles or report missing configuration, inspect
TGCLI_STORE: an inherited temporary/test store can hide the production profile registry. Resolve the intended base store before considering any auth change. - Global proxy fallback lives in
~/.tgclircasproxy=<url>and applies to every account profile. Effective precedence isTELEGRAM_PROXY→ selected profileconfig.json→~/.tgclirc. - For sending format control:
--parse-mode markdown|html|none(case-insensitive)- for
send photoandsend file,--parse-moderequires--caption --reply-to <messageId>replies to a specific message; if both--reply-toand--topicare passed,--reply-towins--silentsends without notification sound--no-forwardsprotects message from forwarding/saving--schedule <iso>schedules message for future delivery (ISO 8601, must be in the future, within 365 days)--caption-aboveshows caption above media (send photo/send file, requires--caption)--spoilerblurs media until tapped (send photo/send file)--retries <n>retries transient network/transport failures forsend photo--retry-backoff <ms|constant|linear|exponential>controls retry delay forsend photo--force-documentsends photo/video as uncompressed document (send fileonly)--retries <n>retry on failure with exponential backoff (default 0); JSON output includesretry_logandattemptswhen retries occurred
- Telegram markdown formatting (when
--parse-mode markdown):- Bold:
**text** - Italic:
__text__(double underscores, NOT single_) - Bold+Italic:
**__text__**(bold wraps italic) - Single
_text_does NOT work for italic — renders as literal underscores - Single
*text*does NOT work for bold — renders as literal asterisks - Monospace inline:
`text` - Code block: triple backticks
- Telegram markdown differs from standard Markdown — always use double markers
- Bold:
- Never delete lock files (
LOCK,database is locked): wait and retry. - For multiline text or arbitrary user-authored text, use
scripts/send_text.pyand pass the body through stdin. It invokestgcliwithout a shell, preserving real line breaks and punctuation exactly. - Never construct a shell command with
JSON.stringify(message)and never put literal\\nsequences in--message: Telegram will display the backslash and the letterninstead of a line break. - After sending multiline text, read the message back and verify that
textcontains real newline characters, not literal\\nsequences.
# Existing commands without a selector continue to use the default profile.
tgcli auth status --json --timeout 30s
# Register an isolated profile without contacting Telegram.
tgcli accounts add work --phone "+7 707 111 22 33" --alias office
tgcli accounts list --json
# Authenticate and use exactly one named profile.
tgcli --account work auth
tgcli --account office auth status --json --timeout 30s
tgcli --account +770****2233 messages search "invoice" --source live --json --timeout 30s
# Environment selection is useful for a dedicated service/process.
TGCLI_ACCOUNT=work tgcli sync --followAccount selectors resolve by stable profile ID, normalized phone number, or alias. If selection is ambiguous or the stored Telegram user ID does not match the session, stop and inspect the profile; never fall back to another account.
For login codes delivered in-app, read Telegram's official service chat 777000
through the same already-authorized target profile. Codes may be localized, for
example Код для входа в Telegram: or Login code:. Filter by the authorization
request timestamp and pass the code over stdin rather than putting it in command
arguments or logs.
tgcli messages list --chat <id|@username> --limit 50 --source archive --json --timeout 30s
tgcli messages list --chat <id|@username> --chat <id2> --limit 50 --source archive --json --timeout 30s
tgcli messages list --chat <id|@username> --topic <topicId> --after 2025-01-01T00:00:00Z --limit 100 --source archive --json --timeout 30s
tgcli messages show --chat <id|@username> --id <msgId> --source archive --json --timeout 30s
tgcli messages context --chat <id|@username> --id <msgId> --before 5 --after 5 --source archive --json --timeout 30s
tgcli messages list --chat <id|@username> --limit 50 --source live --json --timeout 90s
tgcli messages list --chat <id|@username> --limit 50 --source both --json --timeout 90s
tgcli messages buttons list --chat <id|@username> --id <msgId> --json --timeout 30s
tgcli messages buttons click --chat <id|@username> --id <msgId> --row 1 --column 1 --json --timeout 30smessages buttons list always reads the live message and returns 1-based
coordinates plus safe button metadata; it never prints callback payload bytes.
Use those exact coordinates with messages buttons click. The click command
supports callback buttons only and fails closed for URL/Web App or
password-protected buttons.
Never use --since here; the correct flag is --after.
tgcli messages search "Claude Code" --chat <id|@username> --source archive --json --timeout 30s
tgcli messages search --query "Claude Code" --chat <id|@username> --source archive --json --timeout 30s
tgcli messages search --regex "claude\\s+(code|agent)" --chat <id|@username> --source archive --json --timeout 30s
tgcli messages search --tag ai --chat <id|@username> --source archive --json --timeout 30s
tgcli messages search --tags "ai,dev" --chat <id|@username> --source archive --json --timeout 30s
tgcli messages search "release" --chat <id|@username> --after 2025-06-01T00:00:00Z --before 2025-06-30T00:00:00Z --source archive --json --timeout 30s
tgcli messages search "Release" --case-sensitive --chat <id|@username> --source archive --json --timeout 30sBoth positional query and --query flag work. --chat accepts multiple values. Use --regex for pattern matching, --tag/--tags to filter by channel tags, --after/--before for date range, --case-sensitive to disable case-insensitive search.
⚠ send uses --to (alias --chat) for the destination; then --message (alias --text) for text body, --photo for photo uploads, and --file for generic files.
For multiline text, use the stdin helper with a single-quoted heredoc delimiter:
python3 ~/.agents/skills/tgcli/scripts/send_text.py --to <id|@username> <<'TGCLI_MESSAGE'
Первая строка.
1. Первый пункт.
2. Второй пункт.
TGCLI_MESSAGEThe quoted delimiter prevents shell expansion, while stdin preserves actual line
breaks. Direct tgcli send text --message "..." remains suitable for short,
static, single-line messages.
tgcli send text --to <id|@username> --message "Hello" --json --timeout 30s
tgcli send text --to <id|@username> --topic <topicId> --message "**Hello**" --parse-mode markdown --json --timeout 30s
tgcli send text --to <id|@username> --message "Done" --reply-to <messageId> --json --timeout 30s
tgcli send text --to <id|@username> --message "https://example.com check this" --no-preview --json --timeout 30s
tgcli send text --to <id|@username> --message "Nightly report" --silent --json --timeout 30s
tgcli send text --to <id|@username> --message "Confidential" --no-forwards --json --timeout 30s
tgcli send text --to <id|@username> --message "Good morning!" --schedule "2025-01-15T09:00:00+03:00" --json --timeout 30s
tgcli send text --to <id|@username> --message "Hello" --retries 3 --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --caption "Report" --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --caption "**Report**" --parse-mode markdown --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --reply-to <messageId> --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --caption "Breaking news" --caption-above --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --spoiler --json --timeout 30s
tgcli send photo --to <id|@username> --photo /path/to/image.png --retries 3 --retry-backoff exponential --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/file --caption "Report" --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/file --caption "<b>Report</b>" --parse-mode html --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/file --filename custom-name.pdf --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/file --reply-to <messageId> --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/file --caption "Breaking news" --caption-above --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/photo.jpg --spoiler --json --timeout 30s
tgcli send file --to <id|@username> --file /path/to/photo.jpg --force-document --json --timeout 30s- Use
tgcli send photofor local PNG/JPG when Telegram should render a photo preview. - Use
tgcli send filefor generic uploads and explicit document-style attachments. - For draft/approval flow, send to Saved Messages first, review in Telegram, then resend to the target chat.
tgcli media download --chat <id|@username> --id <msgId> --json --timeout 30s
tgcli media download --chat <id|@username> --id <msgId> --output /path/to/save --json --timeout 30stgcli channels list --query "ai" --limit 20 --json --timeout 30s
tgcli channels show --chat <id|@username> --json --timeout 30s
tgcli channels sync --chat <id|@username> --enable --json --timeout 30s
tgcli channels sync --chat <id|@username> --disable --json --timeout 30stgcli topics list --chat <id|@username> --limit 50 --json --timeout 30s
tgcli topics search --chat <id|@username> --query "release" --limit 20 --json --timeout 30stgcli contacts search "alex" --limit 20 --json --timeout 30s
tgcli contacts show --user <id> --json --timeout 30s
tgcli contacts alias set --user <id> --alias "Alex" --json --timeout 30s
tgcli contacts alias rm --user <id> --json --timeout 30s
tgcli contacts tags add --user <id> --tag coworker --tag ai --json --timeout 30s
tgcli contacts tags rm --user <id> --tag ai --json --timeout 30s
tgcli contacts notes set --user <id> --notes "Met at meetup" --json --timeout 30stgcli groups list --query "dev" --limit 20 --json --timeout 30s
tgcli groups info --chat <id|@username> --json --timeout 30s
tgcli groups requests list --chat <id|@username> --limit 100 --json --timeout 30s
tgcli groups requests list --chat <id|@username> --query "alex" --json --timeout 30s
tgcli groups requests list --chat <id|@username> --link <invite-url> --json --timeout 30s
tgcli groups requests approve --chat <id|@username> --user <userId> --json --timeout 30s
tgcli groups requests decline --chat <id|@username> --user <userId> --json --timeout 30s
tgcli groups rename --chat <id|@username> --name "New Name" --json --timeout 30s
tgcli groups members add --chat <id|@username> --user <userId> --user <userId2> --json --timeout 30s
tgcli groups members remove --chat <id|@username> --user <userId> --json --timeout 30s
tgcli groups invite get --chat <id|@username> --json --timeout 30s
tgcli groups invite edit --chat <id|@username> --link <invite-url> --request-needed true --json --timeout 30s
tgcli groups invite edit --chat <id|@username> --link <invite-url> --request-needed false --json --timeout 30s
tgcli groups invite revoke --chat <id|@username> --json --timeout 30s
tgcli groups join --code <invite-code> --json --timeout 30s
tgcli groups leave --chat <id|@username> --json --timeout 30sJoin-request commands require group administrator permissions. Never combine
--query and --link. Approve or decline only the exact userId returned by
the latest groups requests list result; a request may already have been handled
by another administrator. groups invite edit changes the specified existing
link in place and never revokes it. Pass the exact link and verify that the JSON
response contains the same link plus the requested requestNeeded value.
groups invite get --json returns the primary link together with its effective
requestNeeded and isPrimary values for read-back verification.
tgcli tags list --chat <id|@username> --json --timeout 30s
tgcli tags set --chat <id|@username> --tag ai --tag dev --json --timeout 30s
tgcli tags search --tag ai --limit 20 --json --timeout 30s
tgcli tags auto --limit 50 --json --timeout 90s # AI-powered: generates tags from channel metadata/content
tgcli tags auto --chat <id|@username> --source manual --json --timeout 90s
tgcli tags auto --limit 50 --no-refresh-metadata --json --timeout 90stgcli metadata get --chat <id|@username> --json --timeout 30s
tgcli metadata refresh --chat <id|@username> --force --json --timeout 30s
tgcli metadata refresh --only-missing --limit 50 --json --timeout 90stgcli folders list --json --timeout 30s
tgcli folders show <name|id> --json --timeout 30s
tgcli folders show <name|id> --resolve --json --timeout 30s
tgcli folders create --title "Name" --emoji "🤖" --json --timeout 30s
tgcli folders edit <name|id> --title "New Name" --json --timeout 30s
tgcli folders delete <name|id> --json --timeout 30s
tgcli folders order <id1> <id2> <id3> --json --timeout 30s
tgcli folders add-chat <folder> --chat <id> --json --timeout 30s
tgcli folders remove-chat <folder> --chat <id> --json --timeout 30s
tgcli folders join --link "https://t.me/addlist/slug" --json --timeout 30sUse --resolve with folders show to resolve peer IDs to readable channel/user names (slower, requires API calls per peer). Without --resolve, peers are shown as typed IDs (e.g., channel:123).
tgcli sync status --json --timeout 30s
tgcli sync jobs list --json --timeout 30s
tgcli sync jobs list --status error --json --timeout 30s
tgcli sync jobs list --channel <id|@username> --json --timeout 30s
tgcli sync jobs add --chat <id|@username> --depth 500 --json --timeout 30s
tgcli sync jobs add --chat <id|@username> --min-date 2025-01-01T00:00:00Z --json --timeout 30s
tgcli sync jobs retry --all-errors --json --timeout 30s
tgcli sync jobs retry --job-id <id> --json --timeout 30s
tgcli sync jobs retry --channel <id|@username> --json --timeout 30s
tgcli sync jobs cancel --job-id <id> --json --timeout 30s
tgcli sync jobs cancel --channel <id|@username> --json --timeout 30sDo not write tgcli sync --chat ...; queue work with sync jobs add --chat ..., then process it with tgcli sync --once or tgcli sync --follow.
tgcli owner request <requestId> --json --timeout 30sThe active owner retains recent request outcomes in memory. completed means its handler finished successfully; failed includes an error code. running means it has not finished. unknown with code UNKNOWN_RESULT means cancellation left the result uncertain; unknown without a code means the active owner has no record (for example, after a restart). Neither form proves a send did not happen.
Shutdown cancels accepted owner requests and waits for cleanup. If cleanup remains stuck for 10 seconds, the owning process exits with an error; the OS then releases its store guard. Live MCP tools have a 30-second deadline and honor caller cancellation.
tgcli service status --json --timeout 30s
tgcli service install --json --timeout 30s
tgcli service start --json --timeout 30s
tgcli service stop --json --timeout 30s
tgcli service logs --json --timeout 30stgcli auth --json --timeout 30s
tgcli auth --qr --json --timeout 30s
tgcli auth status --json --timeout 30s
tgcli auth logout --json --timeout 30s
tgcli config list --json --timeout 30s
tgcli config get <key> --json --timeout 30s
tgcli config set <key> <value> --json --timeout 30s
tgcli config unset <key> --json --timeout 30s
# Global fallback shared by all profiles
# ~/.tgclirc: proxy=socks5://127.0.0.1:1080
# Route via SOCKS5 for only the selected profile (stored in config.json)
tgcli config set proxy socks5://127.0.0.1:1080 --json --timeout 30s
# Route via MTProxy (Telegram t.me link format also accepted)
tgcli config set proxy "https://t.me/proxy?server=HOST&port=PORT&secret=SECRET" --json --timeout 30s
# Or via env variable (overrides profile config and ~/.tgclirc):
# export TELEGRAM_PROXY="https://t.me/proxy?server=HOST&port=PORT&secret=SECRET"
# Remove the configured proxy
tgcli config unset proxy --json --timeout 30s
tgcli doctor --json --timeout 30s
tgcli doctor --connect --json --timeout 30sFor tasks like "analyze chat history", "what happened this week", "digest/news":
- Resolve chat:
tgcli channels list --query "<name>" --json --timeout 30s- optionally
tgcli groups list --query "<name>" --json --timeout 30s
- Ensure archive flow:
tgcli channels sync --chat <id> --enabletgcli sync jobs add --chat <id> --depth 500tgcli service status --json --timeout 30s— check if runningtgcli service install --json --timeout 30s— if not installed yettgcli service start --json --timeout 30s— start daemon
- Read archive first:
tgcli messages list --chat <id> --source archive --limit 500 --json --timeout 30s
- If archive is still empty, fallback to live:
tgcli messages list --chat <id> --source live --limit 500 --json --timeout 90s
- Build digest/synthesis from JSON payload.
For tasks like "monitor these channels", "keep syncing my subscriptions":
Architecture: channels sync --enable marks channels for watching → sync jobs add creates backfill tasks → service start runs a persistent daemon that processes jobs and pulls realtime updates.
- Enable sync for each channel:
tgcli channels sync --chat <id|@username> --enable --json --timeout 30s- repeat for each channel to monitor
- Add backfill jobs (optional, pulls history):
tgcli sync jobs add --chat <id|@username> --depth 1000 --json --timeout 30s
- Start the background daemon:
tgcli service install --json --timeout 30s— first time onlytgcli service start --json --timeout 30s
- Verify it's running:
tgcli service status --json --timeout 30stgcli sync status --json --timeout 30s— shows per-channel sync progress
- Check for errors:
tgcli sync jobs list --status error --json --timeout 30stgcli sync jobs retry --all-errors --json --timeout 30s
- Stop monitoring a channel:
tgcli channels sync --chat <id|@username> --disable --json --timeout 30s
Alternative without systemd service (one-shot or foreground):
tgcli sync --once— run one sync pass and exittgcli sync --follow— keep syncing in foreground (ctrl-c to stop)tgcli sync --follow --idle-exit 5m— auto-exit after 5 minutes idle
- "My channels/subscriptions" ->
tgcli channels list ... - "Monitored/synced channels" ->
tgcli sync status --json --timeout 30s
- "read messages in "
- "search telegram for "
- "send this text/file to telegram"
- "download file from telegram message"
- "summarize what was discussed this week"
- "what's new in ?"
- "show my mentions in "
- "tag channels by topic"
- "add user to telegram group"
- "get invite link for group"
- "reply to a specific telegram message"
- "list buttons on a telegram message"
- "click inline callback button"
- "start syncing this channel"
- "monitor my telegram channels"
- "прочитай сообщения в канале"
- "найди в телеграме про релиз"
- "отправь сообщение в канал"
- "дай сводку по чату"
- "скачай файл из сообщения"
- "опубликуй пост с картинкой"
- "отправь фото с подписью в канал"
- "post with cover image"
- "edit/delete/forward telegram message"
- "react with emoji to message"
- "ban/kick/promote user with granular permissions"
Use telegram-mcp for those operations.