tgcli
Telegram CLI skill for AI agents.
Install
Install this skill from GitHub:
npx skills add dapi/tgcli --skill tgcli --agent '*' -g -y
Install CLI:
npm install -g @dapi/tgcli
Authenticate once:
tgcli auth
Tool Boundary: tgcli vs telegram-mcp
| Use tgcli for | Use telegram-mcp for |
|---|---|
| Read/search/archive messages | edit/delete/forward |
| Send text/photo/files and topic posts | reactions |
| Forum topics listing/search | inline bot buttons |
| Download media from messages | advanced interactive actions |
| 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 |
Execution Rules
- 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 archive fallback reads. - Prefer explicit
--source archive|live|bothinstead of relying on defaults. - 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.
Core Command Patterns
Accounts
# 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 --follow
Account 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.
Read
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
Never use --since here; the correct flag is --after.
Search
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 30s
Both 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 Text/Photo/File
⚠ 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_MESSAGE
The 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
Photo Preview vs Document Upload
- 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.
Media Download
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 30s
Channels
tgcli 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 30s
Topics
tgcli topics list --chat <id|@username> --limit 50 --json --timeout 30s
tgcli topics search --chat <id|@username> --query "release" --limit 20 --json --timeout 30s
Contacts
tgcli 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 30s
Groups
tgcli 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 revoke --chat <id|@username> --json --timeout 30s
tgcli groups join --code <invite-code> --json --timeout 30s
tgcli groups leave --chat <id|@username> --json --timeout 30s
Join-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.
Tags (Channel Classification)
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 90s
Metadata (Channel Cache)
tgcli 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 90s
Folders
tgcli 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 30s
Use --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).
Sync Jobs
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 30s
Do not write tgcli sync --chat ...; queue work with sync jobs add --chat ..., then process it with tgcli sync --once or tgcli sync --follow.
Service (Background Sync Daemon)
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 30s
Config & Auth
tgcli 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 30s
Archive + Analysis Workflow
For 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.
Continuous Sync Workflow
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
Sync Semantics
- "My channels/subscriptions" ->
tgcli channels list ... - "Monitored/synced channels" ->
tgcli sync status --json --timeout 30s
Trigger Examples
Should trigger
- "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"
- "start syncing this channel"
- "monitor my telegram channels"
- "прочитай сообщения в канале"
- "найди в телеграме про релиз"
- "отправь сообщение в канал"
- "дай сводку по чату"
- "скачай файл из сообщения"
- "опубликуй пост с картинкой"
- "отправь фото с подписью в канал"
- "post with cover image"
Should not trigger
- "edit/delete/forward telegram message"
- "react with emoji to message"
- "click inline button in bot"
- "ban/kick/promote user with granular permissions"
Use telegram-mcp for those operations.