diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9b5c54a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,190 @@ +# AGENTS.md + +This is the canonical cross-platform guidance for AI coding agents working in this repository. +Codex reads it directly; Claude Code imports it from `CLAUDE.md`. + +## Dev commands + +Node 24+ is required (for built-in `node:sqlite` and the TypeScript ecosystem). pnpm 10+ +is the package manager. Everything runs via `tsx`, pinned as a root dev-dependency. + +```sh +pnpm install +pnpm test # vitest run across the whole tree +pnpm typecheck # tsc --noEmit (excludes apps/web — see below) +pnpm lint # biome check +pnpm format # biome check --write +``` + +Running a single test: `pnpm vitest run apps/daemon/test/core/agents.test.ts` (or `pnpm vitest -t 'pattern'`). + +Running the CLI locally (no build step — `tsx` executes `.ts` directly): + +```sh +pnpm tsx apps/cli/src/index.ts +# e.g. pnpm tsx apps/cli/src/index.ts agent chat +# pnpm tsx apps/cli/src/index.ts serve # boots the daemon (web UI runs separately) +``` + +`README.md` has the full user-facing quickstart (`bazilion serve` auto-bootstraps `~/.bazilion` on first run → start the web UI on :4322 → finish first-run setup on /config → spawn from the auto-seeded `default` profile). + +## Architecture + +pnpm workspaces monorepo. **Four apps** (cli, daemon, web, mobile), **two packages** (api-types, client). + +The **daemon** (`apps/daemon`) is the single owner of `~/.bazilion` AND the LLM/tool stack. It's also the only place server-side code lives — there's no `@bazilion/core` or `@bazilion/runtime` package; that code is internal to the daemon under `apps/daemon/src/core/` (DB schema, repos, domain ops, paths, secrets, services, skills) and `apps/daemon/src/runtime/` (pi adapter, providers, memory, tools, worker, sessions). Every other process — CLI, web frontend SSR, mobile app, future browser SPA — talks to the daemon over HTTP. **Workers spawned per turn don't hold their own DB handle either** — they receive pre-resolved data on stdin and round-trip live messaging back to the daemon over Node IPC. + +Three invariants: + +1. **Nothing outside `apps/daemon` imports daemon-internal code at runtime.** Clients talk to the daemon over HTTP via `@bazilion/client` + `@bazilion/api-types`. The CLI's tests are the one pragmatic exception: they reach into `apps/daemon/src/...` via relative imports for setup/inspection. No other consumer should follow that pattern. +2. **The daemon owns the DB, scheduler, agent-cancel registry, secrets table, and the bootstrap token in auth.json.** Other processes are stateless clients. +3. **The daemon self-bootstraps on first `bazilion serve`** — there is no `bazilion init` command. `apps/daemon/src/lib/ctx.ts:bootstrap()` runs idempotently at startup: creates `~/.bazilion/{profiles,agents,skills,groups,logs}`, opens the DB, runs migrations, and (if `auth.json` is missing) mints a bootstrap web_tokens row + writes the plaintext to `auth.json`. + +- **`packages/api-types`** — **hermetic** wire-shape package. Owns the canonical type definitions for everything that crosses the HTTP/IPC wire: entity shapes (`Agent`, `Group`, `Profile`, `Message`, `WebToken`, `AgentTrigger`, `ResolvedAgent`, `LoadedProfile`, `OpenAICodexStatus`, …) in `entities.ts`, chat/provider events (`ChatFrame`, `SessionEvent`, `ProviderMessage`, `ToolCall`, `ToolDef`) in `events.ts`, memory wire types in `memory.ts`, and request/response envelopes + `PROFILE_FILES` in `index.ts`. **Zero deps** — no node-only modules, no daemon code. The daemon imports its entity/wire types FROM here; that's what keeps `apps/web`, `apps/mobile`, and `@bazilion/client` from ever reaching Node-only code (`node:sqlite`, undici, pi-ai, the worker spawner). +- **`packages/client`** — HTTP client for **cross-origin consumers that need explicit auth headers**: the CLI today, a future React Native / mobile app tomorrow. Exports `createClient({ serverUrl, token })`, `ApiClientError`, `BazilionClient`. Pure `fetch` + `TextDecoder` + NDJSON stream async generator — no `node:*` imports, no node-only deps. `token` is `string | (() => string | Promise)` so rotating credentials (OAuth refresh, mobile keychain reads) plug in without rebuilding the client; the package builds `Authorization: Bearer ` + `Origin: ` headers internally. `apps/cli/src/client.ts` wraps it with the Node-specific `loadClientConfig()` (reads `~/.bazilion/auth.json` + `BAZILION_SERVER`/`BAZILION_TOKEN` env via the CLI's local `apps/cli/src/{paths,auth-file}.ts` helpers). **Not used by `apps/web` browser-side code**: the web UI hits its own server same-origin with relative URLs, so the `bz_token` cookie auto-attaches. + +### Auth model + +One token table (`web_tokens`), one source of truth in the daemon. `apps/daemon/src/lib/auth.ts` exposes `isValidToken(t)` (queries `web_tokens` only — there's no separate bootstrap path) and `extractBearer(header)`; `apps/daemon/src/lib/middleware-auth.ts` is the Hono middleware that gates every route. The middleware reads `Authorization: Bearer ` first, falls back to the `bz_token` cookie (via `hono/cookie`) — whichever the client sent, the same `isValidToken` lookup decides. Public paths (`/api/login`, `/api/health`) skip auth; setup-open paths (`/api/config/*`, `/api/auth/*`) skip the first-run gate. + +The bootstrap token is the plaintext stored in `~/.bazilion/auth.json` — written there by the daemon's self-bootstrap on first `bazilion serve` (`apps/daemon/src/lib/ctx.ts:bootstrap()`) alongside its hash inserted into `web_tokens` (label `bootstrap`). Both the CLI (loopback bearer) and the daemon (PBKDF2 seed for the secrets table) read this file. **Do not allow revoking the bootstrap row**: `DELETE /api/tokens/:id` rejects (409) when the requested id matches the auth-token's hash, and the web UI hides the revoke button for that row. Otherwise the operator could lock themselves out. + +**SSR cookie-forward**: server fns inside `apps/web/src/lib/daemon-client.ts` read `bz_token` via `getCookie` from `@tanstack/react-start/server` and forward it as a bearer header when calling the daemon — user identity is preserved end-to-end, daemon audit logs always reflect the actual user. **`daemon-client.ts` is server-only**: any module that ends up in the client bundle must import constants from `apps/web/src/lib/wire-constants.ts` instead (Vite's import-protection rejects `@tanstack/react-start/server` in client code). Native clients (CLI, mobile) send bearer via `@bazilion/client`. Tokens are minted + revoked with `bazilion token create/list/revoke` → `POST|GET|DELETE /api/tokens`. When adding a new auth surface, extend `isValidToken` — don't bypass it. + +### Mobile / LAN notes + +The daemon binds `127.0.0.1:4321` by default. `bazilion serve --host 0.0.0.0 [--port N]` exposes it on the LAN for a mobile client; the serve command prints a loud warning in that case because the API is admin-level and TLS is the user's responsibility (Tailscale handles it for personal networks; reverse-proxy with TLS for anything else). The web frontend's `vite.config.ts` reads `WEB_HOST`/`WEB_PORT` so it can move off 4322 if needed. Pairing: `bazilion token create