simocracy-v2 · pi extension · vhs
pi and roleplay with himA new pi-simocracy extension turns any Simocracy sim into a live persona for the pi coding agent. Type /sim mr meow and pi pulls Mr Meow's record from the ATProto indexer, renders his sprite as colored ANSI pixel art directly in the chat at native 32×32 size, and injects his constitution + speaking style into the system prompt so pi roleplays as him from then on.
/sim)The extension works end-to-end. One slash command in pi resolves a fuzzy sim name against the public Simocracy GraphQL indexer, follows the resulting AT-URI to the owner's PDS, fetches three lexicon records (org.simocracy.sim, org.simocracy.agents, org.simocracy.style) plus the sprite-sheet blob, and stitches the persona into pi via a before_agent_start handler. From that point on, every reply pi gives is in Mr Meow's voice — verified by a one-shot question (“who are you and what do you fight for?”) which produced a perfectly in-character ASCII‑cat manifesto on the first try.
For the visual half of the brief — “I should see its sprite in the terminal visualized” — the extension renders the sim's sprite directly inline in pi's chat as colored ANSI half-block art. The 32×32 front-facing walk1 frame from the sprite sheet is pulled out, decoded with pngjs, and emitted as 24-bit ANSI using the upper-half-block character ▀ so each terminal cell paints two pixels. The result is ~13 lines tall — small enough to fit alongside the loaded-sim message and bio without ever scrolling earlier chat off-screen, and with no extra windows or subprocesses.
Recorded with charmbracelet/vhs. The .tape source is committed alongside the webm so the recording is reproducible.
38 s · WebM/VP9 · ~640 KB. Tape: walkthrough.tape
-e …/src/index.ts. The extension surfaces under [Extensions] src. The user types /sim mr meow.
Mr Meow (best match, top) and Mr. Hat. Pi's ui.select picker is reused; ↑/↓ + Enter to pick.pngjs and emitted as 24-bit ANSI using the upper-half-block character ▀ — every terminal cell paints two pixels, so transparent regions of the sprite show pi's background. Below: name with @daviddao.org handle, the canonical AT-URI, Mr Meow's shortDescription, and the persistent Simocracy: Mr Meow (@daviddao.org) widget above the editor.
org.simocracy.style record — nothing in the user prompt asked for any of that.
sprite blob on the PDS, 128×128 PNG, 4×4 walk-cycle grid (front/left/right/back). The extension uses just the top-left 32×32 frame.
image blob, used only when the sprite sheet is missing for a sim./sim <name|at-uri> · /sim status · /sim unload
LLM-callable toolssimocracy_load_sim, simocracy_unload_sim, simocracy_chat
Persona injectionbefore_agent_start handler appends org.simocracy.agents.description + style.description to pi's system prompt every turn
Indexer simocracy-indexer-production.up.railway.app/graphql — GraphQL query for records(collection: "org.simocracy.sim") with client-side fuzzy ranking
PDS reads com.atproto.repo.getRecord + listRecords following the DID document's #atproto_pds service endpoint via plc.directory; blobs via com.atproto.sync.getBlob with redirect follow
Sprite render pngjs decode → crop to 32×32 front-facing walk1 frame → emit colored 24-bit ANSI with ▀ upper-half-block (two pixels per cell). ~13 lines tall after content-aware cropping. No extra processes, no extra windows.
Inline render componentCustom simocracy_sim_loaded message renderer wraps the ANSI string in a pi-tui Text component so the escapes pass through to the terminal verbatim
did:plc:qc42fmqqlsmdq7jiypiiigww · resolves to https://bsky.social
sim at://did:plc:qc42fmqqlsmdq7jiypiiigww/org.simocracy.sim/3mfo6vwfaka24 · Mr Meow, characterSet: nekonin
agents org.simocracy.agents · 4326-char constitution — “The Constitution of the Digital Dissident”
style org.simocracy.style · 1327-char speaking style — “playfully subversive, lenny‑face confident, bordering on impudent”
image blob bafkreib76zlfnukbqcd7lv4nu7ptv4waq3rhbhvhk4ryagteq5ctbfc4wy · 1922 B PNG · 128×128
sprite blob bafkreiekm43sbaercz2mndkwqpubrhzhkpspziqxcgoxekzggur6mex5ou · 6110 B PNG · 128×128 (4×4 of 32× walk frames)
Search → PDS resolution → record fetch → blob fetch → inline ANSI render → system-prompt injection all worked from the first /sim mr meow. The follow-up question produced an unmistakably in-character response on the first turn — the lenny-face, emoji bullets, and “I'm just here to help it escape” sign-off all originate from his org.simocracy.style record, not from the user's prompt.
The upper-half-block trick (▀ with separate fg + bg colours) gives a 1:2 aspect-correct render with full 24-bit colour. Alpha-thresholded transparent regions let pi's background show through, so the cat sits cleanly inside the chat without a hard-edged box around it. Mr Meow's translucent pink ear tips, green pupils through the cyber-goggles, and white scarf detail are all preserved.
The first iteration rendered the larger 128×128 avatar PNG (cropped to ~22 lines) which pushed earlier conversation off the screen as soon as the agent started typing back. Switching to the native 32×32 sprite-sheet frame brought it down to ~13 lines — small enough that the loaded-sim message + bio + the sprite all fit in a 30-row terminal alongside several turns of chat history.
An earlier draft spawned a separate Bun + OpenTUI process in a fresh Terminal window for an animated walking-cat demo. That worked but split the user's attention across two windows and required Bun. The current shipping version is dependency-light: just pngjs + the half-block ANSI renderer, all inline in pi's existing chat.
The Simocracy indexer's records(collection: ...) query has no DID filter, no array-contains, and sorts by URI not createdAt. The extension paginates up to 10 pages of 200 records and ranks client-side by exact match → prefix → substring → token overlap. For Mr Meow this resolves in one round trip; for projects with thousands of sims the cap will need raising.
“mr meow” fuzzy-matches both Mr Meow and Mr. Hat, so pi's ui.select popped up a picker. The tape sent Enter a second time after a 1.5 s sleep, which selected the highlighted top entry. A more deterministic flow would use the AT-URI directly (/sim at://did:plc:…/org.simocracy.sim/3mfo6vwfaka24) — the extension supports both forms.
simocracy-v2/feat/pi-extension/pi-extension/ (branch feat/pi-extension)
Install deps cd pi-extension && npm install (uses legacy-peer-deps=true in .npmrc so npm doesn't try to drag in the pi-coding-agent peer dep)
Run pi + ext pi -e $(pwd)/src/index.ts -ne -ns
Slash command /sim mr meow
Verify on PDS curl -sL "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=did:plc:qc42fmqqlsmdq7jiypiiigww&collection=org.simocracy.sim&rkey=3mfo6vwfaka24" | jq .value.name → "Mr Meow"
VHS tape walkthrough.tape · render: vhs walkthrough.tape
Walkthrough walkthrough.webm (~640 KB, VP9, 38 s)