@terminalika/pi-play

terminalika inside pi: retro games in an overlay that pause themselves the moment the agent finishes or needs you.

Packages

Package details

extension

Install @terminalika/pi-play from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@terminalika/pi-play
Package
@terminalika/pi-play
Version
0.1.1
Published
Sep 10, 2026
Downloads
not available
Author
recepahmetkara
License
MIT
Types
extension
Size
5.5 MB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions"
  ],
  "video": "https://terminalika.dev/demo-pi.mp4",
  "image": "https://raw.githubusercontent.com/terminalika/pi-play/main/.github/demo.gif"
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-play

terminalika inside pi: /play opens a game over the conversation, and it pauses the moment pi is waiting for you

terminalika as a pi package: the retro games from terminalika-core, running in an overlay inside pi, that pause themselves the moment pi finishes and is waiting for you. No standalone binary, no second pane.

pi install npm:@terminalika/pi-play

Then, while pi works:

/play            pick a game
/play snake      or name one: snake, tetris, 2048, mines
/play tetris     while snake is on: swap it (asks first, until you say don't)
/play stop       end the game

Arrows/WASD move, SPACE pauses, R resets. When pi settles (its turn is over, it is waiting on you) what happens to a running game is asked once, on the first /play, and remembered in the terminalika config dir (pi.json):

  • Pause the game — freeze it with a notice in pi's colour; resume with SPACE (default),
  • Settle the game — the game settles too: parked, hidden, the keyboard back with pi, and a toast pointing at /play,
  • Don't pause — keep playing; pi settling changes nothing.

Skipping the question (Esc) keeps the default for that run and offers it again next time. The file can be edited by hand:

{ "auto_pause": "pause" }

Going back to pi never ends the game: ESC parks it - paused, hidden, the editor has the keyboard again and the status line remembers it - and /play brings it back exactly where it was. The only thing that ends a game is /play stop.

Changing games: /play <other> from the editor, or alt+g from either side - it opens the picker with the game that is on listed first (so Enter returns to it); in the game it parks first, and cancelling brings the same game back. Since swapping closes the game that is on, it asks Yes / No / Yes, and don't ask again; the last one is remembered in pi.json as "confirm_switch": false.

On a Mac every alt+… above reads option+… - in the hints, the toasts and the status line. The key is the same one.

Try it without installing: pi -e /path/to/pi-play.

How it works

This is a thin pi launcher for terminalika-core, the same way the CLI and the website are thin launchers: the games are written once, in Go.

terminalika-core (Go) ──GOOS=js──▶ wasm/terminalika.wasm
                                        │  tcell's web-screen protocol:
                                        │  Go calls resize/clearScreen/drawCell/show,
                                        │  registers onKeyEvent, exposes tkPause/tkResume/tkQuit
                                        ▼
src/worker.ts               the game's thread: screen.ts keeps the cell
  (screen.ts, runtime.ts)   grid, runtime.ts runs the wasm; every frame is
                            posted to the main thread as ANSI lines
                                        │
                                        ▼
src/host.ts                 main thread: keeps the latest frame for a pi
                            overlay component (ctx.ui.custom({ overlay:
                            true })), relays keys (src/keys.ts)
                                        │
                                        ▼
extensions/terminalika.ts   /play, and pi.on("agent_settled") → tkPause.
  • Overlay, not a pane. The game floats over the conversation; pi's own DOOM example is proof the overlay can carry real-time rendering. It takes exactly as many rows as the game says it needs (core.Sized: board, status line above, hint lines below), each game reporting its own number, and the full width of the terminal: pi splices an overlay into each row after the text to its left, measured with its own width tables, and a terminal that draws an emoji one cell differently would shift that row.
  • Its own thread. The wasm runs on a worker thread (src/worker.ts); pi rendering a streaming answer on the main thread cannot starve the game's ticks or its held-key timers. The main thread only keeps the latest frame (src/host.ts).
  • Events come straight from pi. No session-file tailing: agent_settled is the moment pi will not continue on its own. Desktop notifications are not this package's job; pi has extensions for that.
  • Key releases. Under a terminal that speaks the kitty keyboard protocol pi reports releases and held keys feel exact; elsewhere the engine synthesises releases from auto-repeat, like the CLI does.

Not yet: high scores (the wasm has no filesystem).

Layout

extensions/terminalika.ts   the pi extension
src/                        the host: worker + host (the game's thread and the main thread's
                            view of it), screen (cell grid → ANSI), runtime (wasm_exec), keys
go/                         the Go side: main + engine port (pause notice, held keys)
wasm/                       terminalika.wasm + wasm_exec.js, built from go/ (committed)
scripts/build-wasm.sh       npm run build:wasm  (Go 1.24+; prefers a sibling ../terminalika-core)
scripts/smoke.ts            npm run test:wasm   (headless: every game renders, pauses, exits)

Requirements

pi 0.84+ (agent_settled, overlays). Node 22+ or Bun for the host; the package ships the wasm, so no Go toolchain is needed to use it.