Telegram TUI client: local cache of all chats, regex search, delete messages across all devices.
bun installbun start— if.envis missing (andAPI_ID/API_HASHaren't set as real env vars) it creates one from a template and exits; getAPI_ID/API_HASHat https://my.telegram.org → API development tools and fill it inbun startagain — on first run it asks for phone/code/2FA, then downloads history (incrementally: a restart only fetches what's new)
- Type a regex in the search field — results update as you type. A plain string matches case-insensitively;
/pat/flagsis used as-is. tab— switch focus between input and list,space— toggle selection,d— delete the selected (or current) messages with ay/nconfirm,esc— reset,^P— cycle patterns frompatterns.txt(the file is re-read on the fly),^C— quit.- Deletion uses
revoke: for everyone, on all devices. Others' messages are deleted where you have rights; permission errors are shown in the status bar.
Cache and session live in data/ (not committed).
Any subcommand runs headless and prints one line of JSON — no TUI. tg-client tui
(or no args) still launches the interactive client.
bun start search "<regex|/pat/flags>" [--limit N]— search the cache (offline, no auth) →{count, results}bun start stats— number of cached messages (offline) →{messages}bun start sync— download/update history (needs auth) →{chatsDone, messages, errors}; progress prints to stderr as it runs (one line per update, overwritten in place on a TTY), so stdout stays a single JSON linebun start delete <chatId>:<msgId> ...— delete for everyone (needs auth) →{deleted, errors}bun start help— usage JSONbun start --version—{version}
search/stats read data/cache.db directly and need no Telegram connection.
Build a standalone binary with bun run build (→ dist/tg-client); git tag v*
publishes per-platform binaries via .github/workflows/release.yml. See
AGENTS.md for the agent setup guide and the
telegram-grep-cli skill that downloads
that binary and documents the commands.
bun test— unit tests.bun run test:mutation— StrykerJS mutation testing (config instryker.conf.json), kept at 100%. Uses the command runner overbun testplus the TypeScript checker; reports go toreports/mutation/(gitignored).
GitHub Actions (.github/workflows/, deps cached across runs):
- ci.yml —
bun run typecheck+bun teston every push/PR; a separatebun audit --prodjob fails the build on vulnerable production dependencies. - lint.yml — Biome lint + format check (
biome ci, config inbiome.json). - codeql.yml — CodeQL SAST (
security-extended) on push/PR and weekly. - dependabot.yml — weekly PRs bumping dependencies (npm/
bun.lock) and the actions themselves.
Locally: bun run typecheck, bun run lint (bun run format to autofix), bun test, bun audit.
The UI follows the system language (ru/en); translations live in src/locales/*.ts. To override:
- TUI/CLI:
TG_LANG=en bun start(orru). - Web: the dropdown next to the search box (persisted in the browser).
bun run web— the same client with a browser UI. By default it listens on127.0.0.1only.- To access from a phone:
LAN=1 bun run web(listens on0.0.0.0). The console prints a URL with a token. /api/*is token-protected (Authorization: Bearer, stored indata/web-token). Open the printed…/?token=…URL once — the token is saved in the browser, after which you can install it as a PWA (Safari → "Add to Home Screen") with the plain address. Origin is also checked, so third-party sites can't call the API (CSRF).- The TUI (
bun start) and web (bun run web) share one Telegram session — run only one at a time. - Auth persists to
data/sessionafter first login — no need to log in again on restart. - For a one-off headless login (no
data/sessionyet, no TTY), pass a session string exported from mtcute as a real env var:SESSION_STRING=... bun start. An invalid/stale value is ignored (with a warning) rather than needed on every run, so there's no reason to keep it in.envonce you're logged in.