사용 방법
- 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
- 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
- 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/enitrat/skill-issue --skill tg-cli시작 전 확인
- 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
- 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.
Telegram reader / sender CLI
Terminal access to your own Telegram account. It authenticates as your user account over MTProto via Telethon, so it can read and send anything your account can — unlike a bot, which only sees chats it was explicitly added to.
How to invoke
Invoke it as `tg` — on $PATH via claudine/scripts/link_clis.py, which links this repo's bin/tg into ~/.local/bin, so it always runs the current checkout: a git pull, or even an uncommitted edit, takes effect immediately with nothing to reinstall.
tg chatsIf tg is not on $PATH, run <skill-dir>/bin/tg (a symlink to tg_cli.py, a PEP 723 script — uv resolves deps inline on first run: click + telethon).
Authentication
Log in (one-time)
- Get an api_id and api_hash from <https://my.telegram.org> → *API
development tools*. These identify the client app, not your account, and are reused across logins.
- Run
tg login. It prompts for the apiid / apihash (stored after the
first time), then for your phone number, the login code Telegram sends you, and your 2FA password if you have one set.
Without a terminal (Claude Desktop, Cowork), use tg link instead of step 2: it returns at once and opens a local page with a QR code. On the phone, Telegram → Settings → Devices → Link Desktop Device, scan it, and type the 2FA password on the page if asked; then tg whoami confirms. The page stays up 5 minutes. The apiid / apihash come from TG_API_ID / TG_API_HASH (the extension's settings) or the stored config.
Credentials and the authenticated session are written chmod 600 inside a chmod 700 ~/.config/tg-cli/ (override with TG_CONFIG_DIR, or inject creds via TG_API_ID / TG_API_HASH).
tg whoami proves the binding; tg auth-status [--json] reports where the credentials come from (vault broker or local file) without secrets, and tg auth-sync retries a pending vault sync (exit 3 while still pending). tg logout drops the session.
Security
The session file (~/.config/tg-cli/session.session) is a credential — it grants full access to the account without the password. Keep it off synced folders and out of git (the repo .gitignore blocks *.session and config.json). If it ever leaks, revoke it instantly from the Telegram app: Settings → Devices → terminate session. Enabling a 2FA password on the account is strongly recommended.
Output conventions
- Default: compact text, one line per message (
ts sender: text) or per chat. --jsonon every read command: structured rows; messages carryid,ts,
epoch (Unix seconds), sender, text (and transcript with --transcribe). tg folder --json is a bare list of {chat, messages: [...]}.
tg listenprints one JSON line per new message; its status line goes to
stderr.
- stdout carries data only; warnings (e.g. a failed transcription) go to stderr.
- Timestamps are converted from Telegram's UTC to local time; filter on
epoch against your own UTC cutoff.
- Non-text messages render as a
[MediaType]/[ActionType]placeholder. - Senders.
senderismefor own messages, else the display name; a sender
with no resolvable name is emitted as its numeric id. Write that literal id, never a name guessed from content or context.
Commands
# Read
tg whoami [--json] # logged-in account (name, username, id)
tg chats [--limit N] [--not-in-folder NAME] [--json] # conversations, most recent first
tg folders [--json] # chat folders + chat counts
tg folder <name> [--since ISO] [--limit N] [--json] # batch-read every chat in a folder
tg read <query> [--limit N] [--since ISO] [--match N] [--transcribe] [--json]
tg search <text> [--chat QUERY] [--match N] [--limit N] [--json]
tg download <query> [--limit N] [--since ISO] [--docs-only] [--match N] [--out DIR] [--json]
tg listen <query> [--all] [--exec CMD] [--transcribe] # stream new messages as JSON lines
# Write (dry-run unless --yes)
tg send <query> [text] [--file PATH] [--peer P] [--match N] [--reply-to ID] [--yes]
tg folder-add <folder> <query> [--match N] [--yes] [--json]
# Auth
tg login [--api-id ID] [--api-hash HASH] # interactive, one-time
tg link # QR code page, no terminal needed
tg logout
tg auth-status [--json]
tg auth-synctg login [--api-id ID] [--api-hash HASH]
Interactive one-time authentication (see above).
tg whoami [--json]
Show the logged-in account (name, username, id).
tg chats [--limit N] [--not-in-folder NAME] [--json]
List conversations, most recent first, each tagged [dm] / [group] / [channel] with an unread count. --not-in-folder NAME restricts the listing to chats not already in the named folder — the way a folder-scoped digest job spots a newly-joined partner group sitting outside its curated folder (which a folder-only sweep would otherwise miss).
tg chats --not-in-folder Zama --limit 60 --json # recent chats outside the Zama folderA chat already in the folder can still appear in this listing (a stale or unresolved peer entry, not a fold-in candidate): confirm against the actual tg folder <name> output before adding it.
tg folders [--json]
List chat folders (Telegram "chat folders" / dialog filters) and how many chats each explicitly contains.
tg folder <name> [--since ISO] [--limit N] [--json]
Read recent messages from every chat in the folder matching <name>, windowed by --since (ISO date/datetime, UTC if no tz). This is the batch building block for digest jobs: it enumerates the folder's chats, fetches each chat's recent messages, and keeps those at/after the cutoff. In --json each message carries an epoch (Unix seconds) so a caller can filter precisely against its own cutoff.
tg folders # list folders + chat counts
tg folder Zama --since 2026-07-05 --json--json is a bare list of {chat, messages: [{ts, epoch, sender, text}]} (no dict wrapper); filter on epoch against your own UTC cutoff. --limit caps messages fetched per chat before the --since filter, so if a folder's newest messages all cluster at the window start the fetch was truncated: re-pull with a larger --limit. A folder chat count lower than a previous run's is a fetch failure, not a smaller folder.
The call can hang on an otherwise healthy session. Wrap it in a timeout and retry once before treating it as a failure. macOS ships no GNU timeout binary, so use a subprocess timeout or background the call and poll:
tg folder Zama --since 2026-07-05 --json > out.json 2> err.log & pid=$!
sleep 45; kill -0 $pid 2>/dev/null && kill $pid # finished on its own if out.json has contenttg folder-add <folder> <query> [--match N] [--yes] [--json]
Add the chat matching <query> into the folder matching <folder>. This edits your account's folder layout only — never any chat's messages — and defaults to a dry-run; pass --yes to apply. Idempotent: a chat already in the folder is left untouched (already_in_folder: true). Use it to fold a newly-joined partner group into a curated folder so folder-scoped digests stop missing it. Only plain folders are editable (not "All chats", not shared/invite folders). Gate auto-adds to real partner groups (e.g. X <> Zama, Zama x Y) — don't sweep in public channels or unrelated personal groups.
tg folder-add Zama "Zama x Ember" # dry-run: shows the proposed add
tg folder-add Zama "Zama x Ember" --yes # actually add it to the foldertg read <query> [--limit N] [--since ISO] [--match N] [--json]
Show messages from the chat whose name best matches <query> (case-insensitive substring). Ambiguous matches print a numbered list — pick one with --match N. Without --since it shows the last --limit messages (default 50); with --since (ISO date/datetime, UTC if no tz) it shows the first --limit messages at/after the cutoff, oldest first, so a busy window is read from its start. Raise --limit if the output stops short of now.
tg read alice # most likely "Alice" chat
tg read "Dev Team" --limit 200
tg read alice --match 2 # 2nd match when ambiguous
tg read "Founding Apes" --since 2026-09-28T22:00:00 --limit 200 # a day's window
tg read alice --limit 20 --transcribe --json # latest messages with local audio transcriptsFor --transcribe, install transcribe-cli so transcribe is on PATH on the machine running tg. Each machine needs its own tg login; authenticated sessions must not be copied between machines. Transcripts can misrecognize names and technical terms. See the transcription options and cache behavior under Notes.
tg search <text> [--chat QUERY] [--match N] [--limit N] [--json]
Search messages containing <text>. With --chat it searches that one conversation (single exact request); without it, a best-effort global search across all chats.
tg send <query> [text] [--file PATH] [--match N] [--peer P] [--yes]
Send to the chat matching <query>. With --file it sends that document and any text becomes the caption. Defaults to a dry-run printing the resolved recipient and payload; pass --yes to actually send. Target an exact recipient with --peer @username / phone / id, or disambiguate with --match.
tg send alice "running late" # dry-run: shows recipient + text
tg send alice "running late" --yes # actually sends
tg send alice "the deck" --file deck.pdf --yes # send a document with captiontg download <query> [--limit N] [--since ISO] [--docs-only] [--match N] [--out DIR] [--json]
Scan the last --limit messages of the matched chat and save every media attachment into --out (default .), using Telegram's own filename where available. Narrow with --since (only messages at/after the cutoff) and --docs-only (files like PDFs/decks, skipping photos and stickers).
tg download alice --limit 50 --out ./tg-media
tg download "Zama x Steakhouse" --since 2026-07-05 --docs-only --out ./tg-docsdownload can hang indefinitely on a link-only chat (one with no attachments in the window). Wrap each call in a ~30s timeout (see the folder note for the macOS pattern) and treat a hang or an empty result as "no docs", not an error. Read a downloaded deck in full, not just the intro: a partner's load-bearing constraints usually sit in a later product-spec page.
Recipes
# What happened in a folder since yesterday (digest)
tg folder Work --since 2026-07-05 --json > out.json
# A newly-joined group missing from a curated folder: find it, then fold it in
tg chats --not-in-folder Work --limit 60 --json
tg folder-add Work "Dev Team" # dry-run
tg folder-add Work "Dev Team" --yes
# What did alice send me this week, with voice notes transcribed
tg read alice --since 2026-10-20 --transcribe --json
# Every PDF/deck shared in a chat
tg download "Dev Team" --since 2026-07-05 --docs-only --out ./tg-docs
# Reply to a specific message
tg read alice --limit 5 --json # take the message id
tg send alice "agreed" --reply-to 12345 --yesGotchas
tg foldercan hang on an otherwise healthy session: wrap it in a timeout and
retry once (macOS has no GNU timeout; background the call and kill it).
tg downloadcan hang indefinitely on a link-only chat: wrap each call in a
~30s timeout and treat a hang or empty result as "no docs".
--limitcaps messages fetched per chat before--sincefilters: if the
newest messages cluster at the window start, re-pull with a larger --limit. A folder chat count lower than a previous run's is a fetch failure, not a smaller folder.
chats --not-in-foldercan list a chat already in the folder (stale peer
entry): confirm against tg folder <name> before folder-add.
- Global
search(without--chat) is best-effort;--chatis one exact
request.
- No per-message permalinks exist. Cite a message by chat name + timestamp
(+ filename for an attachment).
- Sending is outbound: confirm the exact recipient and text with the user before
send --yes. Each machine needs its own tg login; never copy a session.
When to use
Trigger when the user wants to read, search, or send their personal Telegram messages — "show my Telegram chat with X", "what did Y send me on Telegram", "search my Telegram for Z", "message X on Telegram that …".
Do not use it on someone else's account. Sending is an outbound action: confirm the exact recipient and text with the user before running send --yes.
Notes
- Optional transcription.
read,folder, andlistenaccept
--transcribe/--no-transcribe and --transcribe-backend auto|whisper|parakeet. They download voice notes and audio files and call transcribe file locally; transcribe must be installed on PATH. Off by default; TG_TRANSCRIBE=1 enables it, TG_TRANSCRIBE_BACKEND selects the backend. JSON preserves text and adds transcript to audio rows (null on failure); human output includes the transcript. Errors warn on stderr without dropping the message. Audio downloads are temporary; transcripts persist in ~/.cache/tg-cli/transcripts (TG_CACHE_DIR overrides the cache root), keyed by chat, message, and requested backend, with directory/file modes 0700/0600. Delete entries to retranscribe. Example: tg read alice --transcribe --json.
- Reading is non-destructive — only
send --yesandfolder-add --yeswrite anything.
Tests
for t in tests/test_*.py; do uv run --script "$t"; doneProject layout
tg-cli/
SKILL.md # this file
README.md
tg_cli.py # the CLI (PEP 723 script, Telethon client)
bin/tg # symlink -> ../tg_cli.py — invoke this
tests/ # offline tests (PEP 723 scripts)
