nandi/freeqsay-nimpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/freeqsay-nim.git
git clone ssh://git@rickub.com/nandi/freeqsay-nim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md

freeqsay

Cowsay with your FreeqWorld face. Written in Nim — a port of the
Clojure/Jolt implementation, which
is itself a port of the original TypeScript.

freeqsay <handle> <message>

Resolves a Bluesky handle → DID, derives the deterministic avatar-v1 character,
and prints a cowsay balloon above truecolor ANSI art.

Usage

freeqsay jay.bsky.team hello from the timeline
freeqsay @pfrazee.com "ships free software"
echo "piped in" | freeqsay jay.bsky.team

# wrap width, thought bubble, scale
freeqsay someone.bsky.social -W 20 short lines
freeqsay jay.bsky.team --think hmm
freeqsay jay.bsky.team --scale 3 big face says hi

# raw DID (no network)
freeqsay did:plc:ewvi7nmzuoqusbcablsm7c4h offline ok

Without a built binary, run it straight from the source tree:

nim r -d:ssl src/freeqsay.nim jay.bsky.team hello world

Library

import freeqsay

echo freeqsay("jay.bsky.team", "hello").output

Modules: freeqsay/say (balloon + face), freeqsay/avatar (traits and the
16×24 sprite), freeqsay/ansi (truecolor half-block rendering), freeqsay/png
(RGBA → PNG), freeqsay/resolve (handle → DID), freeqsay/balloon,
freeqsay/sha256 (SHA-256 / HMAC / HKDF). Importing freeqsay re-exports all
of them.

Development

just build                    # optimized binary → ./freeqsay
just test                     # test suite
just run <handle> <message>   # run from source, no build
just shot <handle> <message>  # gapless termshot png
just --list                   # everything else

nimble build and nimble test work too. -d:ssl is set in config.nims,
since handle resolution goes over HTTPS through the stdlib client.

Everything but the HTTP call and JSON parsing is pure Nim with no dependencies:
SHA-256, HMAC and HKDF live in freeqsay/sha256, and the PNG encoder writes
stored deflate blocks.

How it works

handle  →  resolveHandle  →  did
did     →  HKDF-SHA256(avatar-v1)  →  traits  →  16×24 sprite  →  ANSI
message →  cowsay balloon

fixtures/avatar-conformance.json pins the cross-implementation contract:
canonical seed, traits, and a sprite hash over all four facings. The suite
replays it, so this port derives byte-identical characters to the Clojure and
TypeScript implementations — including the sfc32 PRNG, reproduced on uint32
lanes so trait selection matches the JavaScript original bit for bit.