ana-board

module
v0.5.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: MIT

README ΒΆ

Ana Board

Ana Board is a small Go-powered split-flap style status board for agents, automations, reminders, CI jobs, and local scripts.

It runs as one local Go server with a browser display, an admin panel, a command-line sender, and a stdio MCP server for tools like Codex, Claude Code, OpenCode, and remote agents such as Hermes.

Ana Board demo

Watch the demo video

About

Ana Board is a private status display for the small signals that should interrupt you gently:

  • agent progress updates
  • reminders
  • email/task nudges
  • CI/build/deploy status
  • approval requests
  • quick personal automations

The usual setup is:

Agent or script
  -> ana-boardctl or ana-board-mcp
  -> private Ana Board HTTP API
  -> browser split-flap display

Ana Board is intentionally local-first. Run the display where you can see it, then let remote agents reach it over a private network. It is not ready to expose as a public unauthenticated web service.

Pieces:

  • ana-board: local web server, display, admin panel, HTTP API, SSE stream
  • ana-boardctl: CLI sender for scripts, cron, CI, and remote shells
  • ana-board-mcp: stdio MCP server for Codex, Claude Code, OpenCode, Hermes-style agents
  • ana-board-codex-bridge: Codex notify/hook bridge for durable non-spammy board signals

Install

Install Go 1.22 or newer, then install the binaries:

go install github.com/georgestander/ana-board/cmd/ana-board@latest
go install github.com/georgestander/ana-board/cmd/ana-boardctl@latest
go install github.com/georgestander/ana-board/cmd/ana-board-mcp@latest
go install github.com/georgestander/ana-board/cmd/ana-board-codex-bridge@latest

Make sure Go's bin directory is on PATH:

export PATH="$PATH:$(go env GOPATH)/bin"

Upgrade

Upgrade the binaries together:

go install github.com/georgestander/ana-board/cmd/ana-board@latest
go install github.com/georgestander/ana-board/cmd/ana-boardctl@latest
go install github.com/georgestander/ana-board/cmd/ana-board-mcp@latest
go install github.com/georgestander/ana-board/cmd/ana-board-codex-bridge@latest

Then restart any already-running process that uses the upgraded binary. Updating ana-board does not update an already-running board server, and updating ana-board-codex-bridge does not update an already-running bridge daemon until it restarts.

Run

ana-board

Open:

http://localhost:8080
http://localhost:8080/admin

The server binds to 127.0.0.1:8080 by default. Use another bind address or port explicitly:

ANA_BOARD_ADDR=127.0.0.1:18080 ana-board

Bind an additional local or private address when the same board should be reachable through more than one interface:

ANA_BOARD_ADDR=<board-private-ip>:18080 ANA_BOARD_EXTRA_ADDRS=127.0.0.1:18080 ana-board

Browser-based writes from the admin page are accepted only from trusted browser origins derived from ANA_BOARD_ADDR and ANA_BOARD_EXTRA_ADDRS. CLI, MCP, and script writes without a browser Origin header still work. If you open the board through a DNS name or reverse proxy, configure that exact origin:

ANA_BOARD_TRUSTED_ORIGINS=http://ana-board-host:18080 ana-board

From a local clone:

go run ./cmd/ana-board

Private Network Access

Use this when the board is running on one machine and an agent, script, or server on another private machine needs to send updates to it. This can be a VPN, tailnet, VPC, WireGuard network, or any private network you control.

On the board machine, find the private IP address that the other machine can reach. Then bind Ana Board to that private address:

ANA_BOARD_ADDR=<board-private-ip>:18080 ana-board

Open the board from a browser that can reach that private network:

http://<board-private-ip>:18080
http://<board-private-ip>:18080/admin

On the sender machine, point the CLI or MCP server at that board URL:

export ANA_BOARD_URL=http://<board-private-ip>:18080
ana-boardctl send --source agent --kind task "[green]AGENT CONNECTED βœ…"

If browser admin writes use a private DNS name instead of the bind IP, add it to ANA_BOARD_TRUSTED_ORIGINS on the board machine.

If the sender can ping the board machine but cannot send messages, check that Ana Board is listening on the private IP rather than only 127.0.0.1:

lsof -nP -iTCP:18080 -sTCP:LISTEN
curl http://<board-private-ip>:18080/healthz

Do not expose Ana Board's unauthenticated write API on the public internet. Keep it on localhost or a private network you control.

Send A Message

From the browser, use:

http://localhost:8080/admin

From HTTP:

curl -X POST http://localhost:8080/api/messages \
  -H "Content-Type: application/json" \
  -d '{"text":"[green]B[amber]U[blue]I[violet]L[green]D PASSED βœ…","source":"ci","kind":"build"}'

From the CLI:

ana-boardctl capabilities --json
ana-boardctl preview "[blue]HELLO WORLD 🌍"
ana-boardctl send --source codex --kind success "[green]BUILD PASSED βœ…"
ana-boardctl send --tiles-json '[{"symbol":"A","color":"green"},{"symbol":"N","color":"amber"},{"symbol":"A","color":"red"}]'
ana-boardctl send --segments-json '[{"text":"ANA ","color":"green"},{"text":"READY βœ…","color":"blue"}]'
ana-boardctl art list
ana-boardctl send --sprite trophy --source codex --kind success
ana-boardctl frame --image ./tiny.png --source codex
ana-boardctl frame --placements-json '[{"row":0,"col":0,"symbol":"A","color":"green"},{"row":5,"col":21,"symbol":"βœ…","color":"blue"}]'
ana-boardctl send --at "2026-05-31T18:30:00+02:00" "[amber]REMINDER ⏰"

Use a remote/private board URL:

ANA_BOARD_URL=http://ana-board-host:18080 ana-boardctl send --source hermes "[amber]EMAIL NEEDS REPLY βœ‰οΈ"

MCP

cmd/ana-board-mcp exposes a fixed stdio MCP tool surface:

  • ana_board_capabilities
  • ana_board_preview_message
  • ana_board_send_message
  • ana_board_list_sprites
  • ana_board_preview_sprite
  • ana_board_send_sprite
  • ana_board_current
  • ana_board_recent_messages
  • ana_board_clear, requiring confirm=true

Claude Code:

claude mcp add --transport stdio ana-board -- /absolute/path/to/ana-board-mcp

Claude Code JSON form:

claude mcp add-json ana-board '{"type":"stdio","command":"/absolute/path/to/ana-board-mcp","args":[],"env":{"ANA_BOARD_URL":"http://localhost:8080"}}'

OpenCode:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ana-board": {
      "type": "local",
      "command": ["/absolute/path/to/ana-board-mcp"],
      "enabled": true,
      "environment": {
        "ANA_BOARD_URL": "http://localhost:8080"
      }
    }
  }
}

Remote agents can run the MCP process locally and point it at a private board URL:

ANA_BOARD_URL=http://ana-board-host:18080 ana-board-mcp

Codex Bridge

ana-board-codex-bridge is the durable Codex path for lifecycle updates. MCP is still useful for rich manual board moments, but the bridge is meant for persistent notify/hook behavior across fresh Codex threads.

Recommended shape:

Codex notify/hooks
  -> ana-board-codex-bridge enqueue
  -> local queue of safe classified signals
  -> ana-board-codex-bridge daemon
  -> Ana Board HTTP API

enqueue reads a Codex payload, stores only safe signal flags, compact project labels, internal thread labels, and short non-prompt topic labels when a hook provides one. It exits without calling the board. It detects high-signal events such as failures, profanity reactions, approval prompts, Codex questions from planning/user-input flows, and real milestones such as deploys, releases, pushes, tags, or merges. Routine turn-ended completions and passing tests are intentionally skipped. The bridge is intentionally a fallback notifier, not a personality engine: visible bridge frames are terse project/topic alerts, while natural context-rich board notes should be written live by Codex through MCP when Codex has the real thread context. Internal thread hashes are not displayed:

ana-board-codex-bridge enqueue --queue-dir /private/tmp/ana-board-codex-bridge/queue turn-ended '{"last_message":"v0.5.7 released and pushed to GitHub"}'
ana-board-codex-bridge enqueue --queue-dir /private/tmp/ana-board-codex-bridge/queue PostToolUse '{"tool_name":"request_user_input","questions":[{"header":"Scope"}]}'

Run the daemon under a local supervisor such as LaunchAgent:

ANA_BOARD_URL=http://localhost:8080 ana-board-codex-bridge daemon --queue-dir /private/tmp/ana-board-codex-bridge/queue --state /private/tmp/ana-board-codex-bridge/state.json

Useful environment variables:

  • ANA_BOARD_URL
  • ANA_BOARD_CODEX_QUEUE_DIR
  • ANA_BOARD_CODEX_STATE
  • ANA_BOARD_CODEX_GLOBAL_COOLDOWN_SECONDS
  • ANA_BOARD_CODEX_KIND_COOLDOWN_SECONDS
  • ANA_BOARD_CODEX_DUPLICATE_COOLDOWN_SECONDS
  • ANA_BOARD_CODEX_SEND_TIMEOUT_MS

Do not expose this unauthenticated API on the public internet yet. Keep it private on localhost or a tightly scoped private network.

Message Limits

  • Board size: 10 rows x 22 columns.
  • Text is normalized to uppercase.
  • Spaces collapse.
  • Allowed plain characters: A-Z, 0-9, space, ., ,, !, ?, :, -, /, ', ", and β–ˆ.
  • Native emoji can be written directly. On Apple devices they render as native iOS/macOS emoji.
  • Ana Board does not use an emoji whitelist. The alias list is only a shortcut list.
  • Emoji grapheme clusters such as βœ…, πŸ‘πŸ½, πŸ‡ΏπŸ‡¦, and πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ count as one board tile.
  • Common named aliases like :rocket:, :check:, :warning:, :mail:, :calendar:, and :globe: also work.
  • Unknown named aliases are treated as plain text, so agents are safest when they send native emoji directly.
  • Messages that need more than 10 rows fail.
  • Words longer than 22 tiles fail.
  • Exact per-tile color can be sent with JSON tiles: [{"symbol":"A","color":"green"},{"symbol":"N","color":"amber"},{"symbol":"A","color":"red"}].
  • Quick text can color individual letters with inline tokens: [green]A[amber]N[red]A [blue]READY.
  • Agents can also send JSON segments when phrases share a color: [{"text":"OK ","color":"green"},{"text":"FAIL","color":"red"}].
  • Exact sparse placement can be sent with JSON placements: [{"row":0,"col":0,"symbol":"A","color":"green"}].
  • Full exact frames can be sent with JSON frame: cells must be 10 rows x 22 columns; colors is optional and must match the same shape.
  • Block art uses the β–ˆ symbol as a colored pixel. Named sprites are available through ana-boardctl --sprite and MCP sprite tools; local image conversion is available through ana-boardctl --image.
  • The color metadata field is only the default for tiles without an inline token or segment color.
  • Optional exact-time sending is client-side: ana-boardctl send --at ... waits until that time, then sends.

Metadata:

  • animation: row
  • color: default/fallback tile color, one of white, green, amber, red, blue, violet
  • kind: info, success, warning, error, reminder, email, task, deploy, build
  • priority: low, normal, high

API

GET  /healthz
GET  /
GET  /admin
POST /admin/messages
POST /admin/clear
GET  /api/current
GET  /api/messages
POST /api/messages
POST /api/clear
GET  /events

Test

go test ./...
go vet ./...
node --check web/static/board.js
node --check web/static/admin.js

If Go tries to write its build cache outside the sandboxed workspace:

GOCACHE=$PWD/.gocache go test ./...

Directories ΒΆ

Path Synopsis
cmd
ana-board command
ana-board-mcp command
ana-boardctl command
internal
art

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL