Tacky is high-level library for building XMPP clients
  • Tcl 73.6%
  • C 10.5%
  • Rust 10.4%
  • Makefile 1.8%
  • Python 1.6%
  • Other 2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Paavel A. 28ab9df067
1.2.0-rc3: the README's version, figures and rig table, the changelog's section, the DOAP release, Message.unreadable's sealed dated 1.2.0-rc3 and the regenerated HANDBOOK for the tree to be tagged v1.2.0-rc3, the third release candidate of 1.2, built on 1.2.0-rc2 (fe57dd98): the catch-up peeks before it walks (the newest page drawn in one archive answer, 188 ms against 3.7 s for the newest peer row on a day's backlog of the user's mix; OMEMO rows the peek cannot open kept sealed and opened in place in order; holes and the walk's frontier in one commit; a room's walk in line behind the account's; quiet while syncing; heartbeats not archived, 1202 to 2 a day); the call tier withdraws a notice for a chat read elsewhere while it had no session and takes no edit, retraction, reaction or room history for a new message; calls_from_strangers (issue 15); a certificate exception bound to the certificate shown, with an optional sha256 to check the answer (issue 16); a message's disappearing timer inside the OMEMO 2 envelope, the negotiation still in the clear (issue 17); no link preview fetched for a send that cannot carry it (issue 18). Gate on 6125f186 (lanes again on c3ffaeb0 and on the merged review fixes 55d7df67): both lanes tests/run.tcl 7599 total, 0 failed (webrtc 7597 passed, 2 skipped; datachannel 7591 passed, 8 skipped), Rust 833 passed, wasm built, audit clean, api.py check clean; on 6125f186 test-omemo failed in both lanes (the ext's session surface counted thirteen commands, the peek's lookahead made fifteen), fixed by c3ffaeb0 = 8ba175b0; rigs three per server on 6125f186: ejabberd 26.09 338 total, 314 passed, 0 failed, 24 skipped; Prosody 13 338, 285 passed, 0 failed, 53 skipped (test_catchup_history's drop-mid-walk and resume-mid-walk and test_message's history-after-send and search-local on one Prosody rig, test_flaky_send's omemo-ack-lost-fresh-session on another, test_catchup_history's new-process-after-first-page on one ejabberd rig failed once; 18/18, 10/10, 18/18 and 18/18 rerun alone). Coverage on 55d7df67: Tcl 99.0 % procs, Rust 99.2 %, C 99.1 % lines.
2026-11-05 17:16:10 +02:00
docker The crates' rust-version is 1.91, not 1.88, at the user's word: one floor for libtacky and the app, which declares 1.91 as well, so both repositories say one thing. cargo update at 1.91 moves nothing past what 1.88 already allowed (tacky-core on icu 2.4 and idna_adapter 1.2.2, rcgen 0.14.10 and time 0.3.55 kept); vodozemac 0.11 (1.89) is now within the floor but stays at 0.10, an API change of its own, and ROBOTS.md says so. docker/linux.Dockerfile, platforms/wasm.mk, docs/building.md, audit.toml's reasons and CHANGELOG.md's Rust section say 1.91 and why. Gate in the image (emsdk 6.0.13, Rust 1.99.1): make all, tests/run.tcl -j 16 7163 total, 7153 passed, 0 failed, 10 skipped; test-rust 810 passed, 0 failed, 6 ignored; audit 10 lock files, no advisory; wasm, check-api, check-headers, check-native-neutral, test-lib and fuzz-build clean. 2026-11-02 03:12:48 +02:00
docs tags: bot and channel, selectors beside kind, for People, Bots, Groups and Channels folders. "Private" was {kind chat} and so showed bots (anotherim-js #84), and nothing told a channel from a group. FILTER_KEYS and SELECTOR_KEYS gain both; tags set takes them as booleans as it takes unread (and refuses anything else), a list from another device drops a value that is none; Matches compares the row's ChatEntry flag (absent is false) with the key after kind and before jid, so a chosen chat is still shown whatever they say and a tag of chosen chats with either is no longer those chats alone. On the wire they are <bot>1</bot> and <channel>0</channel> in the synced tags list, as unread and muted are; an older version keeps them as unknown criteria and ignores them. docs/protocol/tags-0.md documents both keys, the version and the editor's four kinds (People {kind chat bot 0}, Bots {kind chat bot 1}, Groups {kind muc channel 0}, Channels {kind muc channel 1}); the built-in tags are unchanged; the header notes that the app's tag-filter.ts mirrors Matches key for key. Tests in test_tags: set validation, the four kinds and a row without the flag, chosen chats beside the selectors, the wire round trip and a junk value from another device: 51/51. 2026-11-04 13:03:10 +02:00
embed tacky-olm bound into Tcl and wired into every build, for the Matrix plan of 2026-10-25 (the user: "let's start the Matrix plan which involves fork of mod_matrix_gw, olm/megolm rust library include" and "just go"): ext/olm/olm.c is the tackyolm extension, the package tacky::olm 0.1 with the commands the design's section 3.1 froze (account, session, group, inbound, verify, sas) and the keypair ensemble its section 3.2 added for the cross-signing keys, one command per function of tacky_olm.h, base64 as Tcl text and everything else as byte arrays, handles olmaccountN, olmsessionN, olmgroupN, olminboundN, olmsasN and olmkeypairN freed by their free or with the interpreter, results of a length only the ABI knows asked into the bound their input sets and again into the reported size on LENGTH, and errors as {TACKY tacky::olm STATUS OLM NAME} for the ABI's statuses (verify and verify_mac raising CRYPTO with "bad signature" and "bad MAC") and {TACKY tacky::olm 1 ARG} for what the glue refuses (a handle it does not have, a seed of another length, a count or index out of range, a message type other than 0 and 1). deps.mk builds the crate (TACKY_OLM_A) and _build/ext/olm/tackyolm.so (make olm), puts olm in the floor, EXT_NAMES and the load check, runs the crate in test-rust and test-rust-full and the binding in test-olm (in floor-test), and counts both in coverage-rust, coverage-c and coverage-all; the Makefile merges libtackyolm.a and libtacky_olm.a into libtacky.a and into the Android, iOS and Windows archives, whose platform files build the crate per target; embed/extensions.h calls Tackyolm_Init on every backend interpreter; the fault shim answers six tacky_olm_* calls; tools/stats.py has the olm component, its Rust test family and its crate, and README.md's generated tables are written again with them; ROBOTS.md and README.md name the crate and the package, with a decision paragraph on the crate, the 0.10 pin and the panic the fuzzer found. The browser build has no tacky::olm. The SAS header said a spent SAS still gave its public key, which the code never did: the header now says public_key is STATE too. Tested in the container: make olm test-olm (test_olm.tcl 134 checks: every command and refusal, an Olm conversation both ways with a large and an empty message, the fallback key, a room's session shared, exported at 3 and imported, a SAS between two handles and a small-order key, RFC 8032's keypair vector, handles across interpreters, and libolm's accounts, signatures, Olm transcript and Megolm session from tests/client/fixtures/matrix read back through the commands; test_olm_faults.tcl 14 under the fault shim), olm.c at 99.8 % of its lines under gcov (the one line left is the init's failure), docker/in-image.sh all with check-native-neutral, test-lib and test-tackyd-json passing against the archive with the fourth Rust staticlib, test-rust 800 passed and 0 failed over all five crates, and the full unit run with TLS_TEST_SERVER (204 suites, 6261 tests, 6260 passed, 0 failed, 1 skipped); cross-built with platforms/android.mk libtacky-android in the anotherim-js android image (aarch64-linux-android, rustc 1.90) and docker/in-image-windows.sh lib (x86_64-pc-windows-gnu, rustc 1.98), each archive linked into a probe (an Android shared object with --no-undefined, a MinGW executable) calling tacky_create, with Tackyolm_Init and the tacky_olm_* functions in it. 2026-10-25 22:23:07 +02:00
examples Comments, build files and the crates' docs no longer cite documents this tree does not have: TRANSFER.md, HTTP-TCL.md, LOCALSTORE.md, ARCHITECTURE.md (the workspace's §9.1 and §9.2), MEDIA.md, JINGLE-FT.md, LIVENESS.md, NOTIFY.md, CHATLIST.md, THREADS.md, STORAGE.md, CHAOS-FINDINGS.md, ONE-CLIENT.md, ROADMAP-another-tacky.md, docs/DEPLOY.md, docs/STABILIZE.md, docs/discovery.md, docs/storage-lock.md, reports/OMEMO_issues.md and reviews/2026-09-*, nor ROBOTS.md by section numbers it lost long ago (§2.2, §7.1, §8, §10.80 and the rest). Each pointer now names the place that exists (tacky/http/README.md, tacky-keyring/README.md, ROBOTS.md's HTTP, "The store", "The shape" and rule 2 or 6, tacky_media.h's data channel section, session/liveness.tcl, modules/file.tcl, ext/common.h's rule 1, deps.mk's and wasm.mk's own sqlite3 paragraphs) or is dropped where the sentence already says what the document did; generated outputs (dist/HANDBOOK.md, REPORT.md under COV_DIR) and the guides' pattern are left as they are. Not touched, being other branches' files: tacky/xmpp/modules/ (about 110 such citations), ext/core/keyring.c and keyring.h, ext/core/tests/test_tls.tcl, tacky/core/keyring.tcl and storage.tcl, and the schema's doc strings. Only comments changed; check-native-neutral, check-headers, check-api and test-tacky are green. 2026-10-07 20:42:28 +03:00
ext ext/omemo tests: the session ensemble has fifteen commands. The catch-up rework (8c317ac9) gave tacky::omemo::ratchet::session lookahead and prekey_lookahead, and the surface check still listed the thirteen before them, so make test-omemo failed on develop since 3a2c7016; the list names the two. 2026-11-05 16:25:29 +02:00
platforms platforms/wasm.mk keeps every single-byte encoding table in the browser's staged Tcl library and drops only the double-byte, multi-byte and escape ones, the CJK sets: c32c3dae kept ascii and iso8859-1 alone, but linkmeta's decode_page takes a page's <meta charset> when Tcl knows it, so a link preview of a cp1251, koi8-r, koi8-u or iso8859-2 page would have decoded as UTF-8 with replacement characters on the browser build alone. The tables are told apart by the type letter on each file's second line (S), the build fails if ascii, iso8859-1, cp1251, cp1252, koi8-r, koi8-u, iso8859-2 or iso8859-5 is missing, and a page in a dropped CJK charset decodes as UTF-8 as one in any charset Tcl lacks does. make wasm's fs/ is 7,243,752 bytes in 336 files (10,334,304 in 1,088 before c32c3dae, 7,177,124 after it; the 63 single-byte tables are 68,812 bytes) and gzip -9 of the tree 1,836,554 bytes (2,582,577 before). Under Tcl 9.0.3 with encoding dirs on the staged tree alone, linkmeta::decode_page reads a cp1251 and a koi8-r page's Cyrillic title, gb2312 is not among the encoding names, and the clock, encoding and boot checks of c32c3dae pass again. 2026-11-02 08:44:40 +02:00
schema 1.2.0-rc3: the README's version, figures and rig table, the changelog's section, the DOAP release, Message.unreadable's sealed dated 1.2.0-rc3 and the regenerated HANDBOOK for the tree to be tagged v1.2.0-rc3, the third release candidate of 1.2, built on 1.2.0-rc2 (fe57dd98): the catch-up peeks before it walks (the newest page drawn in one archive answer, 188 ms against 3.7 s for the newest peer row on a day's backlog of the user's mix; OMEMO rows the peek cannot open kept sealed and opened in place in order; holes and the walk's frontier in one commit; a room's walk in line behind the account's; quiet while syncing; heartbeats not archived, 1202 to 2 a day); the call tier withdraws a notice for a chat read elsewhere while it had no session and takes no edit, retraction, reaction or room history for a new message; calls_from_strangers (issue 15); a certificate exception bound to the certificate shown, with an optional sha256 to check the answer (issue 16); a message's disappearing timer inside the OMEMO 2 envelope, the negotiation still in the clear (issue 17); no link preview fetched for a send that cannot carry it (issue 18). Gate on 6125f186 (lanes again on c3ffaeb0 and on the merged review fixes 55d7df67): both lanes tests/run.tcl 7599 total, 0 failed (webrtc 7597 passed, 2 skipped; datachannel 7591 passed, 8 skipped), Rust 833 passed, wasm built, audit clean, api.py check clean; on 6125f186 test-omemo failed in both lanes (the ext's session surface counted thirteen commands, the peek's lookahead made fifteen), fixed by c3ffaeb0 = 8ba175b0; rigs three per server on 6125f186: ejabberd 26.09 338 total, 314 passed, 0 failed, 24 skipped; Prosody 13 338, 285 passed, 0 failed, 53 skipped (test_catchup_history's drop-mid-walk and resume-mid-walk and test_message's history-after-send and search-local on one Prosody rig, test_flaky_send's omemo-ack-lost-fresh-session on another, test_catchup_history's new-process-after-first-page on one ejabberd rig failed once; 18/18, 10/10, 18/18 and 18/18 rerun alone). Coverage on 55d7df67: Tcl 99.0 % procs, Rust 99.2 %, C 99.1 % lines. 2026-11-05 17:16:10 +02:00
tacky certificates: ignore_certificate_errors checks the certificate the user answered. The exception was bound to the last <CertificateUntrusted> emitted for the host, so a dialog answered after a man in the middle forced a reconnect with another certificate bound it to that one with no error; and a certificate refused, then another, then the first again was not reported the third time, so the host showed the first while the exception would take the second (review M1 of issue 16). account and setting ignore_certificate_errors take an optional -sha256, the event's in any spelling: it must be the last refusal shown for the host (certtrust.tcl shown), else an error naming both and nothing stored; left out, the last shown is taken as before. A refusal seen before is emitted again when another was shown for its host since (certtrust.tcl showing), for an account, the installation and a registration, so the last event is the one an answer binds to. untrust_certificate (and a registration's) and account removal forget the refusals shown for the host (review L3). Schema: the sha256 argument of both methods, since 1.2.0-rc3. Cases in test_certificates: given and matching binds, an older one is refused for an account, a registration and the installation, the A, X, A run reports A again and binds to it, and untrust needs a new refusal. 2026-11-05 16:57:36 +02:00
tacky-core stream core, tls: an exception names the one certificate it accepts. A CertOverride's exception was for any certificate on its host until ignore_until, so a certificate an attacker in the middle presented while the user's week ran was accepted with no report, and the stream went on to SASL (issue 16). The exception now carries ignore_sha256, the leaf the user was shown: the verifier accepts that leaf and no other until the end, an exception that names none accepts nothing, and another certificate on the host is refused and reported in the rejection slot as any refusal is. tacky_trust_override_t gains ignore_sha256_hex after ignore_until (56 bytes), connect's overrides take ignore_sha256, tacky::core::net::tls takes -ignore-sha256, and tacky::http hands an override's ignore_sha256 to its TLS plug; the session checks it as it checks a pin. Tests: certtrust.rs, stream_tls.rs (a relay presenting another certificate under an exception is refused with its report), tls.rs, cov_view.rs, and the tacky::core stream and tls suites. 2026-11-05 16:11:36 +02:00
tacky-crypto The crates' rust-version is 1.91, not 1.88, at the user's word: one floor for libtacky and the app, which declares 1.91 as well, so both repositories say one thing. cargo update at 1.91 moves nothing past what 1.88 already allowed (tacky-core on icu 2.4 and idna_adapter 1.2.2, rcgen 0.14.10 and time 0.3.55 kept); vodozemac 0.11 (1.89) is now within the floor but stays at 0.10, an API change of its own, and ROBOTS.md says so. docker/linux.Dockerfile, platforms/wasm.mk, docs/building.md, audit.toml's reasons and CHANGELOG.md's Rust section say 1.91 and why. Gate in the image (emsdk 6.0.13, Rust 1.99.1): make all, tests/run.tcl -j 16 7163 total, 7153 passed, 0 failed, 10 skipped; test-rust 810 passed, 0 failed, 6 ignored; audit 10 lock files, no advisory; wasm, check-api, check-headers, check-native-neutral, test-lib and fuzz-build clean. 2026-11-02 03:12:48 +02:00
tacky-keyring tests: cheap Argon2 for the Tcl suites 2026-11-04 08:49:17 -08:00
tacky-media calls: set_video -on 0 -source camera lets the screen go 2026-11-04 04:41:59 +02:00
tacky-olm tacky-olm: the vodozemac paragraph of Cargo.toml and README.md says the floor as it is, 1.91 since 1.2.0 (the crate's own rust-version, ROBOTS.md's words), not Debian's 1.85 with a promise to move to 0.11 with the floor: 0.11 (Rust 1.89) is within the floor now and the pin stays at 0.10.0 because the move is a dependency change of its own for nothing this crate calls, and 0.11.1 keeps the Ed25519 from_base64 panic on 44 characters that tacky_olm_verify works around. Found in the 1.2.0-beta1 native review (L4). Documentation only, no test. 2026-11-02 06:17:51 +02:00
tacky-omemo tacky-omemo: a session says what unwrapping a message would cost before any key is derived. Session::lookahead reads the header alone: how many message keys the unwrap would derive ahead of the message and keep for the ones before it (the chain's counters up to its own, and for a ratchet step the rest of the chain it leaves), whether those kept fit MAX_SKIPPED_KEYS (past it the oldest go, which are the keys of the messages still to come in order), whether the message is a ratchet step, and whether a pre-key message is a key exchange with a new base key (accepted anew, the state before the previous one let go); Gap, State and Encoding as the unwrap would say. lookahead_fresh judges a key exchange there is no session for. For a host reading an archive newest first, which decides from it whether to unwrap now, out of order, or to wait for the message's turn. The C ABI (tacky_omemo_session_lookahead, tacky_omemo_prekey_message_lookahead_profile, tacky_omemo_lookahead_t) and the Tcl commands session lookahead and session prekey_lookahead follow, with unit and FFI tests. 2026-11-05 00:25:16 +02:00
tests certificates: ignore_certificate_errors checks the certificate the user answered. The exception was bound to the last <CertificateUntrusted> emitted for the host, so a dialog answered after a man in the middle forced a reconnect with another certificate bound it to that one with no error; and a certificate refused, then another, then the first again was not reported the third time, so the host showed the first while the exception would take the second (review M1 of issue 16). account and setting ignore_certificate_errors take an optional -sha256, the event's in any spelling: it must be the last refusal shown for the host (certtrust.tcl shown), else an error naming both and nothing stored; left out, the last shown is taken as before. A refusal seen before is emitted again when another was shown for its host since (certtrust.tcl showing), for an account, the installation and a registration, so the last event is the one an answer binds to. untrust_certificate (and a registration's) and account removal forget the refusals shown for the host (review L3). Schema: the sha256 argument of both methods, since 1.2.0-rc3. Cases in test_certificates: given and matching binds, an older one is refused for an account, a registration and the installation, the A, X, A run reports A again and binds to it, and untrust needs a new refusal. 2026-11-05 16:57:36 +02:00
tools HANDBOOK.md: the handbook is a committed file at the root, linked from the README, as the user asked (2026-11-02: "update README, recount all lines etc, and please make a handbook and link to it in readme"): api.py handbook (tools/api/docs.py's default path) and make handbook write HANDBOOK.md instead of the build output dist/HANDBOOK.md, make docs refreshes it, the release tarballs take it from the root (release.mk), and ROBOTS.md, docs/hacking.md and api.py's notes say so; generated from the schema and docs/, 72 modules, 638 methods, 204 events. The README links it where the API is introduced and in the commands, names the 1.2.0-beta1 additions in its features list from CHANGELOG.md and docs/features.md (caps 2.0, Happy Eyeballs, IAP, XEP-0450, OTR announced, direct MTProto chats, Matrix E2EE through a gateway, room hats, mention notifications, invitation links and spaces, Fluux polls, pubsub, microblogging and stories, WebXDC, meetings and payments as work in progress), recounts the status table from docs/features.md (MOSTLY 171, WIP 27, TODO 9, DONE 0, one NOT SUPPORTED) and its numbers with tools/stats.py --readme (126,337 lines of code, 169,646 of tests, 9,835 cases; coverage as recorded from make coverage-all at 6a6b49de); stats --check, api.py check and doap.py --check clean. 2026-11-02 08:49:54 +02:00
.gitignore The XMPP half of the Tcl tree moves under lib/xmpp and the tacky::xmpp packages, the first step of the rename pass. lib/taco/session becomes lib/xmpp/session with package and namespace tacky::xmpp::session (::tacky::session:: renamed everywhere as one token); lib/taco (taco.tcl, redact.tcl, modules/) becomes lib/xmpp/client (client.tcl, redact.tcl, modules/) with the package tacky::xmpp::client; the package xmpp becomes tacky::xmpp; tests/taco and tests/taco_integration become tests/client and tests/integration, all by git mv. lib/taco/pkgIndex.tcl goes and lib/xmpp/pkgIndex.tcl registers tacky::xmpp and its session, client and component subpackages, since pkgUnknown looks only one level below lib/. The internal names stay: the taco_* snit types, :🌮:, procs, State keys and every package version. Everything that named a moved path or package follows in this commit: tests/run.tcl's suite directories and auto_path, test_session.tcl's auto_path and test_session_scram.tcl's source path, deps.mk's load check list and comments, .gitignore's tmp directory, tools/api/tcl.py's module paths, tools/stats.py's parts and test-to-part map, the schema's per-module tcl paths and doc strings (methods.tcl came out unchanged from make gen-api), README.md, ROBOTS.md (with a decision paragraph naming the old names), docs/, the READMEs of lib/, ext/, examples/ and tests/servers, and comments across lib/ and tests/; notes about what a file was called before 2026-10-07 and the paths of the legacy repositories keep their old names. Checked in the container: make test-tacky (tests/run.tcl 4551 total, 4550 passed, 0 failed, 1 skipped, as before; the load check sees tacky::xmpp, tacky::xmpp::client and tackywire; 22 integration suites load), test-tackykeyring and test-tackycore (the tests/tcl suites), test-tk-example, test-build (24 passed) and check-api. 2026-10-07 12:49:27 +03:00
.gitmodules native/ dissolves: tacky-core, tacky-crypto, tacky-keyring, tacky-media and tacky-ratchet move to the repository root with git mv, the libdatachannel submodule's path following in .gitmodules, so the parts a host builds sit beside ext/, lib/ and embed/ instead of one level down. Every path that named native/ follows: the root Cargo.toml's members, deps.mk, the Makefile, coverage.mk, platforms/*.mk, the Windows image's script and Dockerfile, tools/stats.py and check-core-header.py, test_build.sh, the bindings' and Tcl modules' comments, the crates' own READMEs and the docs, with HANDBOOK.md regenerated from them. check-native-neutral keeps its name and now scans the tacky-*/ directories by glob, pruning the submodule as before; its rule reads that nothing under a tacky-*/ directory knows Tcl. Checked in the container: test-rust, check-headers, check-native-neutral, check-api and test-build (24 of 24) pass. 2026-10-07 09:15:32 +03:00
audit.toml The crates' rust-version is 1.91, not 1.88, at the user's word: one floor for libtacky and the app, which declares 1.91 as well, so both repositories say one thing. cargo update at 1.91 moves nothing past what 1.88 already allowed (tacky-core on icu 2.4 and idna_adapter 1.2.2, rcgen 0.14.10 and time 0.3.55 kept); vodozemac 0.11 (1.89) is now within the floor but stays at 0.10, an API change of its own, and ROBOTS.md says so. docker/linux.Dockerfile, platforms/wasm.mk, docs/building.md, audit.toml's reasons and CHANGELOG.md's Rust section say 1.91 and why. Gate in the image (emsdk 6.0.13, Rust 1.99.1): make all, tests/run.tcl -j 16 7163 total, 7153 passed, 0 failed, 10 skipped; test-rust 810 passed, 0 failed, 6 ignored; audit 10 lock files, no advisory; wasm, check-api, check-headers, check-native-neutral, test-lib and fuzz-build clean. 2026-11-02 03:12:48 +02:00
CHANGELOG.md 1.2.0-rc3: the README's version, figures and rig table, the changelog's section, the DOAP release, Message.unreadable's sealed dated 1.2.0-rc3 and the regenerated HANDBOOK for the tree to be tagged v1.2.0-rc3, the third release candidate of 1.2, built on 1.2.0-rc2 (fe57dd98): the catch-up peeks before it walks (the newest page drawn in one archive answer, 188 ms against 3.7 s for the newest peer row on a day's backlog of the user's mix; OMEMO rows the peek cannot open kept sealed and opened in place in order; holes and the walk's frontier in one commit; a room's walk in line behind the account's; quiet while syncing; heartbeats not archived, 1202 to 2 a day); the call tier withdraws a notice for a chat read elsewhere while it had no session and takes no edit, retraction, reaction or room history for a new message; calls_from_strangers (issue 15); a certificate exception bound to the certificate shown, with an optional sha256 to check the answer (issue 16); a message's disappearing timer inside the OMEMO 2 envelope, the negotiation still in the clear (issue 17); no link preview fetched for a send that cannot carry it (issue 18). Gate on 6125f186 (lanes again on c3ffaeb0 and on the merged review fixes 55d7df67): both lanes tests/run.tcl 7599 total, 0 failed (webrtc 7597 passed, 2 skipped; datachannel 7591 passed, 8 skipped), Rust 833 passed, wasm built, audit clean, api.py check clean; on 6125f186 test-omemo failed in both lanes (the ext's session surface counted thirteen commands, the peek's lookahead made fifteen), fixed by c3ffaeb0 = 8ba175b0; rigs three per server on 6125f186: ejabberd 26.09 338 total, 314 passed, 0 failed, 24 skipped; Prosody 13 338, 285 passed, 0 failed, 53 skipped (test_catchup_history's drop-mid-walk and resume-mid-walk and test_message's history-after-send and search-local on one Prosody rig, test_flaky_send's omemo-ack-lost-fresh-session on another, test_catchup_history's new-process-after-first-page on one ejabberd rig failed once; 18/18, 10/10, 18/18 and 18/18 rerun alone). Coverage on 55d7df67: Tcl 99.0 % procs, Rust 99.2 %, C 99.1 % lines. 2026-11-05 17:16:10 +02:00
coverage.mk tacky-olm bound into Tcl and wired into every build, for the Matrix plan of 2026-10-25 (the user: "let's start the Matrix plan which involves fork of mod_matrix_gw, olm/megolm rust library include" and "just go"): ext/olm/olm.c is the tackyolm extension, the package tacky::olm 0.1 with the commands the design's section 3.1 froze (account, session, group, inbound, verify, sas) and the keypair ensemble its section 3.2 added for the cross-signing keys, one command per function of tacky_olm.h, base64 as Tcl text and everything else as byte arrays, handles olmaccountN, olmsessionN, olmgroupN, olminboundN, olmsasN and olmkeypairN freed by their free or with the interpreter, results of a length only the ABI knows asked into the bound their input sets and again into the reported size on LENGTH, and errors as {TACKY tacky::olm STATUS OLM NAME} for the ABI's statuses (verify and verify_mac raising CRYPTO with "bad signature" and "bad MAC") and {TACKY tacky::olm 1 ARG} for what the glue refuses (a handle it does not have, a seed of another length, a count or index out of range, a message type other than 0 and 1). deps.mk builds the crate (TACKY_OLM_A) and _build/ext/olm/tackyolm.so (make olm), puts olm in the floor, EXT_NAMES and the load check, runs the crate in test-rust and test-rust-full and the binding in test-olm (in floor-test), and counts both in coverage-rust, coverage-c and coverage-all; the Makefile merges libtackyolm.a and libtacky_olm.a into libtacky.a and into the Android, iOS and Windows archives, whose platform files build the crate per target; embed/extensions.h calls Tackyolm_Init on every backend interpreter; the fault shim answers six tacky_olm_* calls; tools/stats.py has the olm component, its Rust test family and its crate, and README.md's generated tables are written again with them; ROBOTS.md and README.md name the crate and the package, with a decision paragraph on the crate, the 0.10 pin and the panic the fuzzer found. The browser build has no tacky::olm. The SAS header said a spent SAS still gave its public key, which the code never did: the header now says public_key is STATE too. Tested in the container: make olm test-olm (test_olm.tcl 134 checks: every command and refusal, an Olm conversation both ways with a large and an empty message, the fallback key, a room's session shared, exported at 3 and imported, a SAS between two handles and a small-order key, RFC 8032's keypair vector, handles across interpreters, and libolm's accounts, signatures, Olm transcript and Megolm session from tests/client/fixtures/matrix read back through the commands; test_olm_faults.tcl 14 under the fault shim), olm.c at 99.8 % of its lines under gcov (the one line left is the init's failure), docker/in-image.sh all with check-native-neutral, test-lib and test-tackyd-json passing against the archive with the fourth Rust staticlib, test-rust 800 passed and 0 failed over all five crates, and the full unit run with TLS_TEST_SERVER (204 suites, 6261 tests, 6260 passed, 0 failed, 1 skipped); cross-built with platforms/android.mk libtacky-android in the anotherim-js android image (aarch64-linux-android, rustc 1.90) and docker/in-image-windows.sh lib (x86_64-pc-windows-gnu, rustc 1.98), each archive linked into a probe (an Android shared object with --no-undefined, a MinGW executable) calling tacky_create, with Tackyolm_Init and the tacky_olm_* functions in it. 2026-10-25 22:23:07 +02:00
deps.mk tests: cheap Argon2 for the Tcl suites 2026-11-04 08:49:17 -08:00
fuzz.mk Merge feat/matrix-12 (L2, d07d98fc) into integ/1.2: Matrix end-to-end encryption through a gateway on the device (tacky-olm over vodozemac bound as tacky::olm, tacky::xmpp::matrix and the module matrix: Megolm DMs and rooms, key requests, cross-signing, SAS), files as EncryptedFile, reactions, 1:1 calls over the Matrix VoIP events (WIP). Conflicts kept both sides: the wire table's chat_entry gains matrix beside integ's otr/mtproto-direct booleans (one row; the merge had left two), the mtproto-direct and matrix-e2e rows in docs/protocol/README.md (matrix-e2e since v1.2.0, the release that first ships it), the software_info_public and matrix_e2e_enabled setting hooks and texts, deps.mk's coverage notes (the rustup llvm-tools with olm among the C extensions), test_wire_surface's count (494: 481 + the lane's 13) and CHANGELOG.md's 1.2.0-beta1 section. Gate in the container: make all, tests/run.tcl -j 32 7159 total, 7149 passed, 0 failed, 10 skipped; test-rust 810 passed, 0 failed, 6 ignored; test-olm all ok; api.py check, check-headers, check-native-neutral and wasm clean; integration/test_matrix_gateway.tcl on ejabberd 26.09 with omemo-bot 8 of 8. 2026-11-02 02:53:43 +02:00
HANDBOOK.md 1.2.0-rc3: the README's version, figures and rig table, the changelog's section, the DOAP release, Message.unreadable's sealed dated 1.2.0-rc3 and the regenerated HANDBOOK for the tree to be tagged v1.2.0-rc3, the third release candidate of 1.2, built on 1.2.0-rc2 (fe57dd98): the catch-up peeks before it walks (the newest page drawn in one archive answer, 188 ms against 3.7 s for the newest peer row on a day's backlog of the user's mix; OMEMO rows the peek cannot open kept sealed and opened in place in order; holes and the walk's frontier in one commit; a room's walk in line behind the account's; quiet while syncing; heartbeats not archived, 1202 to 2 a day); the call tier withdraws a notice for a chat read elsewhere while it had no session and takes no edit, retraction, reaction or room history for a new message; calls_from_strangers (issue 15); a certificate exception bound to the certificate shown, with an optional sha256 to check the answer (issue 16); a message's disappearing timer inside the OMEMO 2 envelope, the negotiation still in the clear (issue 17); no link preview fetched for a send that cannot carry it (issue 18). Gate on 6125f186 (lanes again on c3ffaeb0 and on the merged review fixes 55d7df67): both lanes tests/run.tcl 7599 total, 0 failed (webrtc 7597 passed, 2 skipped; datachannel 7591 passed, 8 skipped), Rust 833 passed, wasm built, audit clean, api.py check clean; on 6125f186 test-omemo failed in both lanes (the ext's session surface counted thirteen commands, the peek's lookahead made fifteen), fixed by c3ffaeb0 = 8ba175b0; rigs three per server on 6125f186: ejabberd 26.09 338 total, 314 passed, 0 failed, 24 skipped; Prosody 13 338, 285 passed, 0 failed, 53 skipped (test_catchup_history's drop-mid-walk and resume-mid-walk and test_message's history-after-send and search-local on one Prosody rig, test_flaky_send's omemo-ack-lost-fresh-session on another, test_catchup_history's new-process-after-first-page on one ejabberd rig failed once; 18/18, 10/10, 18/18 and 18/18 rerun alone). Coverage on 55d7df67: Tcl 99.0 % procs, Rust 99.2 %, C 99.1 % lines. 2026-11-05 17:16:10 +02:00
LICENSE libtacky v0.9.9-alpha3: the whole tree as of this release, in one commit (the user chose to fold the history once, at this tag, so the forge's commit list is readable; from here on every branch lands as one squashed commit). The full history up to this point, 1,000-odd commits with the v0.9.9-alpha1 and v0.9.9-alpha2 tags, is kept unchanged on the branch archive/pre-alpha3 (43b06d4), whose tree is byte-identical to this one. 2026-10-02 02:46:29 +03:00
Makefile Merge feat/matrix-e2e (with feat/matrix-olm) onto develop 1.1.20 for libtacky 1.2.0-beta1 (the user, 2026-11-02: "Add everything we will need for Matrix except MatrixRTC" and "OLM/MegOLM"): tacky-olm bound as tacky::olm, the protocol package tacky::xmpp::matrix and the chat module matrix come in 274 commits late. Conflicts resolved by hand: RetrySend had moved, so the Matrix resend goes into develop's RetrySend after the private-chat branch; Classify's sealed test moved above the XEP-0466 timer read, so a Matrix event counts as sealed there and is opened before the OMEMO step with the wire node kept for the timer; the module list, the method allow-list, the wire types (ChatEntry.matrix, matrix_device, matrix_member, matrix_sas), the schema (ChatEntry.matrix, Message.encryption matrix, unreadable matrix_key, SasEmoji beside ScheduleSupport, the settings matrix_e2e_enabled and matrix_encrypt_dms), ROBOTS.md, docs/features.md and the README's text take both sides; everything the branch said was "since 1.1.3" never shipped and now says since 1.2.0. The README's stats are left to the release. 2026-11-02 02:17:23 +02:00
README.md 1.2.0-rc3: the README's version, figures and rig table, the changelog's section, the DOAP release, Message.unreadable's sealed dated 1.2.0-rc3 and the regenerated HANDBOOK for the tree to be tagged v1.2.0-rc3, the third release candidate of 1.2, built on 1.2.0-rc2 (fe57dd98): the catch-up peeks before it walks (the newest page drawn in one archive answer, 188 ms against 3.7 s for the newest peer row on a day's backlog of the user's mix; OMEMO rows the peek cannot open kept sealed and opened in place in order; holes and the walk's frontier in one commit; a room's walk in line behind the account's; quiet while syncing; heartbeats not archived, 1202 to 2 a day); the call tier withdraws a notice for a chat read elsewhere while it had no session and takes no edit, retraction, reaction or room history for a new message; calls_from_strangers (issue 15); a certificate exception bound to the certificate shown, with an optional sha256 to check the answer (issue 16); a message's disappearing timer inside the OMEMO 2 envelope, the negotiation still in the clear (issue 17); no link preview fetched for a send that cannot carry it (issue 18). Gate on 6125f186 (lanes again on c3ffaeb0 and on the merged review fixes 55d7df67): both lanes tests/run.tcl 7599 total, 0 failed (webrtc 7597 passed, 2 skipped; datachannel 7591 passed, 8 skipped), Rust 833 passed, wasm built, audit clean, api.py check clean; on 6125f186 test-omemo failed in both lanes (the ext's session surface counted thirteen commands, the peek's lookahead made fifteen), fixed by c3ffaeb0 = 8ba175b0; rigs three per server on 6125f186: ejabberd 26.09 338 total, 314 passed, 0 failed, 24 skipped; Prosody 13 338, 285 passed, 0 failed, 53 skipped (test_catchup_history's drop-mid-walk and resume-mid-walk and test_message's history-after-send and search-local on one Prosody rig, test_flaky_send's omemo-ack-lost-fresh-session on another, test_catchup_history's new-process-after-first-page on one ejabberd rig failed once; 18/18, 10/10, 18/18 and 18/18 rerun alone). Coverage on 55d7df67: Tcl 99.0 % procs, Rust 99.2 %, C 99.1 % lines. 2026-11-05 17:16:10 +02:00
release.mk HANDBOOK.md: the handbook is a committed file at the root, linked from the README, as the user asked (2026-11-02: "update README, recount all lines etc, and please make a handbook and link to it in readme"): api.py handbook (tools/api/docs.py's default path) and make handbook write HANDBOOK.md instead of the build output dist/HANDBOOK.md, make docs refreshes it, the release tarballs take it from the root (release.mk), and ROBOTS.md, docs/hacking.md and api.py's notes say so; generated from the schema and docs/, 72 modules, 638 methods, 204 events. The README links it where the API is introduced and in the commands, names the 1.2.0-beta1 additions in its features list from CHANGELOG.md and docs/features.md (caps 2.0, Happy Eyeballs, IAP, XEP-0450, OTR announced, direct MTProto chats, Matrix E2EE through a gateway, room hats, mention notifications, invitation links and spaces, Fluux polls, pubsub, microblogging and stories, WebXDC, meetings and payments as work in progress), recounts the status table from docs/features.md (MOSTLY 171, WIP 27, TODO 9, DONE 0, one NOT SUPPORTED) and its numbers with tools/stats.py --readme (126,337 lines of code, 169,646 of tests, 9,835 cases; coverage as recorded from make coverage-all at 6a6b49de); stats --check, api.py check and doap.py --check clean. 2026-11-02 08:49:54 +02:00
ROBOTS.md omemo: OmemoRoomStatus unreachable_sent (since 1.2.0-rc2) says whether the send unreachable names went to the other members or was not sent. Since 1.1.19 (issue 12) unreachable lists both the members a send left out while the others got it and the members who stopped a send that reached no one else, and only <MembersUnreachable> -sent told them apart; a host reading room_status or a later <RoomStatus> could not, so the app's room banner says "Not sent: it could not be encrypted for ..." above a message that did go out. The room entry keeps the last send's sent beside unreachable (RoomLeftOut sets it, a send reaching everyone clears it), RoomStatus reports it, true only while unreachable names someone; wire.tcl types it bool; the schema and ROBOTS.md say so. Tests: test_omemo_muc.tcl's strict-trust, no-member-reachable and everyone-reached cases read it (1, 0, 0). 2026-11-04 19:39:38 +02:00
tcl.mk Merge feat/matrix-e2e (with feat/matrix-olm) onto develop 1.1.20 for libtacky 1.2.0-beta1 (the user, 2026-11-02: "Add everything we will need for Matrix except MatrixRTC" and "OLM/MegOLM"): tacky-olm bound as tacky::olm, the protocol package tacky::xmpp::matrix and the chat module matrix come in 274 commits late. Conflicts resolved by hand: RetrySend had moved, so the Matrix resend goes into develop's RetrySend after the private-chat branch; Classify's sealed test moved above the XEP-0466 timer read, so a Matrix event counts as sealed there and is opened before the OMEMO step with the wire node kept for the timer; the module list, the method allow-list, the wire types (ChatEntry.matrix, matrix_device, matrix_member, matrix_sas), the schema (ChatEntry.matrix, Message.encryption matrix, unreadable matrix_key, SasEmoji beside ScheduleSupport, the settings matrix_e2e_enabled and matrix_encrypt_dms), ROBOTS.md, docs/features.md and the README's text take both sides; everything the branch said was "since 1.1.3" never shipped and now says since 1.2.0. The README's stats are left to the release. 2026-11-02 02:17:23 +02:00

libtacky

An XMPP client library in Tcl 9, Rust and C. It holds the protocol, the local store, end-to-end encryption, calls and file transfer. Apps talk to it through one JSON API. Tcl programs and gateways load its packages directly.

Version 1.2.0-rc3. Licence GPL-3.0.

What you get

  • dist/libtacky.a: the whole library, Tcl included, behind embed/tacky.h.
  • dist/tackyd-json: the same library over stdin and stdout.
  • A wasm build for a browser worker (make wasm).
  • Builds for Linux, Windows, Android and iOS (platforms/).
  • Tcl packages under tacky::*: session, client, component, HTTP, and the OTR, MTProto and Matrix protocol layers.
  • dist/tackyd: runs a Tcl program with no Tcl installed.
  • Native crates with C ABIs: tacky-core, tacky-crypto, tacky-keyring, tacky-omemo, tacky-olm, tacky-media.

The API is described once, in schema/tacky.schema.json: 72 modules, 638 methods, 204 events, 198 types. HANDBOOK.md documents all of it, with the Tcl reference and the guides under docs/. The handbook (make handbook) and the host bindings are generated from the schema.

Features

  • One SQLCipher store per account, keys in a sealed keyring
  • A message is acknowledged only after it is stored
  • SASL2, Bind 2, FAST, SCRAM with channel binding
  • Stream management with resume, CSI, reconnects
  • Entity Capabilities 2.0, Happy Eyeballs, authentication pipelining
  • TLS through rustls, certificate pins, no plaintext fallback
  • STARTTLS, direct TLS and WebSocket
  • SOCKS5 proxy, Tor and I2P per account
  • OMEMO 0.3 and OMEMO 2 under one identity
  • OMEMO automatic trust management (XEP-0450)
  • OTR v3 secret chats with SMP, announced to the contact's devices
  • Telegram secret chats through a gateway, and MTProto secret chats directly between two XMPP devices (experimental)
  • Matrix end-to-end encryption (Olm, Megolm, SAS verification) through a gateway
  • Group chats (MUC), bookmarks, archive (MAM) with catch-up
  • Room hats, mention notifications, invitation links, spaces
  • Replies, reactions, corrections, retractions, mentions
  • Polls (Fluux's urn:fluux:poll:0), stickers, silent and scheduled messages
  • Publish-subscribe on any service, microblogging, stories
  • WebXDC apps, online meetings, payment requests (work in progress)
  • HTTP upload and Jingle file transfer, encrypted
  • Audio and video calls, group calls (Muji)
  • XEP-0114 components for gateways and bots

docs/features.md has one row per specification with its status. Our own extensions (urn:tacky:*) are specified under docs/protocol/.

status meaning specifications
🔵 MOSTLY implemented and tested, not checked by hand against other software 171
🟡 WIP partly implemented or partly tested 27
🔴 TODO not implemented, at most reserved in the API 9
🟢 DONE checked by hand; set by a maintainer 0
⛔ NOT SUPPORTED deliberately not implemented 1

How it fits together

 app (libtacky.a), tackyd-json, or a browser worker (wasm)
   │  JSON  ["module","method",{args},token] →   ["result"|"error",token,…] ←
   ▼
 embed/tacky.c      one backend thread, one Tcl interpreter
   ▼
 tacky/core/wire    dispatch through the schema's allow-list
   ▼
 tacky/xmpp/client  one module per area (account, roster, message, muc, …)
   ├── tacky/xmpp/session   the XMPP session, in Tcl
   │     └── tacky::core    tacky-core (Rust): DNS, connect, TLS, XML
   ├── tacky/core/storage   SQLCipher store per account
   ├── tacky::core::keyring tacky-keyring (Rust): the key vault
   ├── tacky::omemo         tacky-omemo (Rust): the OMEMO ratchet
   ├── tacky::olm           tacky-olm (Rust, vodozemac): Olm and Megolm
   ├── tacky/http           HTTP/1.1 in Tcl
   └── tacky::media         calls: webrtc | datachannel | host | remote

 tacky/xmpp/component      an XEP-0114 component, no client loaded
  • Rust reads untrusted bytes and holds keys: the stream, TLS, parsers, crypto, OMEMO.
  • C binds each crate to Tcl, and runs media (libwebrtc or libdatachannel).
  • Tcl is everything that means something: the session, the client, the store, HTTP.

Calls use one native media backend per build, chosen by MEDIA_BACKEND. ROBOTS.md has the full layout and the reasons behind it.

component language what it is code tests coverage
core Tcl tacky/core: the wire API dispatch, the per-account store, the keyring layer 2,458 1,779 98.9 % procs
C ext/core: the Tcl binding of tacky-core and the keyring; the SQLCipher crypto provider 3,914 3,500 99.3 % lines
Rust tacky-core: DNS, TCP, TLS (rustls), WebSocket, the XML stream, the C ABI; tacky-keyring: the key vault 12,962 7,887 98.9 % lines
crypto C ext/crypto: the tacky::crypto binding 1,393 719 99.7 % lines
Rust tacky-crypto: hashes, HMAC, HKDF, PBKDF2, AES, X25519 with a C ABI 2,543 2,933 99.9 % lines
http Tcl tacky/http: HTTP/1.1 client for uploads and downloads 1,068 2,199 100.0 % procs
media Tcl tacky/media: the call media contract and its backends 1,009 1,906 97.8 % procs
C ext/media: the binding; tacky-media: the media C API over libdatachannel, with audio (Opus) and video (VP8) engines 9,949 10,538 --
C++ tacky-media/webrtc: the same C API over libwebrtc 4,925 323 --
olm C ext/olm: the tacky::olm binding of Olm and Megolm; Matrix above it is the client's 1,042 375 99.8 % lines
Rust tacky-olm: Olm, Megolm, the SAS and Ed25519 keypairs for Matrix's end-to-end encryption over vodozemac, with a C ABI 827 1,022 100.0 % lines
omemo C ext/omemo: the tacky::omemo binding 979 398 98.6 % lines
Rust tacky-omemo: X3DH, Double Ratchet, XEdDSA; OMEMO 0.3 and OMEMO 2 2,080 4,045 99.8 % lines
xmpp Tcl tacky/xmpp: the session, the client modules, the XEP-0114 component, the in-process test driver 85,135 139,983 99.0 % procs
everything else C, Tcl, Python, sh, make, CMake embed/, examples/, tools/, test runner, build files; not in the total 10,628 5,457 --
total (without everything else) Tcl 89,670 · C/C++ 22,202 · Rust 18,412 130,284 177,607 Tcl 99.0 % procs · Rust 99.2 % lines · C 99.1 % lines

code: lines of code, no blanks, comments or generated files. tests: lines of test code for that part. The total leaves out everything else. coverage: Tcl counts procedures called, Rust and C count lines run, from the make coverage-all run on 2026-11-05 at commit 55d7df67.

The media C and C++ have no coverage figure: the coverage run uses the default webrtc build, and libwebrtc is not measured.

Status and tests

The API is stable. A change that breaks a host comes with a new major version.

Unit suites run against in-process peers: a fake server with real SCRAM, TLS and WebSocket, a stream relay, fault shims for the C bindings, and reference vectors for the ratchet. Integration suites (tests/integration) run against a real server in a container, with OMEMO echo bots beside it.

Integration results, measured on 1.2.0-rc3 (2026-11-05, at commit 6125f186), three rigs per server, the six at once:

server passed failed skipped
Prosody 13 285 0 53
ejabberd 26.09 314 0 24
MongooseIM 6.10.0 160 0 49

MongooseIM's row is from v1.1 (2026-10-23); it was not rerun since. Skips are features a server or its rig lacks.

Nothing has been checked by hand against public servers or other clients yet. That is why no feature is DONE.

130,284 lines of code, 177,607 lines of tests (16,648 of them integration suites), 10,133 test cases. Coverage: Tcl 99.0 % procs · Rust 99.2 % lines · C 99.1 % lines (make coverage-all on 2026-11-05 at commit 55d7df67).

suite family files cases
Tacky unit suites (tests/client, tests/http, tests/tcl) 254 7602
Tacky integration suites (tests/integration, need a server) 60 327
Rust: tacky-core 53 447
Rust: tacky-keyring 6 54
Rust: tacky-crypto 21 134
Rust: tacky-omemo 15 181
Rust: tacky-olm 5 24
Tcl extensions (ext/*) 21 1318
C: tacky-media: libtackymedia, rtc-ma, rtc-mv (ctest programs) 42 42
C++: tacky-media: webrtc (ctest programs) 4 4
total 481 10133

make docs regenerates these numbers. Coverage comes from the last make coverage-all; its full report is _build/cov/REPORT.md.

Building

Everything builds in libtacky's container image. The machine needs git and docker or podman.

git clone --recurse-submodules ssh://git@dev.another.im:3022/another-im/libtacky.git
cd libtacky
make all test               # build, run every suite that needs no server
make all test REQUIRE=1     # the gate: a missing prerequisite fails

MEDIA_BACKEND is webrtc (default), datachannel or none.

target what it does
make, make dist build everything; dist/libtacky.a
make tackyd-json, make tackyd the two executables
make test every suite that needs no server
make test-tacky-prosody, -ejabberd, -mongooseim integration suites against a server in a container
make coverage-all the coverage run behind the table above
make audit every Cargo.lock against the RustSec advisory database (audit.toml is the allow-list); in make gate
make docs handbook, README numbers, DOAP file, Rust API docs
make handbook HANDBOOK.md, from the schema and docs/
make help every target

More in docs/building.md.

Using it

An app links embed/tacky.h and speaks JSON:

tacky *tacky_create(const char *const *backend_args, tacky_emit_fn emit, void *ud);
void   tacky_send(tacky *t, const char *json, size_t len);   /* ["module","method",{args},token] */
void   tacky_destroy(tacky *t);                              /* replies and events arrive at emit */
void   tacky_destroy_wait(tacky *t, unsigned int max_ms);

A Tcl program loads the packages, from a built checkout or through dist/tackyd:

package require tacky::xmpp::component
set h [tacky::xmpp::component new -domain gateway.example.org -secret $secret \
           -host 127.0.0.1 -port 5347 -command onEvent]
tacky::xmpp::component connect $h

examples/component/echo.tcl is a complete component. The handbook, HANDBOOK.md, documents every method, event and Tcl command.