diff --git a/.githooks/post-commit b/.githooks/post-commit index 115a1a89c0..7e5ee7cb1c 100755 --- a/.githooks/post-commit +++ b/.githooks/post-commit @@ -12,6 +12,9 @@ if [ -z "${REPO_ROOT:-}" ]; then exit 0 fi +TRIGGER_COMMIT=$(git -C "$REPO_ROOT" rev-parse HEAD) +CURRENT_BRANCH="$(git -C "$REPO_ROOT" symbolic-ref --short HEAD 2>/dev/null || true)" + NODE_BIN="${NODE_BIN:-node}" if ! command -v "$NODE_BIN" >/dev/null 2>&1; then exit 0 @@ -42,37 +45,29 @@ fi # Pipeline: # 1. push HEAD to knot (origin) so lith's pull matches local # 2. fire lith/deploy.fish β€” pulls knot, restarts service -# 3. wait for the knotβ†’github mirror (lith's 60s systemd timer) -# 4. fire session-server/deploy.fish β€” VPS git pull + node restart +# 3. lith retains its lock while waiting for the mirror and deploying +# session-server at the same SHA # # Both deploys run async so they don't block the commit. Logs land in # .git/arena-auto-deploy.log for postmortems. -CURRENT_BRANCH="$(git -C "$REPO_ROOT" symbolic-ref --short HEAD 2>/dev/null || true)" if [ "${AC_NO_AUTO_DEPLOY:-}" != "1" ] && [ "$CURRENT_BRANCH" = "main" ]; then ARENA_TOUCH_RE='^(system/public/aesthetic.computer/disks/arena\.mjs|system/public/aesthetic.computer/lib/(arena-world|pmove|cam-doll)\.mjs|session-server/(arena-manager|session)\.mjs|shared/.*)$' - if git diff-tree --no-commit-id --name-only -r HEAD | grep -qE "$ARENA_TOUCH_RE"; then + if git diff-tree --no-commit-id --name-only -r "$TRIGGER_COMMIT" | grep -qE "$ARENA_TOUCH_RE"; then echo "🏟️ arena change on main β€” auto-deploying lith + session-server in background…" - DEPLOY_LOG="$REPO_ROOT/.git/arena-auto-deploy.log" + DEPLOY_LOG="$(git -C "$REPO_ROOT" rev-parse --path-format=absolute --git-common-dir)/arena-auto-deploy.log" ( - printf "\n=== %s commit=%s ===\n" "$(date -Iseconds)" "$(git -C "$REPO_ROOT" rev-parse --short HEAD)" >> "$DEPLOY_LOG" + printf "\n=== %s commit=%s ===\n" "$(date -Iseconds)" "$TRIGGER_COMMIT" >> "$DEPLOY_LOG" # 1. Push to knot if local is ahead (idempotent if already pushed). - LOCAL_HEAD=$(git -C "$REPO_ROOT" rev-parse HEAD) - REMOTE_HEAD=$(git -C "$REPO_ROOT" rev-parse origin/main 2>/dev/null || echo none) - if [ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]; then - echo "β†’ pushing $LOCAL_HEAD to origin/main" >> "$DEPLOY_LOG" - git -C "$REPO_ROOT" push origin main >> "$DEPLOY_LOG" 2>&1 || \ - echo "⚠ push to knot failed β€” skipping deploys" >> "$DEPLOY_LOG" + if ! git -C "$REPO_ROOT" push origin "$TRIGGER_COMMIT:refs/heads/main" >> "$DEPLOY_LOG" 2>&1; then + echo "Push to knot failed; deploy stopped" >> "$DEPLOY_LOG" + exit 1 fi # 2. lith (pulls from knot directly). echo "β†’ fish lith/deploy.fish" >> "$DEPLOY_LOG" - fish "$REPO_ROOT/lith/deploy.fish" >> "$DEPLOY_LOG" 2>&1 || \ - echo "⚠ lith deploy failed" >> "$DEPLOY_LOG" - # 3. give knotβ†’github mirror time to sync (lith systemd timer = 60s). - sleep 75 - # 4. session-server (pulls from github via VPS). - echo "β†’ fish session-server/deploy.fish" >> "$DEPLOY_LOG" - fish "$REPO_ROOT/session-server/deploy.fish" >> "$DEPLOY_LOG" 2>&1 || \ - echo "⚠ session-server deploy failed" >> "$DEPLOY_LOG" + if ! AC_DEPLOY_ARENA=1 DEPLOY_BRANCH=main EXPECTED_COMMIT="$TRIGGER_COMMIT" fish "$REPO_ROOT/lith/deploy.fish" >> "$DEPLOY_LOG" 2>&1; then + echo "Lith deploy failed; session deploy stopped" >> "$DEPLOY_LOG" + exit 1 + fi echo "β†’ done" >> "$DEPLOY_LOG" ) & disown 2>/dev/null || true diff --git a/CLAUDE.md b/CLAUDE.md index 07fd2887a2..94ce272937 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,161 +1,21 @@ -# CLAUDE.md +# Claude adapter -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. It is kept deliberately short (target: under 200 lines); domain detail lives in on-demand files noted throughout β€” read those when working in their area. +@SCORE.md -## Execution Environment +SCORE owns shared instructions and task routes. Apply the current user request +before repository defaults. The piece API guide is explicitly routed from +SCORE for all agents; it is not a second root policy. -Read `ENVIRONMENT.md` and resolve the active environment before applying -editor-specific instructions from any score. Claude Terminal on macOS uses its -local shell directly; Emacs MCP/fishy instructions apply only when the active -environment is `emacs`. +## Local memory -## Project Overview +Claude prompt hooks in `.claude/settings.json` write through `memory/hook.mjs` +to the encrypted local store (`~/.ac-agent-memory`, or `AGENT_MEMORY_HOME`). +The installed post-commit hook can log commits, import Codex sessions and flush +an explicitly enabled remote queue. Inspect installation with the instruction +doctor; a tracked hook is not proof it is installed. -Aesthetic Computer (AC) is a mobile-first runtime and social network for creative computing. It's designed as a musical instrument-like interface where users discover memorizable paths through commands and published "pieces" (interactive programs). The system supports both JavaScript (.mjs) and KidLisp (.lisp) pieces. - -## Agent Memory (Local-First) - -When @jeffrey is working, Claude hook events are written to a local encrypted memory store first. - -- **Hook**: `.claude/settings.json` β†’ `UserPromptSubmit` β†’ `node memory/hook.mjs` -- **Git hook**: `.githooks/post-commit` β†’ commit log + Codex import + remote flush -- **Local store**: `~/.ac-agent-memory` (overridable via `AGENT_MEMORY_HOME`); AES-256-GCM; redacted before indexing/sync -- **CLI**: `node memory/cli.mjs` (`list`, `remember`, `checkpoint`, `doctor`, `profile`, `flush-remote`); Codex import via `node memory/codex-sync.mjs` -- Remote writes are optional and disabled by default (`AGENT_MEMORY_REMOTE_ENABLED=true` + `AGENT_MEMORY_REMOTE_URL=...` to enable). `remember` continuity is lineage (`remembered_from`), not session takeover. - -## AestheticAnts & Score.md - -Automated maintenance system ("AA"). Main score: `SCORE.md`; ant mindset/rules: `ants/mindset-and-rules.md`. Read both before contributing. - -**Important:** Do not modify `ants/mindset-and-rules.md` unless you are the queen (@jeffrey). - -## The Hand (Code Style) - -`HAND.md` at the repo root is the code-style guide β€” companion to `papers/VOICE.md` (prose). Read it before writing or carving core code. Key idea: leaves (pieces) stay small and can be loop-generated; the foundational libs are the instrument and want knowability over raw terseness. The active "rehandify" campaign and its guardrails live at the bottom of that file. - -## The Screen (Piece Layout) - -`SCREEN.md` at the repo root governs how a piece *draws* β€” the corner-label zone the system reserves at `(6, 6)`, `reframed` layout, `ui.Button` + `pens()` multitouch, killing held voices in `leave`. Read it before drawing a piece. HAND covers how the code reads; this covers where the pixels go. - -## Development Commands - -### Running the Development Environment - -```bash -npm run aesthetic # Run all servers (site, session, edge, stripe) β€” primary dev command (alias: npm run ac) -npm run site # Main dev server (port 8888) -npm run server:session # Session backend (port 8889) -npm run stripe # Stripe webhook listener -``` - -### Testing - -```bash -npm test # All tests -npm run test:kidlisp # KidLisp tests (watch; :direct for no watch) -npm run test:perf # Performance tests (:chrome, :lighthouse variants) -``` - -### Health Check (Doctor) - -Stack-wide preflight before debugging β€” tells you *which* layer is sick: - -```bash -npm run doctor # full sweep; -- --local / -- --prod / -- --strict -``` - -Stopped dev servers read as ⚠️ (advisory); only prod site + CDN are critical. Add checks in the `CHECKS` array of `toolchain/doctor.mjs` (dependency-free). - -### Pieces, Sessions, Assets - -```bash -npm run new piece-name "Description" # New piece from blank.mjs template -npm run session:alive # List active session backends -npm run server:session:logs ID # Logs for a session (session:reset ID to terminate) -npm run session:publish # Deploy session server -npm run assets:sync:down # Sync assets from DO Spaces (:up to push) -``` - -### AC Native OS (fedac/native/) - -**Routine OTA releases are built remotely on the oven, and you must ask for them.** Landing fedac/native/ changes on `origin/main` builds nothing β€” the oven's native git poller ships disabled (`NATIVE_POLL_INTERVAL_MS` defaults to 0), so pushing and waiting means the published OTA silently stays stale. Run `ac-os oven` to release. - -```bash -ac-os oven # Trigger remote OTA build for HEAD -ac-os oven status # Show oven queue + recent builds -ac-os oven watch # Tail active build logs (SSE) -ac-os oven cancel # Cancel active oven job -``` - -**Use `ac-os oven` for OTA releases β€” not `ac-os upload`.** `ac-os upload` is a local-build-and-push fallback that requires a clean tree and has historically auto-stashed uncommitted work in ways that strand changes. - -Local-only commands (rarely needed): `ac-os build` (binary β†’ initramfs β†’ kernel), `ac-os flash`, `ac-os upload`, `ac-os flash+upload`. - -**Critical:** `ac-os upload` always does a full rebuild before uploading. The kernel embeds the git hash and build name at compile time (`AC_GIT_HASH`, `AC_BUILD_NAME` in the Makefile). Uploading without rebuilding would serve a stale kernel with the wrong version string. - -### Notation - -- **compush** - commit & push. If the commit touches live-served paths (`system/public/**`, `system/netlify/functions/**`), follow with `fish lith/deploy.fish` unprompted β€” pushing alone doesn't put it in production. -- **oskieploy** - commit & push, then release oskiewar across every surface with - `npm run oskiewar:deploy`. That one command carries the lot: it stamps - `buildVersion` to match the commit count and reburns the hash-bound social - preview (both used to be manual steps the release refused without), runs - `fish lith/deploy.fish`, and then reconciles web, iOS and Xbox, verifying the - production bytes against the source hash. Report the channel table. A devkit - that is switched off comes back `offline` rather than `failed` and is not a - problem β€” `blocked: []` is the line that says nothing went wrong. Catch a - sleeping console up later with `npm run oskiewar:reconcile`. -- **sticky the X** - on a macOS host, run `node toolchain/macos/sticky.mjs` on X β€” translucent, larger-text Stickies note sized to fit and centered. See `toolchain/macos/README.md`. - -## Architecture - -### Core Components - -1. **Boot** (`system/public/aesthetic.computer/boot.mjs`) β€” entry point; loads BIOS, service-worker module caching, WebSocket module loader for hot reload, boot telemetry to `/api/boot-log`. -2. **BIOS** (`system/public/aesthetic.computer/bios.mjs`) β€” main runtime coordinator; piece lifecycle, API surface, routing/navigation. -3. **Disk** (`system/public/aesthetic.computer/lib/disk.mjs`) β€” large (~572KB) core API for pieces: graphics primitives, audio, input, UI. All pieces talk to AC through the Disk API. -4. **Module loader** (`system/public/aesthetic.computer/module-loader.mjs`) β€” WebSocket dynamic loading; hot reload in dev; prefetches common modules. - -### Pieces - -All pieces live in `system/public/aesthetic.computer/disks/` (`.mjs` and `.lisp`). **The authoring guide β€” lifecycle functions, API surface, event patterns, multiplayer dual-channel, UI components, publishing β€” is `system/public/aesthetic.computer/disks/CLAUDE.md`** (loads automatically when working there). Canonical multiplayer reference: `squash.mjs`. - -### Servers and Services - -1. **System Server** (`system/` + `lith/`) β€” production is **lith**: Express + Caddy monolith on a DigitalOcean VPS (`lith.aesthetic.computer`), deployed with `fish lith/deploy.fish`, pulling from the tangled knot `git@knot.aesthetic.computer:aesthetic.computer/core`. Netlify is no longer the host. Dev: `npm run site` (port 8888). Backend function handlers live in `system/netlify/functions/` β€” path is historical; lith's Express adapts each file as a route, so keep adding endpoints there. -2. **Session Server** (`session-server/`) β€” per-session backend via Jamsocket; Geckos.io WebSocket+UDP for chat, multiplayer, real-time state; Redis for sync. -3. **Feed Server** (`dp1-feed/`) β€” Cloudflare Worker for activity feeds, deployed separately. - -### KidLisp - -Minimal Lisp dialect for generative art (118 built-ins across 12 categories). **Comprehensive docs: `kidlisp/README.md`.** Evaluator: `system/public/aesthetic.computer/lib/kidlisp.mjs`; storage API: `system/netlify/functions/store-kidlisp.mjs`; tools in `kidlisp/tools/` (`./kidlisp/tools/source-tree.mjs $cow`, `get-source.mjs $piece-code`; dev server must be running). - -### Data Storage - -**MongoDB** (users, handles, chat, moods) Β· **Redis** (session state) Β· **Firebase** (auth, messaging) Β· **DO Spaces** (asset CDN). - -### Routing and URLs - -Pieces are URL-addressable: `aesthetic.computer/piece-name`, params `piece-name:p1:p2`, user pieces `@handle/piece-name`, QR via `share piece-name`. - -## Development Workflow - -- **Codespaces**: server at `https://{CODESPACE_NAME}-8888.app.github.dev` (`echo $CODESPACE_NAME`). -- **Hot reload**: piece changes reflect on save via the module loader; WebSocket status shows in the boot canvas; use `channel custom-name` for multi-device testing. - -## Important Directories - -- `system/public/aesthetic.computer/disks/` - All pieces (+ authoring guide CLAUDE.md) -- `system/public/aesthetic.computer/lib/` - Shared libraries -- `system/netlify/functions/` - Serverless backend functions (served by lith) -- `session-server/` - Real-time session backend -- `shared/` - Code shared between system and session servers -- `kidlisp/` - KidLisp docs and tools -- `spec/` - Jasmine tests for KidLisp -- `ants/` - AestheticAnts automated maintenance -- `easel/` - Easel, the terminal coding interface (`ac`); `cd easel && ./install.sh` - -## Notes - -- `.mjs` ES modules throughout -- When making changes, consult `ants/mindset-and-rules.md` for the ant operating philosophy +Use `node memory/cli.mjs` for list, remember, checkpoint, doctor and profile; +`node memory/codex-sync.mjs` imports Codex. Remote writes are disabled by +default and require `AGENT_MEMORY_REMOTE_ENABLED=true` plus an endpoint. +`remember` records lineage, not session takeover. Raw histories and local paths +stay private; publish only intentionally selected, minimized evidence. diff --git a/OPERATIONS.md b/OPERATIONS.md new file mode 100644 index 0000000000..8d0695328d --- /dev/null +++ b/OPERATIONS.md @@ -0,0 +1,608 @@ +# Operations for Aesthetic Computer + +Task reference; shared rules live in [SCORE.md](SCORE.md). Read the section needed for the current task. + +## Front Door + + +359 built-in pieces (341 JS + 18 KidLisp), ~90 API endpoints.
+2812 registered handles, 265 user-published pieces, 4429 paintings, 16779 KidLisp programs, 18107 chat messages, 20 prints ordered.
+*Last refreshed: Mar 16, 2026* + + +Visit https://aesthetic.computer β€” press the top left of the screen or type any key to activate the prompt. + +Enter names of built-in pieces like `notepat`, `boyfriend`, or `list` for a scrollable index. User-published pieces live at handles like `@bash/hub`. + +Every piece is URL addressable (e.g. https://aesthetic.computer/notepat). Generate QR codes with `share notepat`. + +**Getting started:** +1. Enter `imnew` to register +2. Verify your email +3. Set a @handle via `handle your-name` +4. Enter `chat` to say hi + +**Recipes:** See [USER-GUIDE.md](USER-GUIDE.md) for making paintings, playing melodies, and joining the community. + +**Links:** +- **Tangled** (new home): https://tangled.org/aesthetic.computer/core +- **GitHub** (deprecating): https://github.com/whistlegraph/aesthetic-computer +- **No Paint (predecessor)**: https://nopaint.art ([HN 2020](https://news.ycombinator.com/item?id=23546706)) +- **Notepat on HN**: https://news.ycombinator.com/item?id=41526754 + +> We are migrating from GitHub to [Tangled](https://tangled.org), a decentralized code hosting platform built on AT Protocol. Our repo now lives on a self-hosted knot at `knot.aesthetic.computer` under the same ATProto identity (`did:plc:k3k3wknzkcnekbnyde4dbatz`) that powers our PDS, user handles, and federated content. GitHub will be maintained as a read-only mirror during the transition. + +--- + +## Back Door + +### Architecture + +**Frontend (system/)** +- `system/public/aesthetic.computer/` β€” Web client (Canvas + WebGL) + - `bios.mjs` β€” Core runtime, loads pieces + - `boot.mjs` β€” System initialization + - `disk.mjs` β€” Piece loader and lifecycle + - `disks/*.mjs` β€” Individual pieces (programs) + - `lib/*.mjs` β€” Shared libraries and utilities + +**Backend** +- `session-server/` β€” Real-time multiplayer (Socket.io + geckos.io UDP). Hosted on its own DigitalOcean VPS; deployed via `fish session-server/deploy.fish` (sshes to the box, `git pull` from github main, restart node `session.mjs`). Houses `arena-manager.mjs` which is the authoritative pmove + snapshot pipeline for `disks/arena.mjs`. +- `lith/` β€” Production monolith deploy (Express + Caddy on a DigitalOcean VPS, pulled from the tangled knot `git@knot.aesthetic.computer:aesthetic.computer/core` via `lith/deploy.fish`). Express adapts the handlers in `system/netlify/functions/` as routes β€” the `netlify/functions/` path is historical; Netlify is no longer the host. +- `lith/mirror/` β€” knot↔github bidirectional mirror (systemd timer, every 60s). Lets us push to knot only while still letting downstream consumers (session-server VPS, mirrors) pull from github. +- `help/bridge/` β€” Local Express bridge on @jeffrey's macbook that spawns the host `claude` CLI and streams it as SSE; reached publicly through `help.aesthetic.computer` via the existing droplet proxy + autossh reverse tunnel. Powers the `aa` piece (admin-only phone-side chat with the macbook's claude). Auto-runs under launchd as `computer.aesthetic.aa-bridge` and `computer.aesthetic.aa-tunnel`. +- Authentication and data storage + +**Arena auto-deploy (post-commit hook)** β€” Any commit on `main` that touches `disks/arena.mjs`, `lib/{arena-world,pmove,cam-doll}.mjs`, `session-server/{arena-manager,session}.mjs`, or `shared/` triggers a paired deploy in the background: +1. push HEAD to knot (`origin`) so lith pulls the right commit +2. `fish lith/deploy.fish` β€” pulls knot, restarts the monolith +3. wait ~75s for the knotβ†’github mirror +4. `fish session-server/deploy.fish` β€” VPS pull from github + node restart + +This is critical because `lib/pmove.mjs` is shared physics: client (lith) and server (session-server) MUST match, otherwise `arena-manager.mjs`'s reconciler will fight the client's prediction and movement glitches. Logs land in `.git/arena-auto-deploy.log`. Set `AC_NO_AUTO_DEPLOY=1` to skip (e.g. while bisecting). + +**Languages** +- `kidlisp/` β€” KidLisp dialect (Lisp for generative art) + - `compiler.mjs` β€” Parser and compiler + - `spec/*.mjs` β€” Test specs + +**Desktop (ac-electron/)** +- `ac-electron/main.js` β€” Electron main process: tray, menubar/notepat modes, multi-window orchestration, native bridges, auto-update. +- `ac-electron/preload.js` + `webview-preload.js` β€” Bridges between main process and the AC web client running in the embedded webview. +- `ac-electron/renderer/` β€” Native chrome (preferences, flip-view, notepat overlay) rendered outside the web client. +- `npm start` β€” Run against `https://aesthetic.computer` (prod) or `http://localhost:8888` (dev with `--dev`). +- `npm run build:mac` / `build:win` / `build:linux` β€” electron-builder packaging. +- `npm run release:host:mac` β€” Notarized signed mac build published to `silo.aesthetic.computer/desktop/`. +- **File associations:** mp3/wav/flac/ogg/m4a drop on the Dock icon (or onto a running window) opens the `play` piece. A loopback http server (`127.0.0.1:`) streams the file with HTTP Range so scrubbing works. + +**AC Electron Backlog:** +- [x] Drag-and-drop mp3 onto app icon launches `play` piece via loopback streaming server +- [ ] Same drop pipeline for paintings (`.png`/`.gif`) β†’ open in `nopaint` +- [ ] Same drop pipeline for `.lisp` source β†’ load directly as a piece (`source piece-name`) +- [ ] Multi-file drop: queue tracks in `play` as an ad-hoc playlist +- [ ] Promote `system.droppedFile` to a generic `system.droppedFiles[]` API so any piece can accept its own MIME types + +**Bare Metal OS (fedac/native/)** +- Routine OTA releases: `ac-os oven`; pushing to main does not queue a build. +- Build, publish, download and physical flash are separate results. Follow + [fedac/native/SCORE.md](fedac/native/SCORE.md) for the requested release lane. +- Local `ac-os upload` rebuilds before upload; it is a fallback, not the routine OTA path. + +**AC Native Backlog:** +- [ ] Per-user wifi credential storage: move hardcoded SSIDs out of JS pieces into per-handle config (e.g. `config.json` or `/mnt/wifi_creds.json` on USB). Each user's build should bundle their saved networks, not @jeffrey's home wifi. +- [ ] Wifi cred persistence across OTA updates: saved networks on USB should survive re-flashing. +- [ ] Geo-aware greeting: use `geo` piece's IP location for dynamic "enjoy [city]!" instead of hardcoded "Los Angeles". +- [x] Claude native binary: switched to native binary (225MB ELF, no Node.js needed) +- [x] Claude OAuth: using device-code auth method, loopback interface enabled +- [ ] Session log upload to machines: on wifi connect + shutdown, upload ac-native.log to machines API (keyed by machine-id). View live/historical logs per device on machines dashboard. +- [ ] Live log streaming: WebSocket pipe from device β†’ machines dashboard for real-time debug +- [ ] A/B kernel slots with auto-rollback: if boot doesn't reach "healthy" checkpoint in 60s, swap .prev kernel back +- [ ] Terminal: full Unicode font support (bitmap glyphs for box drawing, block elements) +- [ ] KidLisp GPU compositing: render effects on GPU buffer, recompose with CPU renderer + +**Host Tooling (slab/)** β€” @jeffrey's macOS host, not a deployed service +- **Cleaner** (`toolchain/macos/cleaner.sh`) β€” canonical safe fleet-Mac disk + cleanup. β€œCall the cleaner” means run `cleaner --apply`; plain `cleaner` is + report-only, and APFS snapshot thinning remains separately opt-in. Install + with `toolchain/macos/cleaner.sh --install`. Fleet MCP exposes `fleet_cleaner`. +- **Unipointer** (`slab/deskflow-handoff/UNIPOINTER.md`) β€” canonical identifier + for the Fuser seat's one logical pointer. Neo and Blueberry can exchange the + physical Deskflow controller role without changing the unipointer's active + machine or display-local position. Code and agents should use the literal + identifier `unipointer` and state-record kind `unipointer-state`; every fleet + host exposes `~/.local/bin/unipointer` for versioned JSON state. +- `slab/menubar-swift/` β€” native Swift menubar daemon (launchd `computer.slab.menubar`). Shows live Claude-session status, themes each Terminal.app/iTerm2 window by session state (working/awaiting/complete), tiles all windows into one grid, tints the desktop, and serves passphrases over a unix socket. Built + installed locally with `slab/menubar-swift/install.sh` (`swift build -c release` β†’ universal arm64+x86_64 binary β†’ app bundle + launchd agent); there is no remote deploy. +- **Swift build budget** β€” `~/.local/bin/swift` is the host build guard (`toolchain/macos/swift-guard.sh`): shell-invoked `swift build` commands inside this repo serialize per Swift package, run at lower priority, and default to two compiler jobs on ≀16 GB hosts (an explicit `--jobs` still wins). For Slab Menubar iteration use `slab/menubar-swift/build-dev.sh`; run its `install.sh` only for the final release install. Do not bypass the guard with `/usr/bin/swift build` unless deliberately diagnosing the guard itself. +- **Media render budget** β€” `~/.local/bin/{ffmpeg,ffprobe}` point at the repo QoS shims in `toolchain/shims/`, so shell/agent media work runs at utility priority and yields to the interactive UI under contention. Do not invoke the Homebrew binaries by absolute path unless deliberately bypassing QoS for a benchmark. +- **Tiling auto-fits the type:** `tileNow` sizes each Terminal window's font to the grid (Far/Near/Tiny modes) and drives `View β–Έ Default Font Size` per window so a live window actually adopts it β€” a per-window zoom otherwise silently overrides the profile font and is invisible to AppleScript. Floors keep it legible (Far 10 / Near 9 / Tiny 8). iTerm2 has no AppleScript font property, so it tiles by bounds only. +- **Prompt rocks** (`slab/menubar-swift/Sources/SlabMenubar/PromptSigilOverlay.swift`) β€” the tumbling little stones parked at the top-right of each terminal window, one per live Claude session. Each rock is a 3D sigil rendered from the session's `sessionId + prompt` seed (so it re-forms when the session moves to a new prompt), lit by a shared global sun that tracks local time of day, wearing a pet name in bubble lettering. Its *motion* is the status channel β€” spin speed and direction encode working/awaiting/complete; being read by a peer makes it blink and rattle. Hovering one reveals a bubble summarizing the prompt (a cached one-line `claude -p haiku` inference). They're borderless click-through `.floating` windows, so hit-testing has to check occlusion by hand: a rock only answers the pointer while it's on screen *and* its terminal is still the topmost normal window under the cursor. +- **Prompt rocks MCP** (`slab/bin/prox-mcp.mjs`, registered as `prox` in `.mcp.json`) β€” an MCP over the fleet handle ledger (`Ledger.swift`; `~/.config/slab/ledger/{local,peers/*}.json`, served per-machine on tailnet-only `:5252`) so any agent can `prox_list` every live session across machines, `prox_find` a `host:name` reference (e.g. `neo:regif`) to its status/subject/cwd/seed, `prox_poke` one (`POST /poke` β†’ the target rock blinks + rattles), `prox_wake` a local one with a bounded steering prompt, send `prox_artifact_ready` the output paths from an asynchronous render, and `prox_launch` a new Claude/Codex Terminal on a prompt host. Wake uses the same poke + TTY reactivation pattern as Loopboy; `prox_artifact_ready` supplies the standard inspect/iterate/integrate/continue prompt itself. Launch is deliberately not a remote shell: the target accepts only those two fixed agents, a bounded prompt, and a cwd beneath that user's home. `prox_close` reads the session marker for tty+pid and closes locally only (refuses the calling session). This is how a `machine:promptname` handle resolves without an SSH crawl. +- **Loopboy** (`~/.config/slab/loopboy.json`, surfaced in the Slab menubar) β€” the primary client-loop interface. Each route maps one private iMessage contact key to one stable local prox session; new inbound messages poke and optionally wake only that contact's rock. Loopboy never replies by itself. Armed rocks spin faster, glow pink, and identify themselves as Loopboy on hover. Create or replace a route with `prox_bind_notification(handle, contact)`. +- **Paper MCP / `/papers` stack** (`slab/bin/paper-mcp.mjs`, registered as `paper`) β€” β€œuse the papers stack,” β€œuse `/papers`,” and similar requests name the studio's scholarly publishing workflow, not merely a request to export or prettify a PDF. Begin by consulting [`papers/SCORE.md`](papers/SCORE.md), the public Platter index, relevant sub-platters, prior papers and bibliographies, and the underlying code/data/evidence. Unless the user names another mill lane, shape the result as an archival/arXiv-style LaTeX paper: title/byline/date, abstract, problem and context, related work, system or method, implementation, evidence/evaluation, ethics/privacy/limitations, conclusion, references, numbered/captioned figures and tables, and reproducible source/assets. Briefings, decks, cards, dossiers, and visual reports are distinct lanes and require explicit intent or strong task evidence. The MCP is the transport/build/inspection layer: `paper_list`/`paper_find` locate precedent, `paper_read` supports the Platter consult, `paper_build` runs XeLaTeX or Tectonic, `paper_figure_table_qa_check` supports mandatory visual inspection, and `paper_open` raises the result through `slab-pdf`. The shared loopback daemon is `:7777`; `toolchain/mcp/install-daemons.sh` registers it for Claude and Codex. A build may pass `notifyHandle` to return its PDF to the originating rock through `prox_artifact_ready`. +- `slab/bin/ac-passphrase` β€” pinentry-free secret fetch from the daemon (see Development Environment below). + +**Other Projects** +- `tezos/` β€” NFT/blockchain experiments +- `grab/` β€” Media utilities +- `feed/` β€” RSS/content feeds + +### How to Run + +**Start the dev server:** +```bash +npm start +# Visit http://localhost:8888 +``` + +**Run all tests:** +```bash +npm test +``` + +**Run KidLisp tests:** +```bash +npm run test:kidlisp +# Or filter: npm run test:kidlisp -- --filter= +``` + +### Adding a Piece + +Every piece is a single `.mjs` (JS) or `.lisp` (KidLisp) file in +[`system/public/aesthetic.computer/disks/`](system/public/aesthetic.computer/disks/). +Scaffold from the template: + +```bash +npm run new "one-line description" +``` + +**Header convention** β€” the docs auto-scan reads lines 1–2: + +```js +// Name, YY.MM.DD.HH.MM +// One-line description shown in `list` and prompt autocomplete. +``` + +**Lifecycle exports** (all optional except whichever ones you need): +`boot`, `paint`, `sim`, `act`, `leave`. Export `meta()` to opt the piece **into** +`list` and prompt autocomplete β€” omit it and the piece stays reachable at +`/` but hidden from indexes (good for drafts). + +**Curating the entry** (richer `desc`, `colon` params/`examples`, force +`hidden: true`, or override an auto-entry): edit the `pieces` map in +[`system/netlify/functions/docs.js`](system/netlify/functions/docs.js). Curated +entries always win over the auto-scan. + +See [`WRITE-A-PIECE.md`](WRITE-A-PIECE.md) for the end-user `source` / `publish` +flow and [`disks/CLAUDE.md`](system/public/aesthetic.computer/disks/CLAUDE.md) for the full piece API surface. + +### Development Environment + +The global environment model lives in [`ENVIRONMENT.md`](ENVIRONMENT.md). Resolve +the active environment before applying editor- or host-specific instructions. +An ordinary Claude Terminal session on macOS is `terminal-macos`; it should use +its local shell directly and should not describe Emacs/fishy as a missing bridge +or fallback. + +#### `[environment: emacs]` Emacs Terminal Buffers + +When Emacs is the active environment, use Emacs MCP tools (`mcp_emacs_*`) and +the named terminal buffers. The `🐟-fishy` buffer is the primary shell: + +- `🐟-fishy` β€” Main fish shell (use this for all commands!) +- `🌐-site` β€” Site/web server logs +- `πŸ“‹-session` β€” Session server logs +- `πŸ§ͺ-kidlisp` β€” KidLisp test runner +- `πŸ”΄-redis` β€” Redis logs +- `πŸ“Š-top` β€” System monitoring +- `πŸš‡-tunnel` β€” Tunnel logs +- (See AGENTS.md.backup for full list) + +**How to run commands in fishy (Emacs environment only):** +1. Use `mcp_emacs_emacs_switch_buffer` to switch to `🐟-fishy` +2. Use `mcp_emacs_emacs_send_keys` to send the command +3. Send newline to execute + +**Fish Shell Commands (`ac-*` helpers):** + +#### `[environment: emacs]` Emacs & Development Environment +- `ac-aesthetic` β€” Connect to aesthetic emacs UI (alias for `aesthetic-now`) +- `ac-emacs-restart` β€” Kill and restart emacs daemon +- `ac-emacs-full-restart` β€” Restart emacs and reconnect UI +- `ac-emacs-kill` β€” Kill emacs daemon +- `ac-emacs-status` β€” Check emacs daemon health +- `ac-emacs-logs` β€” View emacs logs +- `ac-emacs-health-check` β€” Verify emacs config loaded correctly +- `ac-restart` β€” Restart all AC tabs/processes (calls emacs `ac-restart`) +- `ac-crash-diary` β€” View emacs crash log +- `ac-emacs-crash-monitor` β€” Background process that monitors emacs + +#### Core Development +- `ac-artery` β€” Start artery development server +- `ac-artery-dev` β€” Start artery in dev mode +- `ac-site` β€” Start site server +- `ac-session` β€” Start session server +- `ac-url` β€” Get local tunnel URL +- `ac-views` β€” View stats +- `ac-watch` β€” Watch and rebuild (alias for `npm run watch`) +- `ac-repl` β€” Start REPL + +#### KidLisp Tools +- `ac-st` β€” KidLisp source tree viewer (`ac-st cow`, `ac-st $cow`, `ac-st cow --source`) + +#### Testing & Debugging +- `ac-test-tabs` β€” Test tab functionality +- `ac-diagnose` β€” Run diagnostics +- `ac-profile-start` β€” Start performance profiling +- `ac-profile-stop` β€” Stop performance profiling +- `ac-profile-report` β€” Generate profile report +- `ac-watch-cpu` β€” Monitor CPU usage +- `ac-dev-log` β€” View development logs +- `ac-dev-logs` β€” View all dev logs +- `ac-dev-log-clean` β€” Clean old logs +- `ac-dev-log-new` β€” Create new log +- `ac-piece-logs [slug]` β€” Recent piece-run telemetry (see [Piece-Log Debugging](#piece-log-debugging-client-side-errors)) +- `ac-piece-logs-events [slug]` β€” Include captured `console.log`/`warn`/`error` output +- `ac-piece-logs-errors` β€” Runs with `status=error` in the last 60 minutes +- `ac-piece-logs-grep ` β€” Search console-event text across recent runs + +#### Deployment & Distribution +- `ac-pack` β€” Package for distribution +- `ac-unpack` β€” Unpack distribution +- `ac-ship` β€” Deploy/ship changes +- `ac-keep` β€” Save state/backup +- `ac-keeps` β€” List saved states +- `ac-keep-test` β€” Test keep functionality + +#### Media & Recording +- `ac-tv` β€” TV mode +- `ac-record` β€” Start recording +- `ac-pix` β€” Image utilities +- `ac-media` β€” Media server + +#### Services & Infrastructure +- `ac-servers` β€” Start all servers +- `ac-tunnel` β€” Start tunnel +- `ac-chat-system` β€” Start chat system +- `ac-chat-sotce` β€” Start sotce chat +- `ac-chat-clock` β€” Start clock chat +- `ac-stripe-print` β€” Stripe print service +- `ac-stripe-ticket` β€” Stripe ticket service +- `ac-logger` β€” View lith backend logs (the handlers living under `system/netlify/functions/` run under lith's Express; the name is legacy) +- `ac-oven` β€” Oven service +- `ac-offline` β€” Offline mode + +#### Authentication & Tokens +- `ac-login` β€” Login to AC +- `ac-token` β€” Manage auth tokens +- `slab/bin/ac-passphrase