0.12.0 - 2026-09-08
Full audit ship: six new capabilities, a release supply chain (SBOM + build provenance), and roughly forty fixes from the five-domain adversarial audit (kernel, commands, streams, security, release engineering). Every done capability row is contract-tested against the real CLI surface.
Added
msg export- per-chat history export:tele msg export --account A --chat X [--format txt|jsonl] [--out FILE] [--limit N] [--offset-id M] [--since T] [--until T]. One message JSON object per line (default) or a human transcript;--outfiles are created with private permissions and sensitive basenames are refused; channel rows carry an additivelinkt.me permalink (https://t.me/<user>/<id>orhttps://t.me/c/<internal>/<id>); rows carrycount/scanned/truncatedwhen writing to a file. Also routed as a serve/MCPmsg exportop (Read lane, unbounded like download).msg get --replied- with a single--id, fetches the message the target replies to via grammers'get_reply_to_message(resolves cross-chat discussion parents for channel-post comments) and embeds it as an additivereplied_toobject. Rejected for--idsbatches; absent when there is no parent. Serve/MCP:GetParams.replied.profile photos- profile photo history viaiter_profile_photos:tele profile photos [--user USER] [--limit N]. Rows carry{id, date, size, sizes[], current}(photos.getUserPhotos for users, chat-photos sweep for channels/supergroups). Serve/MCP:profile photosop.chat permissions- participant rights read-back:tele chat permissions --chat X --user U. Full admin-rights flag map (12 write-side flags), full banned-rights map (22 flags +until_dateas RFC 3339), creator rights, plusrank/can_edit/kicked_by/promoted_bywhere provided; basic groups degrade to the participant role with an explanatory note. Serve/MCP:chat permissionsop.msg send --file -- streamed stdin uploads viaupload_stream:--file-size <BYTES>(exact stdin byte count) and--file-name <NAME>required; same 2 GiB cap; cannot be combined with other--filepaths or albums;--file-size/--file-nameare rejected when no stdin upload is present.- MCP resources -
tele mcpnow advertises the MCPresourcescapability with three read-only context resources:tele://skill(the embedded SKILL.md, same bytes astele skill print),tele://profile(bound account profile as JSON), andtele://dialogs(first 100 dialogs as JSON). Available in full and--read-onlymodes; unknown URIs fail with a clean-32602listing the valid set. - Release supply chain - every release target ships an SPDX SBOM (anchore/sbom-action) and a build-provenance attestation (actions/attest-build-provenance); both actions are SHA-pinned and the token scope is limited to the build job.
docs/release.mddocumentsgh attestation verifyand corrects the manual npm-publish fallback to reproduce the CI staging (publishing from the barenpm/directory would ship a launcher that can never find a binary). - Skill versioning - the installed
SKILL.mdcompatibility line is stamped from the binary version andtele skill installwarns when an existing install predates the running binary; a contract test pins the stamp toCargo.tomlso it cannot drift again.
Fixed
Kernel and executor
- FTS5 message cache:
INSERT OR REPLACEno longer silently bypasses the sync triggers -PRAGMA recursive_triggers = ONat open, with anfts5vocab/integrity-checkregression test that fails without it. - Token bucket: idle time no longer banks credit - the refill anchor advances while the bucket is full and refills are bounded by the remaining room, so a long idle gap cannot mint an instant full refill or keep over-minting afterwards.
- Replay dedupe (
CappedMap): re-delivered updates refresh their eviction position, so a redelivered update is no longer re-emitted as new when its original entry rotates out. - Peer eviction set is bounded (1024, clear-on-full) instead of permanent and unbounded.
+phonetargets now require at least 5 digits (including country code):+1can no longer trigger a realcontacts.ImportContactsside effect; parenthesized/dotted phone spellings classify consistently betweenparse_targetandclassify_target.- Per-account fan-out:
msg download,msg export,story send, andtakeout exportrun without the 300s per-account budget (matching the documented serve lane table - the old cap killed legitimate mid-transfer work and reported exit 3), and timed-out account tasks are aborted instead of left running. - Proxy URLs: IPv6 hosts are bracket-wrapped (
socks5://[::1]:9050) instead of producing a malformed authority. --tagwarns on tagged accounts that have no session file yet instead of skipping them silently.config.tomlwrites fsync the temp file before rename (no zero-length config on crash); the missing-HOME panic prints an actionable, scrubbed message on stderr.- Session import: stale
-wal/-shmsidecars of the previous database are deleted before the rename (a hot WAL from a crashed run would be replayed onto the imported session), and the account lock now covers only the copy+probe+install window instead of the whole source read. - Cache open: directory creation and permission hardening run via
spawn_blockinginstead of blocking the async runtime.
Messaging
msg deletereports the server's real affected count (partialwhen some ids were not deleted) instead of fabricating"deleted": N.msg download --allresume no longer permanently skips the checkpoint boundary: two-bound checkpoints (last_message_id+max_seen_id), local-timezone--since/--untildate handling, and an additivetruncatedflag.msg get --idsbatch fetch (single-RPC chunks of 100, additivemissing_ids).msg search --from meuses the server-side own-messages filter.msg send --split Nchunks oversized text into sequential UTF-16-aware messages (paragraph-preferred cuts).listen --count Nand--until <ts>finite-stream exits (combined across accounts, stderr notice, exit 0).listen: the connect semaphore no longer starves accounts at--parallel 1; gap detection uses the incomingpts_count(grammers continuity rule) and gap/peer state now survives reconnects; stream task panics surface their payload; filter applicability is consistent and documented (direction/pattern filters suppress action-family rows instead of silently bypassing them).chat settings --noforwardsactually works:messages.toggleNoForwardsexists at layer 227 and is now wired (the old rejection claimed the layer lacked it).admin-log --untilrejects post-2038 timestamps instead of silently wrapping viaas i32and disabling pagination early-stop;--sinceis now counted against the limit inside collection so it can no longer hide matching events.
Chats and contacts
chat createno longer reports failure after server-side success onUpdates::Combinedresponses (the retry path duplicated chats).contact addno longer reports first-time adds as failures (min-usercontact:falseresponse is a warning).--signaturesno longer clears author profiles (writessignature_profilestoo).chat requests:--userwith--linkis rejected instead of silently ignoring the link scope.chat kick:--banno longer overrides an explicit--rights view_messagesvalue;--demoterejects--preset/--rights(they were silently discarded); empty--rightsis rejected instead of promoting nobody.- Bare invite hashes are canonicalized to full
https://t.me/+...URLs where the API expects a link (--edit/--importers), while--checkstill extracts the hash. dialog --folderpagination anchors per-page and album bundling no longer drops entries.dialog folder-createverifies the written filter afterUpdateDialogFilter(a racing creator overwriting the id now surfaces an honest error) and counts Chatlist folders when allocating ids.dialog draftsdocs pinned to the exact channel-id form (the-100Bot-API convention, matching numeric--chat).
Accounts, privacy, and security
- Login: inverted
session_existed_beforefixed (failed logins deleted good sessions) across the code and staged paths. - Staged login:
--stage resend/--stage cancel-code, 303 DC migration on sign-in/resend/cancel and change-phone flows (auto home-DC switch + one retry), 2FA accepts piped stdin like the code step, takeout start clears stale export artifacts. account deletepurges session, pending secrets, and config entry after server-side delete;--dry-runis reachable without--yes.privacy set --replace(revocation) added; merge mode rejects a user resolved on both the allow and deny sides (alias-proof: username vs numeric id of the same person).phone --confirm-codedry-run redacts the OTP code andphone_code_hash; one-time secrets on argv (--confirm-code/--phone-hash) warn about process listings like--phonedoes;--show-tokenbypasses the log scrubber so the promised login URI actually prints in quiet mode.account sessions --terminatedry-run applies the same current-session guard as the real run instead of promising a refused action.- Upload exfiltration guard blocks
.session.export/.session.tmp;export-sessionrefuses a hard link to the live session (file-identity comparison). - Callback button
data_strdecodes lossy UTF-8 (invalid bytes become U+FFFD) instead of an empty string, consistent with listen rows and the click selector. rand_seedmixes a process-wide call counter: same-tick topic creations no longer collide onrandom_id.
Streams and serving
stream.resyncnow arms on every StreamError (auto-catch-up after a broken stream) and completion guards use bounded retries, so a saturated driver cannot silently drop final envelopes (10s drain budget documented).- Dispatcher responses use bounded backpressure: a driver that stops reading stdout while writing stdin no longer deadlocks the pair.
mcp --groupsfails fast on unknown groups instead of starting a silent zero-tool server.- MCP tool surface and counts documented honestly (81 routed ops; read-only set 24;
rawjoins the destructive confirm-gated set).
Release and CI
- All GitHub Actions pinned to full commit SHAs, least-privilege job permissions,
--lockedrelease builds, npm publish behind thenpmenvironment gate, and tag-to-Cargo.toml-to-CHANGELOG verification as a release preflight (plus an offline contract test enforcing the same sync). - npm launcher: musl detection prefers
process.report(no execSync on the hot path), a failed binary spawn falls through to the next candidate, and the deprecatedteleclialias prints a warning. - Contract test gate hardened: every backticked CLI reference in the capability matrix is checked (not just the first), status cells are normalized and unknown statuses fail loudly, wildcards expand against real subcommands, group-only rows must have subcommands - the first run caught a stale matrix reference.
Changed
- Docs: every broken example and recipe fixed (
--targetto--chat,--typeto--kind, story examples carrying the required--chat, correct jq paths, the real Unix sessions path, real output API in CONTRIBUTING, MCP tool count 81, listen filter applicability contract, supervisor guidance for long-running listen/serve). - Docs: recorded deliberate non-goal - output i18n (English-only;
lang_codeaffects only the MTProto client identity). - Capability matrix: 6 stale rows corrected (TTL flags, pin
--notify, forward notify behavior,--clear-username,schedule_repeat_period,--noforwardsattribution).