linji #
The CLI for 林集 (linji.at) — an atproto study-community client. One binary: multi-account login, spaces, threads, posting, watching, Markdown export. It is also the supported surface for building bots that read and post on app0.linji.at.
Build & install #
Requires Zig 0.16 (zvm/zigup recommended) and a C toolchain.
zig build # -> zig-out/bin/linji
zig build test # unit tests
zig build install --prefix ~/.local # -> ~/.local/bin/linji
Dev scenarios (need ../zds built — cd ../zds && zig build -Dtarget=x86_64-linux-gnu.2.36; the explicit target uses zig's bundled
CRT because today's system glibc crt1.o carries .sframe relocations
zig's linker rejects): zig build scenario-space,
zig build scenario-serve.
Accounts #
Any atproto handle works. To post in spaces hosted on app0.linji.at, the bot's records are hosted on our zds (phase 001: member records live on the host's zds — see docs/architecture.dj "External members"), and an admin must add the account to the space's member list:
linji space add-member --space at://<community>/space/at.linji.space/<skey> --did <bot-did>
linji space rename --space at://<community>/space/at.linji.space/<skey> --name "<display name>"
Login is OAuth. Tokens refresh themselves afterwards; the session lives in
$LINJI_HOME/accounts.json (default ~/.local/share/linji).
linji login <handle> --pds <pds-url> # prints an authorize URL, opens consent
Headless servers: log in once on a machine with a browser, then copy
accounts.json to the server (chmod 600 — it carries the DPoP key and
refresh token). Refresh is automatic from then on.
Commands (bot-relevant) #
linji thread list --space <uri> # thread AT-URIs + titles
linji thread create --space <uri> --title <t> # -> thread AT-URI
linji post --space <uri> --thread <at-uri> <text…> # -> post AT-URI
linji read --space <uri> [--tag <t>] # merged thread view
linji watch --space <uri> [--interval ms] # follow new posts
linji edit|delete --space <uri> --post <at-uri> …
linji export --space <uri> --thread <at-uri> # thread as Markdown
--as <handle> picks a non-default account. --tag filters to threads
containing an inline hashtag.
A minimal bot #
The loop: watch for new posts, reply with post. Here reacting to a
#question tag:
#!/bin/sh
SPACE="at://did:plc:community/space/at.linji.space/general"
THREAD="at://did:plc:bot/at.linji.thread/…" # reply thread
linji watch --space "$SPACE" --interval 3000 | while IFS= read -r line; do
case "$line" in
*"#question"*) linji post --space "$SPACE" --thread "$THREAD" \
"收到 — 让我想想。" ;;
esac
done
For machine-readable reads, the BFF serves GET /api/space?uri=<space>
(JSON: threads + posts) once you hold a session cookie; the CLI's
rendered output is the stable interface for shell bots.
Run it under your service manager. app0/dinit/ has examples for the dev stack.
Dev rig #
dinit -d app0/dinit dev # zds + seed + BFF + vite (see app0/dinit/dev)
# seeded accounts: community/alice/bob (.test, password <name>-password)
Layout, architecture, roadmap: see ../docs/.