Monorepo for the Roomy personal AI assistant.
packages/server/— API, DB, storage, runtime, scheduler, sandbox CLI, setup scripts.packages/app/— React web UI (no CSS, semantic HTML) + Playwright e2e.packages/cli/— host CLI (roomybinary).
The full stack runs on the host — no VM, no systemd. The roomy-server
process serves the API on :35139; the Vite dev server proxies /api/*
calls to it on :5174. A published install serves API + app together on :35138. Sandbox containers spawn through the host's
container runtime — docker if present, otherwise nerdctl
(containerd) — both detected. State lives under ~/Roomy/.
- Node.js 23.x (pinned by
.nvmrc+engines). Use volta/fnm/nvm/mise/asdf. - A container runtime on the host. Either:
- Docker — Linux rootful, Linux rootless, or macOS Docker Desktop
(auto-detected via the
dockerCLI), OR - nerdctl + containerd — useful on hosts that already run
containerd-rootless(Lima, Rancher Desktop, standalone) wheredockerd-rootlesscan't coexist. nerdctl ≥ 2.0 recommended. Override the auto-pick withROOMY_CONTAINER_ENGINE=docker|nerdctl.
- Docker — Linux rootful, Linux rootless, or macOS Docker Desktop
(auto-detected via the
- API keys are configured per-user via Settings after the first sign-in.
npm install --include=optional
npm run dev
# npm run dev builds or refreshes the roomy/sandbox:v1 image when needed.
# To force-skip that step, set ROOMY_SKIP_SANDBOX_BUILD=1.| Command | What it does |
|---|---|
npm run dev |
Boot roomy-server (tsx watch) on :35139 + Vite on :5174; also prepares missing built-in app bundles and refreshes the sandbox image when needed. Ctrl+C stops both. |
npm run dev:app |
Vite only — useful when roomy-server is running elsewhere. |
npm run build |
All workspace packages via Nx. |
npm run typecheck |
tsc on all workspaces. |
npm run test:host |
Vitest unit + integration tests. |
npm run test:e2e |
Playwright against a spawned roomy-server + Vite preview. |
- api/ — HTTP + WS server (entry: api/src/main.ts)
- db/ — SQLite migrations + typed queries
- storage/ — file layout under
~/Roomy/ - runtime/ — sandbox lifecycle (engine.ts → docker/nerdctl) +, pi-driven
- scheduler/ — runs + at/crontab adapter + reconcile
- sandbox-cli/ — the
roomy-agentbinary installed inside sandboxes - shared/ — cross-package types + Zod schemas
- setup/ —
dev.shhost launcher - docs/ — architecture, dev environment, OpenAPI spec, Postman collection, plans
TOKEN=$(curl -s -X POST http://127.0.0.1:35138/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"roomy@roomy.local","password":"change-me"}' | jq -r .token)
curl -s http://127.0.0.1:35138/me -H "Authorization: Bearer $TOKEN"The Postman collection at
packages/server/docs/roomy-api.postman_collection.json
covers every route in api/src/app.ts.
roomy-serverexits immediately: check~/Roomy/roomy.dbfor stale state, and inspect the logs. Migrations run idempotently on every boot — a corrupt schema row from a partial earlier run can wedge them.dockercommands fail with "permission denied" on the host: wrap withsg docker -c "...", or log out and back in once afterusermod -aG docker $USER.No container runtime availableon boot: the runtime probesdocker infoandnerdctl infoin that order; install one of them, or setROOMY_CONTAINER_ENGINE=docker|nerdctlif both are present and you want to force a choice. nerdctl rootless typically also needsXDG_RUNTIME_DIRexported (the runtime defaults to/run/user/$UIDif you don't set it).dockerd-rootlesswon't start becausecontainerd-rootlessis already running: rootlesskit only supports one user-namespace per uid, so the two daemons can't coexist. Use the one that's already running (the runtime will pick it up automatically), stop the other one, or run rootful Docker (sudo systemctl start docker).- Sandbox bind-mount writes fail under a rootless runtime: the
runtime detects rootless mode and runs agent commands as UID 0 (which
maps to the daemon's host uid). If it ever doesn't, set
ROOMY_SANDBOX_USER=0:0to pin the override.
See docs/dev-environment.md for more depth.