Read your Telegram unread. Without reading it.
A local CLI that turns Telegram chats, YouTube videos, web pages, and files into reports with citations — using whichever LLM you keep an API key for.
You have 47 unread Telegram groups. You will never read them. You will now.
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install unread && unread init
unread @somegroup --last-days 7
It pulls the chat, runs it through whichever LLM you keep an API key for, and hands you a report with clickable citations back to every claim. Same shape for YouTube videos, web pages, voice messages, recorded meetings, podcasts, PDFs, and stdin. Or run it as a self-hosted Telegram bot and forward anything weird at it — see Self-hosted Telegram bot below.
See a real report from @thehackernews
— 99 messages over two weeks, four chunks, $0.016, every bullet linked back to its source.
Three verbs. The same <ref>
shape works on all of them.
unread <ref>
—analyze. Map-reduce the source into a Markdown report. Every claim links back to its message / paragraph / timestamp.unread ask <ref> "Q"
—ask. One-shot Q&A with citations. Multi-turn follow-ups are one keystroke away.unread dump <ref>
—dump. Save the original — chat history, transcript, article — verbatim. No LLM call.
<ref>
is any of:
unread @somegroup --last-days 7 # Telegram chat / channel / forum
unread "https://youtu.be/jmzoJCn8evU" # YouTube video (captions or Whisper)
unread "https://paulgraham.com/greatwork" # Web page / article
unread ./meeting.mp3 # Local file (PDF, DOCX, audio, video, image)
cat notes.txt | unread # Stdin
This is the bit you actually want.
Use unread tg
to analyze any yours chats with interactive picker.
Source language and report language are independent. The chat can be in Russian, the report in English. Or English source → Spanish report. Or anything → anything — the LLM does the heavy lifting and the source hint is optional.
unread @forklog --last-days 7 --report-language en
unread @thehackernews --last-days 14 --report-language ru
Out of the box, hand-tuned preset structures ship for en
and ru
under presets/<lang>/*.md — section names, the forum addendum, the report skeleton. For any other report language the LLM writes the structure on the fly using the English preset as a template. It works. It's not as polished as the native trees.
What you get back, for a Russian chat with --report-language en
:
## Decisions
- Migrate to indexed fund structures starting Q1 2026. [#1586](https://t.me/c/3865481227/584/1586)
- Drop the legacy K8s 1.27 cluster by end of month — Bob owns the rollout. [#1604](...)
## Open questions
- Who pays the OpenAI bill across the joint team? [#1612](...)
Every citation is a t.me
link. Click → Telegram opens that message.
unread
handles forums (topics), channel comments, voice notes
(transcribed), photos (described), forwarded media (deduped — Whisper
runs once across N chats), and your folder structure (--folder Work
batches everything in that Telegram folder). The full source-shape matrix is in docs/sources.md.
unread <youtube-url>
does the obvious thing: tries captions first,
falls back to Whisper if the video has none. Every citation in the
report becomes a t=SECONDS
deep link, so clicking jumps you to that moment of the video.
unread https://www.youtube.com/watch?v=Pmd6knanPKw
unread https://www.youtube.com/watch?v=SBEtiXnLtpw --report-language de
from what timecode should i start watching if i want to know about RAG?
unread ask https://youtube.com/watch\?v\=k1njvbBmfsw "from what timecode should i start watching if i want to know about RAG?"
unread dump https://www.youtube.com/watch?v=BDqvzFY72mg --mode=transcript
Cached after the first run. Re-asking a question about the same video costs only the answer call — no yt-dlp, no Whisper, no re-spend.
Plain web pages (unread <url>
), PDFs, DOCX, Markdown, audio, video,
and images all use the same <ref>
syntax. See docs/sources.md for the full list of supported extensions and the cache rules.
That 12-minute voice message someone sent you. The 45-minute meeting recording you'll never play back. The hour-long lecture you wanted to skim. Drop any of them in and get a Markdown summary:
unread ./meeting.mp3 # audio → Whisper → summary
unread ./standup.mp4 # video → ffmpeg → Whisper → summary
unread ./voice-note.ogg # forwarded voice message saved to disk
unread ./report.pdf # PDF
unread ./screenshot.png # image (vision)
Inside a Telegram chat the same step runs invisibly: voice notes and
video circles are transcribed, photos are described, the analysis
treats them as text. Forward a voice across five chats — Whisper runs
once, cached by Telegram's stable document_id
.
Whisper is roughly $0.006 per minute of audio. A 30-minute podcast costs under twenty cents. Re-running on the same file is free when it hits the cache.
The full kind matrix and cache rules are in docs/sources.md#media-enrichment.
Drop in a key for any of these. Switch at any time with
unread settings
— caches and analyses persist across switches.
| Provider | What you get |
|---|---|
| OpenAI | |
| Chat models + Whisper (audio) + embeddings + vision. The full toolkit. | |
| Anthropic (Claude) | |
| Chat models only. Pair with an OpenAI key if you also want voice / image enrichment. | |
| Google (Gemini) | |
| Chat models only. Same pairing note. | |
| OpenRouter | |
| One key, dozens of chat models — handy for trying Llama / DeepSeek / Mistral without separate signups. | |
| Local (Ollama / LM Studio / vLLM) | |
| OpenAI-compatible HTTP. Zero cost, zero data leakage, your own model. |
Whisper / vision / embeddings are OpenAI-only. If you pick Anthropic or
Google as your chat provider and also want media enrichment, add an
OpenAI key alongside — unread init
will offer it.
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
uv tool install unread
unread init
unread "https://paulgraham.com/greatwork.html" # any web page
unread ./meeting.mp3 # any local file
unread @somegroup --last-days 7 # last week of a chat
unread doctor # confirm everything works
unread help # show help
No virtualenv to activate, no pip
conflicts, no global Python
pollution. Upgrade later with uv tool upgrade unread
.
Skip the Telegram step if you only want YouTube / web / file analysis — those work with only an AI key.
Full install matrix (Windows / ffmpeg / dev install / editable) is in docs/install.md.
Run the same pipeline as a Telegram bot. Forward it a voice message
you don't feel like listening to, a PDF you don't feel like reading, a
YouTube link, a t.me/...
post from a channel you're not sure you want to subscribe to, or that suspicious link a friend just sent — you get a Markdown summary back as a document, with cost and timing in the caption.
Single-user by design: the bot only answers ONE Telegram ID. The
allowlist is auto-derived from the user session you give it (mounted
or sent via /upload_session
); UNREAD_BOT_OWNER_ID
is only a bootstrap fallback for the case where no session is installed yet. Everyone else is silently dropped.
Three install paths — pick by environment:
1. Local laptop / dev (unread bot run
foreground):
uv tool install unread
export UNREAD_BOT_TOKEN=123:abc... # from @BotFather
unread bot run
2. Fresh Linux VM — one line, blank disk to running systemd service:
UNREAD_EXECUTABLE="install-unread-bot.sh" && curl -fsSL https://raw.githubusercontent.com/maxbolgarin/unread/main/scripts/install-bot.sh > $UNREAD_EXECUTABLE && chmod +x $UNREAD_EXECUTABLE && ./$UNREAD_EXECUTABLE && rm ./$UNREAD_EXECUTABLE
Installs uv + ffmpeg + libpango, runs unread init
, asks for the
@BotFather
token, drops a systemd --user
unit that auto-restarts on crash and survives SSH disconnect.
3. Docker — pull the generic unread
image from GHCR (same image
can also run one-off CLI commands; command: ["unread","bot","run"]
in the compose file specifies the bot mode):
cp .env.bot.example .env.bot && $EDITOR .env.bot
docker compose -f docker-compose.bot.yml --env-file .env.bot up -d
Reports default to PDF; set UNREAD_BOT_REPORT_FORMAT=md
in .env.bot
to skip PDF rendering (.md
upload instead). The first time you
message the bot with a t.me/...
link it'll ask for /upload_session
— send your laptop's ~/.unread/storage/session.sqlite
as a Telegram document and it's ready.
Full feature reference (slash commands, the confirm panel, what each input kind does) is in docs/bot.md. End-to-end VM deployment guide covering all three paths is in
docs/bot-vm-deploy.md
I have ~50 Telegram groups I genuinely want to follow and not enough hours to read them. The same is true for the videos I bookmark and the articles I save to "read later." LLMs are now cheap enough that analyzing a week of group chat costs less than a coffee. My time is not. So I built the CLI I wanted to use — local-first, citation-backed, provider-agnostic — and now I open Telegram a lot less.
Local-first. SQLite under~/.unread/
. Your messages, embeddings, analyses, and secrets stay on your disk. The only network calls are to Telegram, your chosen AI provider, and any URLs you point at.Security. Your API keys, Telegram session, and other secrets can be encrypted at rest. You can use your OS keychain (default) or passphrase to unlock the CLI.Citations on every claim. Reports link back to the source message / paragraph / timestamp.--cite-context N
expands citations into<details>
blocks with surrounding context, so the report is auditable without re-opening Telegram.Two-layer cache. Local analysis cache (re-running an unchanged chat is free) + the AI provider's prompt cache (server-side discount on repeated prefixes).unread cache stats
shows the hit rate.Cost guardrails.--max-cost 0.50
aborts or confirms before you spend more than that.--dry-run
estimates without calling the model.unread stats
shows lifetime spend by chat / preset / day.PII redaction.--redact
scrubs phones, emails, IBANs, and Luhn-valid card numbers from what gets sent to the LLM. Originals stay in the local DB.Map-reduce, automatic. Big histories get chunked, summarized in parallel, then merged. Each chunk is cached independently — adding one message to the tail re-costs only the last chunk.Forums, channels with comments, folders. Telegram's awkward shapes are first-class.--all-per-topic
,--with-comments
,--folder Work
.
Encryption modes (keystore
, passphrase-derived pass
), session hygiene, and the threat model are documented in docs/security.md.
Does this ship my Telegram history to OpenAI?
Only the messages in the window you asked about, only after PII
redaction if you set --redact
, and only to the provider whose key you
configured. Nothing else leaves the machine. The local cache is in
your install dir; you can wipe it any time with unread cache ai purge
.
What if I don't use Telegram?
Skip unread init
's Telegram step. unread <url>
and unread <file>
work with only an AI key. Most of the codebase is source-agnostic.
What languages does it actually support? Source content: anything Whisper auto-detects (audio) or your LLM can read (text) — pretty much everything human languages cover. Reports: anything your LLM can write. EN and RU get hand-tuned preset structures; other report languages use the English preset as a template that the LLM translates on the fly.
Will it cost me money?
Yes — your AI provider charges. unread
itself is free. Per-call USD
is logged; --max-cost N
caps a single run. Re-running an unchanged
chat is free (local cache hit). With cheap models (gpt-5.4-mini
-class, Gemini Flash, Claude Haiku) the bill is small enough that most users stop reading the cost reports after a week.
Is it actually fast?
Fast enough that I stopped reading group chats. No benchmark table —
speed depends on chat size, model choice, and your network. Try it
with unread @somegroup --last-days 1 --dry-run
to see the estimate before any LLM call.
Can I run it on a server / in cron?
Yes. Non-TTY mode skips interactive prompts. unread watch --interval 1h ...
loops in the foreground (run under tmux / systemd / nohup). API keys
can come from env vars or ~/.unread/.env
. The passphrase backend
reads UNREAD_PASSPHRASE
for headless unlock.
Can I plug in my own preset / prompt?
--preset custom --prompt-file my-prompt.md
. Same frontmatter format as the bundled ones in presets/. Bump
prompt_version
in the frontmatter when you edit, otherwise the cache won't notice.The full reference manual lives under docs/
:
| Topic | File |
|---|---|
| Install on macOS / Linux / Windows + first-run setup + where files live | |
docs/install.md |
docs/sources.md
watch
, subscriptionsdocs/reference.md
config.toml
, maintenance, troubleshooting, architecturedocs/configuration.md
docs/security.md
Useful inline help: unread --help
, unread <subcommand> --help
,
unread help
, unread doctor
.
PRs welcome. Read CLAUDE.md first — it's the contributor map (DB invariants, cache keys, preset version bumps, the three language axes).
covers the bench (lint / format / tests).
CONTRIBUTING.md
SECURITY.md
uv sync --extra dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
Issues and feature requests: https://github.com/maxbolgarin/unread/issues.
Standing on the shoulders of: Telethon for the Telegram side, OpenAI Whisper / Anthropic / Google Gemini / OpenRouter / Ollama for the LLM side, yt-dlp for YouTube, trafilatura for article extraction, tiktoken for token counting, Typer / Rich / structlog for the CLI shell, and uv for keeping all of the above out of your system Python.
Apache 2.0 — see LICENSE.