toes/CLAUDE.md
Claude eb8ef0dd4d
Rename stats to metrics and add data size metric
Rename the "stats" CLI command, tool app, and all internal references
to "metrics". Add file size tracking from each app's DATA_DIR as a new
metric, shown in both the CLI table and web UI.

https://claude.ai/code/session_013agP8J1cCfrWZkueZ33jQB
2026-02-12 15:28:20 +00:00

216 lines
7.1 KiB
Markdown

# Toes
Personal web appliance that auto-discovers and runs multiple web apps on your home network.
"Plug it in, turn it on, and forget about the cloud."
## How It Works
1. Server scans `APPS_DIR` for directories with a `package.json` containing a `scripts.toes` entry
2. Each app is spawned as a child process with a unique port (3001-3100)
3. Dashboard UI shows all apps with status, logs, and links via SSE
4. CLI communicates with the server over HTTP
## Tech Stack
- **Bun** runtime (not Node)
- **Hype** (custom HTTP framework wrapping Hono) from `@because/hype`
- **Forge** (typed CSS-in-JS) from `@because/forge`
- **Commander** + **kleur** for CLI
- TypeScript + Hono JSX
- Client renders with `hono/jsx/dom` (no build step, served directly)
## Running
```bash
bun run dev # Hot reload (deletes pub/client/index.js first)
bun run start # Production
bun run check # Type check
bun run test # Tests
```
## Project Structure
```
src/
server/ # HTTP server and process management ($)
client/ # Browser-side dashboard
shared/ # Types shared between server and client (@)
lib/ # Code shared between CLI and server (%)
cli/ # CLI tool
tools/ # @because/toes package exports
pages/ # Hype page routes
```
Path aliases: `$` = server, `@` = shared, `%` = lib (defined in tsconfig.json).
### Server (`src/server/`)
- `apps.ts` -- **The heart**: app discovery, process spawning, health checks, auto-restart, port allocation, log management, graceful shutdown. Exports `APPS_DIR`, `TOES_DIR`, `TOES_URL`, and the `App` type (extends shared `App` with process/timer fields).
- `api/apps.ts` -- REST API + SSE stream. Routes: `GET /` (list), `GET /stream` (SSE), `POST /:name/start|stop|restart`, `GET /:name/logs`, `POST /` (create), `DELETE /:name`, `PUT /:name/rename`, `PUT /:name/icon`.
- `api/sync.ts` -- File sync protocol: manifest comparison, push/pull with hash-based diffing.
- `index.tsx` -- Entry point. Mounts API routers, tool URL redirects (`/tool/:tool`), tool API proxy (`/api/tools/:tool/*`), initializes apps.
- `shell.tsx` -- Minimal HTML shell for the SPA.
- `tui.ts` -- Terminal UI for the server process (renders app status table when TTY).
### Client (`src/client/`)
Client-side SPA rendered with `hono/jsx/dom`. No build step -- Bun serves `.tsx` files directly.
- `index.tsx` -- Entry point. Initializes rendering, SSE connection, theme, tool iframes.
- `state.ts` -- Mutable module-level state (`apps`, `selectedApp`, `sidebarCollapsed`, etc.) with localStorage persistence. Components import state directly.
- `api.ts` -- Fetch wrappers for server API calls.
- `tool-iframes.ts` -- Manages tool iframe lifecycle (caching, visibility, height communication).
- `update.tsx` -- SSE connection to `/api/apps/stream` for real-time state updates.
- `components/` -- Dashboard, Sidebar, AppDetail, Nav, AppSelector, LogsSection.
- `modals/` -- NewApp, RenameApp, DeleteApp dialogs.
- `styles/` -- Forge CSS-in-JS (themes, buttons, forms, layout).
- `themes/` -- Light/dark theme token definitions.
### CLI (`src/cli/`)
- `index.ts` -- Entry point (`#!/usr/bin/env bun`).
- `setup.ts` -- Commander program definition with all commands.
- `commands/` -- Command implementations.
- `http.ts` -- HTTP client for talking to the toes server.
- `name.ts` -- App name resolution (argument or current directory).
- `prompts.ts` -- Interactive prompts.
- `pager.ts` -- Pipe output through system pager.
CLI commands:
- **Apps**: `list`, `info`, `new`, `get`, `open`, `rename`, `rm`
- **Lifecycle**: `start`, `stop`, `restart`, `logs`, `metrics`, `cron`
- **Sync**: `push`, `pull`, `status`, `diff`, `sync`, `clean`, `stash`
- **Config**: `config`, `env`, `versions`, `history`, `rollback`
### Shared (`src/shared/`)
Types shared between browser and server. **Cannot use Node/filesystem APIs** (runs in browser).
- `types.ts` -- `App`, `AppState`, `LogLine`, `Manifest`, `FileInfo`
- `gitignore.ts` -- `.toesignore` pattern matching
### Lib (`src/lib/`)
Server-side code shared between CLI and server. Can use Node APIs.
- `templates.ts` -- Template generation for `toes new` (bare, ssr, spa)
- `sync.ts` -- Manifest generation, hash computation
### Tools Package (`src/tools/`)
The `@because/toes` package that apps/tools import. Published exports:
- `@because/toes` -- re-exports from server (`src/index.ts` -> `src/server/sync.ts`)
- `@because/toes/tools` -- `baseStyles`, `ToolScript`, `theme`, `loadAppEnv`
### Pages (`src/pages/`)
Hype page routes. `index.tsx` renders the Shell.
## Key Concepts
### App Lifecycle
States: `invalid` -> `stopped` <-> `starting` -> `running` -> `stopping` -> `stopped`
- Discovery: scan `APPS_DIR`, read each `package.json` for `scripts.toes`
- Spawn: `Bun.spawn()` with `PORT`, `APPS_DIR`, `TOES_URL`, `TOES_DIR`, plus per-app env vars
- Health checks: every 30s to `/ok`, 3 consecutive failures trigger restart
- Auto-restart: exponential backoff (1s, 2s, 4s, 8s, 16s, 32s), resets after 60s stable run
- Graceful shutdown: SIGTERM with 10s timeout before SIGKILL
### Tools vs Apps
Tools are apps with `"toes": { "tool": true }` in package.json. From the server's perspective they're identical processes. The dashboard renders tools as iframe tabs instead of sidebar entries. Tool URLs redirect through the server: `/tool/:tool?app=foo` -> `http://host:toolPort/?app=foo`.
### Versioning
Apps live at `APPS_DIR/<name>/` with timestamped version directories and a `current` symlink. Push creates a new version; rollback moves the symlink.
### Environment Variables
Per-app env files in `TOES_DIR/env/`:
- `_global.env` -- shared by all apps
- `<appname>.env` -- per-app overrides
The server sets these on each app process: `PORT`, `APPS_DIR`, `TOES_URL`, `TOES_DIR`, `DATA_DIR`.
### SSE Streaming
`/api/apps/stream` pushes the full app list on every state change. Client reconnects automatically. The `onChange()` callback system in `apps.ts` notifies listeners.
## Coding Guidelines
TS files should be organized in the following way:
- imports
- re-exports
- const/lets
- enums
- interfaces
- types
- classes
- functions
- module init (top level function calls)
In each section, put the `export`s first, in alphabetical order.
Then, after the `export`s (if there were any), put everything else,
also in alphabetical order.
For single-line functions, use `const fn = () => {}` and put them in the
"functions" section of the file.
All other functions use the `function blah(){}` format.
Example:
```ts
import { code } from "coders"
import { something } from "somewhere"
export type { SomeType }
const RETRY_TIMES = 5
const WIDTH = 480
enum State {
Stopped,
Starting,
Running,
}
interface Config {
name: string
port: number
}
type Handler = (req: Request) => Response
class App {
config: Config
constructor(config: Config) {
this.config = config
}
}
const isApp = (name: string) =>
apps.has(name)
function createApp(name: string): App {
const app = new App({ name, port: 3000 })
apps.set(name, app)
return app
}
function start(app: App): void {
console.log(`Starting ${app.config.name}`)
}
```
## Writing Apps and Tools
See `docs/CLAUDE.md` for the guide to writing toes apps and tools.