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 · 89 lines · 3.0 KBmarkdown
Blame HistoryOpen raw

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 static                   # fully static binary → ./freeqsay-static
just test                     # test suite
just run <handle> <message>   # run from source, no build
just shot <handle> <message>  # gapless termshot png
just --list                   # everything else

just static produces a binary with no dynamic dependencies at all — musl
libc and OpenSSL linked in, nothing dlopen'd — so it runs on any Linux. It
builds through nimstatic, pinned in
tools/nimstatic as a DotSlash file: the first run
fetches that exact build, checks its hash and caches it, and the URL names a
commit so it cannot move. A static binary carries no CA store, so set
SSL_CERT_FILE when resolving handles on a host without one at the default
path.

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# freeqsay

Cowsay with your FreeqWorld face. Written in Nim — a port of the
[Clojure/Jolt implementation](https://gitlab.com/nandithebull/freeqsay), which
is itself a port of the original TypeScript.

```bash
freeqsay <handle> <message>
```

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

## Usage

```bash
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:

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

## Library

```nim
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

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

`just static` produces a binary with no dynamic dependencies at all — musl
libc and OpenSSL linked in, nothing dlopen'd — so it runs on any Linux. It
builds through [nimstatic](https://rickub.com/nandi/nimstatic), pinned in
`tools/nimstatic` as a [DotSlash](https://dotslash-cli.com) file: the first run
fetches that exact build, checks its hash and caches it, and the URL names a
commit so it cannot move. A static binary carries no CA store, so set
`SSL_CERT_FILE` when resolving handles on a host without one at the default
path.

`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.