| freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 6h ago | 1 | # freeqsay |
| 2 | |
| 3 | Cowsay with your FreeqWorld face. Written in Nim — a port of the |
| 4 | [Clojure/Jolt implementation](https://gitlab.com/nandithebull/freeqsay), which |
| 5 | is itself a port of the original TypeScript. |
| 6 | |
| 7 | ```bash |
| 8 | freeqsay <handle> <message> |
| 9 | ``` |
| 10 | |
| 11 | Resolves a Bluesky handle → DID, derives the deterministic `avatar-v1` character, |
| 12 | and prints a cowsay balloon above truecolor ANSI art. |
| 13 | |
| 14 | ## Usage |
| 15 | |
| 16 | ```bash |
| 17 | freeqsay jay.bsky.team hello from the timeline |
| 18 | freeqsay @pfrazee.com "ships free software" |
| 19 | echo "piped in" | freeqsay jay.bsky.team |
| 20 | |
| 21 | # wrap width, thought bubble, scale |
| 22 | freeqsay someone.bsky.social -W 20 short lines |
| 23 | freeqsay jay.bsky.team --think hmm |
| 24 | freeqsay jay.bsky.team --scale 3 big face says hi |
| 25 | |
| 26 | # raw DID (no network) |
| 27 | freeqsay did:plc:ewvi7nmzuoqusbcablsm7c4h offline ok |
| 28 | ``` |
| 29 | |
| 30 | Without a built binary, run it straight from the source tree: |
| 31 | |
| 32 | ```bash |
| 33 | nim r -d:ssl src/freeqsay.nim jay.bsky.team hello world |
| 34 | ``` |
| 35 | |
| 36 | ## Library |
| 37 | |
| 38 | ```nim |
| 39 | import freeqsay |
| 40 | |
| 41 | echo freeqsay("jay.bsky.team", "hello").output |
| 42 | ``` |
| 43 | |
| 44 | Modules: `freeqsay/say` (balloon + face), `freeqsay/avatar` (traits and the |
| 45 | 16×24 sprite), `freeqsay/ansi` (truecolor half-block rendering), `freeqsay/png` |
| 46 | (RGBA → PNG), `freeqsay/resolve` (handle → DID), `freeqsay/balloon`, |
| 47 | `freeqsay/sha256` (SHA-256 / HMAC / HKDF). Importing `freeqsay` re-exports all |
| 48 | of them. |
| 49 | |
| 50 | ## Development |
| 51 | |
| 52 | ```bash |
| 53 | just build # optimized binary → ./freeqsay |
| just static: fully static builds through a pinned nimstatic 078b24d nandi 3h ago | 54 | just static # fully static binary → ./freeqsay-static |
| freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 6h ago | 55 | just test # test suite |
| 56 | just run <handle> <message> # run from source, no build |
| 57 | just shot <handle> <message> # gapless termshot png |
| 58 | just --list # everything else |
| 59 | ``` |
| 60 | |
| just static: fully static builds through a pinned nimstatic 078b24d nandi 3h ago | 61 | `just static` produces a binary with no dynamic dependencies at all — musl |
| 62 | libc and OpenSSL linked in, nothing dlopen'd — so it runs on any Linux. It |
| 63 | builds through [nimstatic](https://rickub.com/nandi/nimstatic), pinned in |
| 64 | `tools/nimstatic` as a [DotSlash](https://dotslash-cli.com) file: the first run |
| 65 | fetches that exact build, checks its hash and caches it, and the URL names a |
| 66 | commit so it cannot move. A static binary carries no CA store, so set |
| 67 | `SSL_CERT_FILE` when resolving handles on a host without one at the default |
| 68 | path. |
| 69 | |
| freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 6h ago | 70 | `nimble build` and `nimble test` work too. `-d:ssl` is set in `config.nims`, |
| 71 | since handle resolution goes over HTTPS through the stdlib client. |
| 72 | |
| 73 | Everything but the HTTP call and JSON parsing is pure Nim with no dependencies: |
| 74 | SHA-256, HMAC and HKDF live in `freeqsay/sha256`, and the PNG encoder writes |
| 75 | stored deflate blocks. |
| 76 | |
| 77 | ## How it works |
| 78 | |
| 79 | ``` |
| 80 | handle → resolveHandle → did |
| 81 | did → HKDF-SHA256(avatar-v1) → traits → 16×24 sprite → ANSI |
| 82 | message → cowsay balloon |
| 83 | ``` |
| 84 | |
| 85 | `fixtures/avatar-conformance.json` pins the cross-implementation contract: |
| 86 | canonical seed, traits, and a sprite hash over all four facings. The suite |
| 87 | replays it, so this port derives byte-identical characters to the Clojure and |
| 88 | TypeScript implementations — including the sfc32 PRNG, reproduced on `uint32` |
| 89 | lanes so trait selection matches the JavaScript original bit for bit. |