Monorepo for Aesthetic.Computer
aesthetic.computer
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5## Project Overview
6
7Aesthetic 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.
8
9## Agent Memory (Local-First)
10
11When @jeffrey is working, Claude hook events are written to a local encrypted memory store first.
12
13- **Hook**: `.claude/settings.json` → `UserPromptSubmit` → `node memory/hook.mjs`
14- **Git hook**: `.githooks/post-commit` → commit log + Codex import + remote flush
15- **Local store**: `~/.ac-agent-memory` (overridable via `AGENT_MEMORY_HOME`)
16- **Encryption**: AES-256-GCM (`AGENT_MEMORY_KEY` optional; local key file otherwise)
17- **Redaction**: metadata and summaries are redacted before indexing/sync
18- **CLI**: `node memory/cli.mjs` (`list`, `remember`, `checkpoint`, `doctor`, `profile`, `flush-remote`)
19- **Codex import**: `node memory/codex-sync.mjs`
20
21Remote writes are optional and disabled by default:
22
23- `AGENT_MEMORY_REMOTE_ENABLED=true` + `AGENT_MEMORY_REMOTE_URL=...` enables queued remote sync
24
25`remember` continuity is represented as lineage (`remembered_from`) instead of taking over a live mutable session.
26
27## AestheticAnts & Score.md
28
29This repository uses an automated maintenance system called "AestheticAnts" (AA). The main score lives at `SCORE.md`, and ant-specific mindset/rules live in `ants/mindset-and-rules.md`. Read both before contributing.
30
31**Important:** Do not modify `ants/mindset-and-rules.md` unless you are the queen (@jeffrey).
32
33## Development Commands
34
35### Running the Development Environment
36
37The system consists of multiple servers that run concurrently:
38
39```bash
40# Run all servers (site, session, edge, stripe) - primary dev command
41npm run aesthetic
42# or shorthand:
43npm run ac
44
45# Run individual servers:
46npm run site # Main dev server (port 8888)
47npm run server:session # Session backend (port 8889)
48npm run stripe # Stripe webhook listener
49```
50
51### Testing
52
53```bash
54# Run all tests
55npm test
56
57# KidLisp tests (with auto-reload on changes)
58npm run test:kidlisp
59
60# KidLisp tests (direct, no watch)
61npm run test:kidlisp:direct
62
63# Performance tests
64npm run test:perf
65npm run test:perf:chrome
66npm run test:perf:lighthouse
67```
68
69### Creating New Pieces
70
71```bash
72# Generate a new piece from the blank.mjs template
73npm run new piece-name "Description of the piece"
74```
75
76### Working with Sessions
77
78```bash
79# List active session backends
80npm run session:alive
81
82# View logs for a specific session
83npm run server:session:logs SESSION_ID
84
85# Reset/terminate a session
86npm run session:reset SESSION_ID
87
88# Deploy session server
89npm run session:publish
90```
91
92### Assets Management
93
94```bash
95# Sync assets down from Digital Ocean Spaces
96npm run assets:sync:down
97
98# Sync assets up to Digital Ocean Spaces
99npm run assets:sync:up
100```
101
102### AC Native OS (fedac/native/)
103
104**Routine OTA releases are built remotely on the oven.** When fedac/native/
105changes land on `origin/main`, oven's git poller auto-triggers a build. To
106trigger or observe a build explicitly:
107
108```bash
109ac-os oven # Trigger remote OTA build for HEAD
110ac-os oven status # Show oven queue + recent builds
111ac-os oven watch # Tail active build logs (SSE)
112ac-os oven cancel # Cancel active oven job
113```
114
115**Use `ac-os oven` for OTA releases — not `ac-os upload`.** `ac-os upload`
116is a local-build-and-push fallback that requires a clean tree and has
117historically auto-stashed uncommitted work in ways that strand changes.
118
119Local-only commands (rarely needed for OTA):
120
121```bash
122# Full build pipeline: binary → initramfs → kernel
123ac-os build
124
125# Build + flash USB
126ac-os flash
127
128# Build + upload OTA release (legacy local path — prefer `ac-os oven`)
129ac-os upload
130
131# Build + flash + upload
132ac-os flash+upload
133```
134
135**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.
136
137### Notation
138
139- **compush** - commit & push
140
141## Architecture
142
143### Core Components
144
1451. **Boot System** (`system/public/aesthetic.computer/boot.mjs`)
146 - Entry point that loads the BIOS and initializes the runtime
147 - Manages service worker registration for module caching
148 - Handles WebSocket module loader for hot reloading
149 - Boot telemetry system logs to `/api/boot-log`
150
1512. **BIOS** (`system/public/aesthetic.computer/bios.mjs`)
152 - Main runtime coordinator
153 - Manages piece lifecycle (boot, act, paint, sim, etc.)
154 - Provides the API surface for pieces
155 - Handles routing and navigation
156
1573. **Disk System** (`system/public/aesthetic.computer/lib/disk.mjs`)
158 - Large module (~572KB) that provides the core API for pieces
159 - Graphics primitives, audio, input handling, UI components
160 - All pieces interact with AC through the Disk API
161
1624. **Module Loader** (`system/public/aesthetic.computer/module-loader.mjs`)
163 - WebSocket-based dynamic module loading
164 - Enables hot reloading during development
165 - Prefetches commonly used modules
166
167### Pieces
168
169Pieces are the fundamental unit of content in AC. Each piece is a single `.mjs` or `.lisp` file located in `system/public/aesthetic.computer/disks/`.
170
171#### Piece Structure (JavaScript)
172
173Pieces export lifecycle functions that receive an API object:
174
175```javascript
176// boot: runs once when the piece loads
177function boot({ wipe, screen, params, colon, api }) {
178 // Initialize state
179}
180
181// paint: runs every frame
182function paint({ wipe, ink, line, circle, screen }) {
183 // Render graphics
184}
185
186// act: handles user input and events
187function act({ event: e }) {
188 if (e.is("keyboard:down:space")) {
189 // Handle spacebar press
190 }
191}
192
193// sim: simulation/game logic that runs every frame
194function sim() {
195 // Update game state
196}
197
198export { boot, paint, act, sim };
199```
200
201Common lifecycle functions:
202- `boot` - Initialization, runs once
203- `paint` - Rendering, runs every frame
204- `act` - Event handling (input, network, etc.)
205- `sim` - Simulation/logic, runs every frame
206- `leave` - Cleanup when exiting the piece
207- `preview` - Static preview image generation
208
209#### Piece API Surface
210
211The API is provided through function parameters. Common APIs:
212- **Graphics**: `wipe`, `ink`, `line`, `box`, `circle`, `plot`, `paste`, etc.
213- **Text**: `write`, `type`, `paste`, `help`
214- **Input**: `event`, `pen`, `hand`, `gamepad`
215- **Audio**: `sound`, `speaker`, `microphone`
216- **UI**: `ui.Button`, `ui.TextInput`, `cursor`
217- **System**: `screen`, `params`, `colon`, `store`, `net`, `jump`, `send`
218
219### Servers and Services
220
2211. **System Server** (`system/` + `lith/`)
222 - Production: **lith** monolith — Express + Caddy on a DigitalOcean VPS (`lith.aesthetic.computer`). Deployed with `fish lith/deploy.fish`, which pulls from the tangled knot `git@knot.aesthetic.computer:aesthetic.computer/core`. Netlify is no longer the host.
223 - Dev: `npm run site` (port 8888)
224 - Backend function handlers live in `system/netlify/functions/` — the path is historical, but lith's Express adapts each file as a route, so keep adding new endpoints there.
225 - Handles piece serving, authentication, storage APIs
226
2272. **Session Server** (`session-server/`)
228 - Per-session backend using Jamsocket for ephemeral deployments
229 - WebSocket server using Geckos.io for real-time communication
230 - Manages chat, multiplayer state, and real-time features
231 - Uses Redis for state synchronization
232
2333. **Feed Server** (`dp1-feed/`)
234 - Cloudflare Worker for activity feeds
235 - Deployed separately to Cloudflare Workers
236
237### KidLisp
238
239KidLisp is a minimal Lisp dialect for generative art, with 118 built-in functions across 12 categories. See `kidlisp/README.md` for comprehensive documentation.
240
241**Key files:**
242- `system/public/aesthetic.computer/lib/kidlisp.mjs` - Main evaluator
243- `system/netlify/functions/store-kidlisp.mjs` - Storage API
244- `kidlisp/tools/` - Analysis and debugging tools
245
246**Running KidLisp tools:**
247```bash
248# Analyze piece structure (requires dev server running)
249./kidlisp/tools/source-tree.mjs $cow
250
251# Get source code
252./kidlisp/tools/get-source.mjs $piece-code
253```
254
255### Data Storage
256
257- **MongoDB**: User data, handles, chat messages, moods
258- **Redis**: Session state, real-time coordination
259- **Firebase**: Authentication, cloud messaging
260- **Digital Ocean Spaces**: Asset storage (CDN)
261
262### Routing and URLs
263
264- Pieces are URL-addressable: `https://aesthetic.computer/piece-name`
265- Parameters: `piece-name:param1:param2`
266- User pieces: `@handle/piece-name`
267- QR code sharing: `share piece-name`
268
269## Development Workflow
270
271### Local Development in Codespaces
272
273When running in GitHub Codespaces, the server is accessible at:
274```
275https://{CODESPACE_NAME}-8888.app.github.dev
276```
277
278Get your Codespace name: `echo $CODESPACE_NAME`
279
280### Hot Reloading
281
282The module loader provides hot reloading during development:
283- Piece changes are reflected immediately when saved
284- WebSocket connection shown in boot canvas
285- Use `channel custom-name` for multi-device testing
286
287### Publishing
288
289```bash
290# In the AC prompt:
291publish # Publish current piece
292publish piece-name # Publish with custom name
293source # Download blank template
294source piece-name # Fork existing piece
295```
296
297## Important Directories
298
299- `system/public/aesthetic.computer/disks/` - All pieces (.mjs and .lisp files)
300- `system/public/aesthetic.computer/lib/` - Shared libraries and utilities
301- `system/netlify/functions/` - Serverless backend functions
302- `session-server/` - Real-time session backend
303- `shared/` - Code shared between system and session servers
304- `kidlisp/` - KidLisp language documentation and tools
305- `spec/` - Jasmine tests for KidLisp
306- `ants/` - AestheticAnts automated maintenance system
307
308## Key Patterns
309
310### Event Handling in Pieces
311
312Events use a string-based pattern matching system:
313```javascript
314event.is("keyboard:down:a") // 'a' key pressed
315event.is("touch") // Any touch event
316event.is("lift") // Touch/click released
317event.is("draw") // Drag with pen down
318event.is("keyboard:down:arrowup") // Arrow key
319```
320
321### State Management
322
323Pieces maintain state in module-level variables:
324```javascript
325let score = 0;
326let enemies = [];
327
328function boot() {
329 // Initialize state
330}
331
332function sim() {
333 // Update state
334 score += 1;
335}
336```
337
338### API Requests from Pieces
339
340Use the `net` API for HTTP requests:
341```javascript
342function boot({ net }) {
343 net.pieces("@user/list").then((data) => {
344 // Handle response
345 });
346}
347```
348
349### Multiplayer Networking (Dual-Channel Pattern)
350
351Multiplayer pieces use both WebSocket (reliable) and UDP (low-latency) channels. See `squash.mjs` for the canonical implementation.
352
353```javascript
354function boot({ net: { socket, udp }, handle }) {
355 // UDP for high-frequency position sync (low latency, may drop packets)
356 udpChannel = udp((type, content) => {
357 if (type === "game:move") {
358 const d = typeof content === "string" ? JSON.parse(content) : content;
359 // Update remote player position
360 }
361 });
362
363 // WebSocket for reliable game events (join/leave, scoring, round control)
364 server = socket((id, type, content) => {
365 if (type.startsWith("connected")) { /* joined session */ }
366 if (type === "left") { /* player disconnected */ }
367 if (type === "game:join") { /* another player joined */ }
368 if (type === "game:score") { /* reliable score event */ }
369 });
370
371 // Send reliable event
372 server.send("game:join", { handle: handle() });
373 // Send low-latency position update
374 udpChannel.send("game:move", { x, y, vx, vy });
375}
376```
377
378**Session server routing:**
379- UDP handlers: add `channel.on("game:move", ...)` in geckos section of `session-server/session.mjs`
380- WebSocket: position messages use `others()` (relay to all except sender), game events use `everyone()` (catch-all relay)
381- Chat invites: typing `'piece-name'` in chat creates a clickable link to join
382
383**Reference pieces:** `squash.mjs` (2D platformer), `1v1.mjs` (3D FPS), `udp.mjs` (minimal UDP test)
384
385### UI Components
386
387```javascript
388function boot({ ui: { Button, TextInput } }) {
389 const btn = new Button("Click me", { box: [10, 10, 100, 40] });
390}
391
392function act({ event: e }) {
393 if (btn.trigger(e)) {
394 // Button was clicked
395 }
396}
397```
398
399## Notes
400
401- The codebase uses `.mjs` extension for ES modules
402- Prefer `const` destructuring for API parameters to minimize imports
403- Graphics operations are immediate-mode (no retained scene graph)
404- All coordinates are in pixels
405- Default color depth is 8-bit RGB (0-255 per channel)
406- The `wipe` function clears the screen and should be called first in `paint`
407- When making changes, consult `ants/mindset-and-rules.md` for the ant operating philosophy