tgcli

If tgcli is not found, install it: npm install -g @dapi/tgcli then run tgcli auth for first-time login.; tgcli auth only authenticates the Telegram session. Use tgcli sync --once or tgcli sync --follow to seed/archive dialogs.; Always add --json for agent workflows.

dapi/tgcli94 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "tgcli"
description: "If tgcli is not found, install it: npm install -g @dapi/tgcli then run tgcli auth for first-time login.; tgcli auth only authenticates the Telegram session. Use tgcli sync --once or tgcli sync --follow to seed/archive dialogs.; Always add --json for agent workflows."
license: "MIT"
---

# tgcli

Telegram CLI skill for AI agents.

## Install

Install this skill from GitHub:

```bash
npx skills add dapi/tgcli --skill tgcli --agent '*' -g -y
```

Install CLI:

```bash
npm install -g @dapi/tgcli
```

Authenticate once:

```bash
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 `tgcli` is not found, install it: `npm install -g @dapi/tgcli` then run `tgcli auth` for first-time login.
- `tgcli auth` only authenticates the Telegram session. Use `tgcli sync --once` or `tgcli sync --follow` to seed/archive dialogs.
- Always add `--json` for agent workflows.
- Add `--timeout 30s` by default; use `--timeout 90s` for heavy archive fallback reads.
- Prefer explicit `--source archive|live|both` instead of relying on defaults.
- Never use `--since`; tgcli uses `--after` and `--before` with ISO timestamps.
- Never use `tgcli sync --chat ...`; top-level `sync` only runs workers via `--once` / `--follow`.
- To sync a specific chat: `tgcli channels sync --chat <id|@username> --enable` and/or `tgcli sync jobs add --chat <id|@username>`, then run `tgcli sync --once` or `tgcli sync --follow`.
- If command shape is uncertain, verify it first with `tgcli <command> --help` instead of guessing flags.
- Select named accounts with the global `--account <id|alias|phone>` option before the command, or with `TGCLI_ACCOUNT`. Selection precedence is `--account` → `TGCLI_ACCOUNT` → `default`.
- The `default` profile keeps the legacy store and existing authorization. Never run `auth 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.
- For sending format control:
  - `--parse-mode markdown|html|none` (case-insensitive)
  - for `send photo` and `send file`, `--parse-mode` requires `--caption`
  - `--reply-to <messageId>` replies to a specific message; if both `--reply-to` and `--topic` are passed, `--reply-to` wins
  - `--silent` sends without notification sound
  - `--no-forwards` protects message from forwarding/saving
  - `--schedule <iso>` schedules message for future delivery (ISO 8601, must be in the future, within 365 days)
  - `--caption-above` shows caption above media (`send photo`/`send file`, requires `--caption`)
  - `--spoiler` blurs media until tapped (`send photo`/`send file`)
  - `--retries <n>` retries transient network/transport failures for `send photo`
  - `--retry-backoff <ms|constant|linear|exponential>` controls retry delay for `send photo`
  - `--force-document` sends photo/video as uncompressed document (`send file` only)
  - `--retries <n>` retry on failure with exponential backoff (default 0); JSON output includes `retry_log` and `attempts` when 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
- Never delete lock files (`LOCK`, `database is locked`): wait and retry.
- For multiline text or arbitrary user-authored text, use `scripts/send_text.py` and
  pass the body through stdin. It invokes `tgcli` without a shell, preserving real
  line breaks and punctuation exactly.
- Never construct a shell command with `JSON.stringify(message)` and never put
  literal `\\n` sequences in `--message`: Telegram will display the backslash and
  the letter `n` instead of a line break.
- After sending multiline text, read the message back and verify that `text`
  contains real newline characters, not literal `\\n` sequences.

## Core Command Patterns

### Accounts

```bash
# 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

```bash
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

```bash
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:

```bash
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.

```bash
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 photo` for local PNG/JPG when Telegram should render a photo preview.
- Use `tgcli send file` for 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

```bash
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

```bash
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

```bash
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

```bash
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

```bash
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)

```bash
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)

```bash
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

```bash
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

```bash
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)

```bash
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

```bash
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
# Route via SOCKS5 (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 config.json; put in dotfiles/envrc):
# 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":

1. Resolve chat:
   - `tgcli channels list --query "<name>" --json --timeout 30s`
   - optionally `tgcli groups list --query "<name>" --json --timeout 30s`
2. Ensure archive flow:
   - `tgcli channels sync --chat <id> --enable`
   - `tgcli sync jobs add --chat <id> --depth 500`
   - `tgcli service status --json --timeout 30s` — check if running
   - `tgcli service install --json --timeout 30s` — if not installed yet
   - `tgcli service start --json --timeout 30s` — start daemon
3. Read archive first:
   - `tgcli messages list --chat <id> --source archive --limit 500 --json --timeout 30s`
4. If archive is still empty, fallback to live:
   - `tgcli messages list --chat <id> --source live --limit 500 --json --timeout 90s`
5. 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.

1. Enable sync for each channel:
   - `tgcli channels sync --chat <id|@username> --enable --json --timeout 30s`
   - repeat for each channel to monitor
2. Add backfill jobs (optional, pulls history):
   - `tgcli sync jobs add --chat <id|@username> --depth 1000 --json --timeout 30s`
3. Start the background daemon:
   - `tgcli service install --json --timeout 30s` — first time only
   - `tgcli service start --json --timeout 30s`
4. Verify it's running:
   - `tgcli service status --json --timeout 30s`
   - `tgcli sync status --json --timeout 30s` — shows per-channel sync progress
5. Check for errors:
   - `tgcli sync jobs list --status error --json --timeout 30s`
   - `tgcli sync jobs retry --all-errors --json --timeout 30s`
6. 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 exit
- `tgcli 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 <channel>"
- "search telegram for <query>"
- "send this text/file to telegram"
- "download file from telegram message"
- "summarize what was discussed this week"
- "what's new in <chat>?"
- "show my mentions in <channel>"
- "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.

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY