bazilion 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +22 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Dev commands
|
|
6
|
+
|
|
7
|
+
Node 22.12+ required (for built-in `node:sqlite` + `.ts` ecosystem). pnpm 10+ as the package manager. Everything runs via `tsx`, which is pinned as a root dev-dep — no separate install.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm test # vitest run across the whole tree
|
|
12
|
+
pnpm typecheck # tsc --noEmit (excludes apps/web — see below)
|
|
13
|
+
pnpm lint # biome check
|
|
14
|
+
pnpm format # biome check --write
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Running a single test: `pnpm vitest run apps/daemon/test/core/agents.test.ts` (or `pnpm vitest -t 'pattern'`).
|
|
18
|
+
|
|
19
|
+
Running the CLI locally (no build step — `tsx` executes `.ts` directly):
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm tsx apps/cli/src/index.ts <subcommand>
|
|
23
|
+
# e.g. pnpm tsx apps/cli/src/index.ts agent chat <uuid>
|
|
24
|
+
# pnpm tsx apps/cli/src/index.ts serve # boots the daemon (web UI runs separately)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`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).
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
pnpm workspaces monorepo. **Four apps** (cli, daemon, web, mobile), **two packages** (api-types, client).
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
Three invariants:
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
2. **The daemon owns the DB, scheduler, agent-cancel registry, secrets table, and the bootstrap token in auth.json.** Other processes are stateless clients.
|
|
39
|
+
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`.
|
|
40
|
+
|
|
41
|
+
- **`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).
|
|
42
|
+
- **`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<string>)` so rotating credentials (OAuth refresh, mobile keychain reads) plug in without rebuilding the client; the package builds `Authorization: Bearer <token>` + `Origin: <serverUrl>` 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.
|
|
43
|
+
|
|
44
|
+
### Auth model
|
|
45
|
+
|
|
46
|
+
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 <t>` 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.
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
**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.
|
|
51
|
+
|
|
52
|
+
### Mobile / LAN notes
|
|
53
|
+
|
|
54
|
+
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 <label> --qr` mints a token and emits a `bazilion://pair?server=<url>&token=<t>` URL plus a terminal QR code (via `qrcode-terminal`). The server URL is auto-detected from the first non-loopback IPv4 interface when the CLI's stored URL is loopback-only; `--server <url>` overrides detection (useful for Tailscale hostnames). No gateway/relay has been built — the mobile app is expected to speak to the daemon directly over LAN/Tailscale/VPN.
|
|
55
|
+
|
|
56
|
+
### apps/mobile
|
|
57
|
+
|
|
58
|
+
Expo SDK 54 + Expo Router 6 (file-based), React 19, RN 0.81, new-architecture-enabled. URL scheme `bazilion://` for deep-link pairing. Structure:
|
|
59
|
+
- `app/` — file-based routes. `_layout.tsx` wraps the stack; `index.tsx` is the auth gate (loads credentials from `expo-secure-store`, redirects to `/pair` or `/agents`); `pair.tsx` is the camera+manual-paste pairing flow; `agents/index.tsx` is a FlatList of agents (pull-to-refresh, 401→/pair auto-unpair, header-level unpair button); `agents/[id].tsx` is an agent detail stub where chat will land next.
|
|
60
|
+
- `src/pair-url.ts` — pure TS parser for `bazilion://pair?server=…&token=…`. RN-free, unit-tested by root vitest (`apps/mobile/test/pair-url.test.ts`).
|
|
61
|
+
- `src/auth.ts` — `expo-secure-store` wrapper (`loadCredentials` / `saveCredentials` / `clearCredentials` / `verifyCredentials`) and a `clientFor(creds)` factory returning `@bazilion/client`'s `BazilionClient`.
|
|
62
|
+
- `metro.config.js` — sets `watchFolders`/`nodeModulesPaths` so Metro resolves workspace packages through pnpm's symlink layout.
|
|
63
|
+
- Excluded from root `tsconfig.json`, `biome.json`. Run `pnpm --filter @bazilion/mobile typecheck` for mobile-only TS checks. Run `pnpm --filter @bazilion/mobile start` for the Expo dev server.
|
|
64
|
+
- `@bazilion/api-types` is hermetic — entity types and wire shapes are defined inline (`entities.ts`, `events.ts`, `memory.ts`); it has no dep on the daemon. That's what keeps mobile's TS checker out of Node-only code. Metro itself never sees these imports at runtime (babel's TS transform strips `import type` / `export type`) — but do not add a *value* import from `@bazilion/daemon` (or any daemon-internal path) to the mobile tree, or the bundle will explode.
|
|
65
|
+
|
|
66
|
+
- **`apps/cli`** — thin citty-based CLI. Every subcommand except `serve` talks to the daemon over HTTP via `@bazilion/client`. No direct DB access in the CLI's runtime path. The two filesystem-level commands are: `serve` (boots `apps/daemon` directly — the daemon then auto-bootstraps `~/.bazilion`); `uninstall` (two-tier teardown: data wipe vs. full — operates on the filesystem so it works even when the daemon isn't running). Local helpers `apps/cli/src/paths.ts` (`resolveCliPaths`) and `apps/cli/src/auth-file.ts` (`readAuthFile` + `AuthFile` type) duplicate just enough of the daemon's path/auth utilities for these filesystem-touching commands. The web UI runs separately during dev: `cd apps/web && pnpm dev`. Remote daemon is opt-in via `BAZILION_SERVER` + `BAZILION_TOKEN` env vars; otherwise the client reads `auth.json` and hits `http://127.0.0.1:4321`.
|
|
67
|
+
|
|
68
|
+
- **`apps/daemon`** — Hono on `@hono/node-server`, binds `127.0.0.1:4321` by default (`HOST`/`PORT` env override). Owns all server-side code under one roof:
|
|
69
|
+
- `src/index.ts` — entry: eagerly calls `getCtx()` so the bootstrap message + auth.json land before the port binds, then starts Hono.
|
|
70
|
+
- `src/lib/` — daemon-only glue: `ctx.ts` (singleton + self-bootstrap), `middleware-auth.ts`, `auth.ts`, `agent-cancel.ts`, `agent-id.ts`, `agent-turn.ts`, `api-key.ts`, `cron.ts`, `messaging-host.ts`, `scheduler.ts`.
|
|
71
|
+
- `src/routes/` — HTTP routes by resource family: `agents.ts` (CRUD + group + skills + triggers + messages + sessions + chat NDJSON + `/cancel`), `groups.ts` (CRUD + USER.md + per-group shared memory at `/api/groups/:slug/memory*`), `profiles.ts`, `skills.ts`, `triggers.ts`, `messages.ts`, `config.ts`, `auth-login.ts` (ChatGPT OAuth + `/providers/test` + `/login`), `misc.ts` (`/health`, `/backup`, `/tokens`).
|
|
72
|
+
- `src/core/` — what used to be `packages/core`: SQLite schema + migrations (`db/`), repos (`repos/`), domain ops (`agent/`, `group/`, `profile/`, `skills/`), `paths.ts`, `secrets.ts`, `services.ts`, `availableModels.ts`. Barrel export at `src/core/index.ts`.
|
|
73
|
+
- `src/runtime/` — what used to be `packages/runtime`: pi-coding-agent integration (`pi/`), provider adapters (`providers/`), memory backends (`memory/`), Bazilion-specific tools (`tools/`), per-turn worker subprocess (`worker/{entry,spawn,ipc-protocol}.ts`), heartbeat helpers (`auto-reply/`), OpenAI Codex OAuth (`auth/`). Barrel export at `src/runtime/index.ts`.
|
|
74
|
+
- Auth + first-run middleware (`src/lib/middleware-auth.ts`) gates every route; public paths whitelisted inside the middleware. The daemon installs its own SIGINT/SIGTERM handlers and shuts the HTTP server gracefully.
|
|
75
|
+
- **`apps/web`** — TanStack Start (React 19 + Vite 7) frontend, **daemon-only client**. The canonical end-user UI. File-based routes under `src/routes/`: `__root.tsx` (root layout + `beforeLoad` auth gate), `index.tsx`, `login.tsx`, `welcome.tsx`, `agents/{index,$id/{index,inbox,triggers}}.tsx`, `profiles/{index,$id}.tsx`, `groups/{index,$id/{index,memory}}.tsx`, `skills/index.tsx`, `config/{index,services,tokens}.tsx`, `api/$.ts` (catch-all proxy). Tailwind v4 (CSS-first) + shadcn/ui. SSR loaders use `apps/web/src/lib/daemon-client.ts` (server-only) inside `createServerFn` handlers; that helper reads the request's `bz_token` cookie and forwards it as `Authorization: Bearer …` to `http://127.0.0.1:4321` (overridable via `BAZILION_DAEMON`). Browser fetches hit relative `/api/*` URLs that pass through the catch-all reverse proxy (cookie→bearer translation, streaming-capable for chat NDJSON). The dev server binds `WEB_PORT` (default 4322) — pair with `bazilion serve` on 4321. **CLI/web parity is mandatory**: every HTTP endpoint must have both a CLI surface and a web UI surface. Chat markdown is rendered via `marked` + DOMPurify; styles for the `.md-content` class live in `apps/web/src/styles.css` (without them, Tailwind preflight strips list markers, header sizes, code styling, etc).
|
|
76
|
+
|
|
77
|
+
### SQLite driver
|
|
78
|
+
|
|
79
|
+
`apps/daemon/src/core/db/client.ts` uses `node:sqlite` (Node 22+ built-in). `node:sqlite` has no callable `transaction()` wrapper, so the wrapper implements manual `BEGIN/COMMIT/ROLLBACK` in its `transaction()` method. Don't look for a `bun:sqlite` fallback — the project is Node-only.
|
|
80
|
+
|
|
81
|
+
### On-disk layout
|
|
82
|
+
|
|
83
|
+
State lives in `~/.bazilion/` (overridable via `$BAZILION_HOME`):
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
~/.bazilion/
|
|
87
|
+
bazilion.db # ALL DB state: entities + secrets + config (encrypted) + tokens
|
|
88
|
+
auth.json # {token, remote?} — bootstrap bearer + optional CLI remote target
|
|
89
|
+
groups/<slug>/ # collaboration root, mounted as cwd; may be a symlink (--link)
|
|
90
|
+
memory/ # group-shared qmd index (.qmd-index.sqlite + markdown notes)
|
|
91
|
+
... project files / work product ...
|
|
92
|
+
agents/<id>/ # agent's PRIVATE home — strictly outside the group tree
|
|
93
|
+
SOUL.md / IDENTITY.md / AGENTS.md / TOOLS.md / HEARTBEAT.md / [BOOTSTRAP.md]
|
|
94
|
+
sessions/<sessionId>.jsonl # pi's append-only transcript
|
|
95
|
+
agent.json
|
|
96
|
+
profiles/<id>/ # profile templates
|
|
97
|
+
skills/<name>/SKILL.md # installed skills
|
|
98
|
+
logs/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Path resolution is centralized in `apps/daemon/src/core/paths.ts`. The `Paths` struct has `home`, `db`, `authFile`, `profilesDir`, `agentsDir`, `groupsDir`, `skillsDir`, `logsDir`, plus `agentDir(id)` / `groupDir(slug)` / `profileDir(id)` / `skillDir(name)` computed helpers. The CLI has its own minimal `apps/cli/src/paths.ts` (`resolveCliPaths` returns just `{home, authFile}`) for the filesystem-level commands (`uninstall`, `backup`, `login`, `token show-local`) — they don't need the full struct. **There is no longer a `configFile` field — `config.json` and `secrets.enc` were collapsed into the DB.** `bazilion uninstall` mirrors this layout as a two-tier teardown: data tier = DB + `profiles/` + `agents/` + `groups/`; full wipe adds `auth.json`, `logs/`, `skills/`. Symlinked groups (registered with `--link`) only have the slot under `~/.bazilion/groups/` removed; the symlink target is never touched.
|
|
102
|
+
|
|
103
|
+
### Group registration
|
|
104
|
+
|
|
105
|
+
Groups always live at `~/.bazilion/groups/<slug>/`. The CLI (`bazilion group add <slug> [--link <target>]`) and the web `/groups` create form pass only the slug + optional name + optional link target; the daemon decides where the slot goes. `--link <abs-path>` materializes the slot as a symlink to an existing directory (the "agents working on my existing project tree" path); the target must exist and be a directory. Without `--link`, a fresh real directory is created. `groupRepo.get/list/insert(db, ..., paths)` derive `Group.path` from `paths.groupDir(id)` at read time — there is no `path` column anymore.
|
|
106
|
+
|
|
107
|
+
### Memory model
|
|
108
|
+
|
|
109
|
+
Memory is **per-group**, shared across every agent in the group. The qmd backend lives at `<group.path>/memory/`; each turn's worker calls `qmdBackend(join(agent.group.path, 'memory'))`. The `memory_*` tool descriptions explicitly tell the LLM the store is shared and direct personal notes (persona quirks, preferences) to `home_write IDENTITY.md` instead. The schema's per-agent memory dir is gone — `spawnAgent` only creates `agents/<id>/sessions/`. External surfaces match the ownership: HTTP at `/api/groups/:slug/memory*`, web UI at `/groups/:slug/memory`, CLI at `bazilion memory <write|read|list|search|rm> <group-slug> ...`. There are no `/api/agents/:id/memory*` routes — clients always address the group.
|
|
110
|
+
|
|
111
|
+
### Worker subprocess + IPC
|
|
112
|
+
|
|
113
|
+
Every chat turn runs in its own Node subprocess (`apps/daemon/src/runtime/worker/{entry,spawn,ipc-protocol}.ts`). The daemon's `apps/daemon/src/lib/agent-turn.ts:runAgentTurn`:
|
|
114
|
+
1. Resolves the agent + provider gate + merged secrets env in-process (the worker no longer holds a SQLite handle).
|
|
115
|
+
2. Pre-fetches the OAuth access token for `openai-codex` agents via `apps/daemon/src/lib/api-key.ts:resolveAgentApiKey` (env-key providers return `{}`).
|
|
116
|
+
3. Spawns the worker with `stdio: ['pipe', 'pipe', 'inherit', 'ipc']` — the IPC channel is what makes messaging tools work without a worker DB handle.
|
|
117
|
+
4. Sends `{agent, message, enabledProviders, apiKey?}` on stdin.
|
|
118
|
+
5. Line-parses NDJSON `ChatFrame`s from worker stdout and yields them.
|
|
119
|
+
6. Services worker `process.send({type:'rpc', method, args, id})` calls (the messaging-tool RPCs) by dispatching to `apps/daemon/src/lib/messaging-host.ts:createDbMessagingHost(db)` and replying with `child.send({type:'rpc-reply', id, ok, result|error})`.
|
|
120
|
+
|
|
121
|
+
`SessionEvent` types: `user_message` / `assistant_message` / `assistant_delta` / `tool_call` / `tool_result` / `tool_error` / `error`. `ChatFrame` shapes: `{kind:'event', event}` / `{kind:'done', messages}` / `{kind:'fatal', error}`. **There is no `runId`, no `runs` table, no `events` table** — those were dropped along with the per-run audit metadata layer; pi's session JSONL files are the canonical transcript and the only persistent record of what an agent has said.
|
|
122
|
+
|
|
123
|
+
Cancellation: keyed by **agentId** (not runId). The daemon's `apps/daemon/src/lib/agent-cancel.ts` registers an `AbortController` per active agent on each `runAgentTurn` start; `POST /api/agents/:id/cancel` aborts the controller, which SIGTERMs the worker (3 s grace → SIGKILL); the child's own SIGTERM handler aborts its internal controller so pi unwinds the provider fetch and emits an `error` event with `error: 'cancelled'` before exiting. CLI: `bazilion agent cancel <id>`. The worker also calls `process.disconnect()` in its `finally` so the IPC channel doesn't pin the event loop alive after the turn settles.
|
|
124
|
+
|
|
125
|
+
See `docs/agent-engine.md` for the full turn-loop walkthrough.
|
|
126
|
+
|
|
127
|
+
### Providers
|
|
128
|
+
|
|
129
|
+
Model strings are `provider:model` (e.g. `lmstudio:my-loaded-model`, `anthropic:claude-opus-4-6`, `gemini:gemini-2.0-flash-exp`, `openai-codex:gpt-5.3-codex`). `createProviderRegistry` + `loadProviderConfigFromEnv` in `apps/daemon/src/runtime/providers/registry.ts` resolve strings to `{ provider, model }`. Credentials come from env vars (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `LMSTUDIO_URL`/`LMSTUDIO_API_KEY`, `OLLAMA_URL`); plain API-key providers never touch the DB. The daemon's `mergeSecretsIntoEnv(db, authToken)` layers DB-stored secrets + plaintext config over `process.env` — this happens **server-side** per API request (chat, provider test, health) and again per worker spawn (the merged env is passed via `child_process.spawn`'s `env` option).
|
|
130
|
+
|
|
131
|
+
**`openai-codex`** is the OAuth exception: credentials live as a JSON `{refresh, access, expires}` blob under the `OPENAI_CODEX_OAUTH` row of the `secrets` table. The provider registry's `apiKey` field is invoked as an async supplier so refreshes happen lazily on each chat() without rebuilding the cached Provider instance. Pass `{db, authToken}` as the second arg to `loadProviderConfigFromEnv(env, oauth?)` to enable it. For the worker subprocess specifically, `runAgentTurn` pre-fetches the initial access token via `resolveAgentApiKey` and passes it through `WorkerInput.apiKey` → `createBazilionSession({apiKey})`. The worker doesn't have a refresher wired — long turns that exceed the JWT lifetime fail; the daemon-side compact/context paths get full lazy refresh because they pass `withRefresher: true`.
|
|
132
|
+
|
|
133
|
+
### Secrets and config
|
|
134
|
+
|
|
135
|
+
`secrets.enc` and `config.json` are gone. The DB has two tables:
|
|
136
|
+
|
|
137
|
+
- **`secrets(key TEXT PK, envelope TEXT NOT NULL, updated_at INTEGER)`** — AES-256-GCM envelopes (salt+iv+tag+data hex JSON), one row per env-var-shaped key (`ANTHROPIC_API_KEY`, `OPENAI_CODEX_OAUTH`, …). Encryption key derived from `auth.json`'s `token` via PBKDF2-SHA256 100k. Same crypto as the previous file-based store; only the storage medium changed. Threat model is unchanged: anyone who can read both `bazilion.db` *and* `auth.json` can decrypt — the encryption guards against accidental exposure (cat'd dumps, screenshares), not against filesystem read.
|
|
138
|
+
- **`config(key TEXT PK, value TEXT NOT NULL, updated_at INTEGER)`** — plaintext for env-var-shaped values that don't need confidentiality (server URLs, region slugs, project IDs). The `CONFIG_KEYS` allowlist (derived from `services.ts`) is enforced in `repos/config.ts` on writes.
|
|
139
|
+
|
|
140
|
+
API in `apps/daemon/src/core/`: `openSecrets(db, password)` and `openConfig(db)` (ConfigStore + SecretsStore types). The daemon caches the auth token in `getCtx().authToken` so all routes can call `mergeSecretsIntoEnv(db, authToken)` without re-reading `auth.json`.
|
|
141
|
+
|
|
142
|
+
## Conventions
|
|
143
|
+
|
|
144
|
+
- Biome formatter: single quotes, no semicolons, trailing commas, 2-space indent, 100-col width.
|
|
145
|
+
- TS is strict with `noUncheckedIndexedAccess` and `verbatimModuleSyntax`. `.ts` extensions are required on all relative imports (`allowImportingTsExtensions: true`) — `tsx` and Vite both handle it.
|
|
146
|
+
- `apps/web/` is excluded from the root `tsconfig.json` and from biome (it has its own Vite/TanStack-managed tooling). Run `pnpm --filter @bazilion/web typecheck` for web-only TS checks. The whole-tree `typecheck` intentionally skips it.
|
|
147
|
+
- Migrations are numbered SQL files in `apps/daemon/src/core/db/migrations/`. The schema is consolidated into a single `0001_init.sql` (the project is alpha; we wiped the DB and collapsed the prior chain on every shape change rather than maintaining an ALTER chain). Add new migrations as new numbered files going forward; the runner is idempotent and runs from the daemon's self-bootstrap (`apps/daemon/src/lib/ctx.ts:bootstrap()`) on every startup. Existing installs pick new files up automatically.
|
|
148
|
+
|
|
149
|
+
## Already-shipped invariants (don't re-implement)
|
|
150
|
+
|
|
151
|
+
- **Daemon = sole owner of `~/.bazilion`** — the worker subprocess delegates anything DB-backed (messaging tools, provider gate, agent resolution, secrets) back to the daemon over Node IPC + stdin. There is no per-worker SQLite handle.
|
|
152
|
+
- **Groups = single filesystem root + USER.md + roster + shared memory.** One agent → one group. `groups.user_md` is a DB column (agents can't clobber it via `write`/`edit`). Groups always live under `~/.bazilion/groups/<slug>/` (real dir or symlink via `--link`).
|
|
153
|
+
- **Memory is group-shared.** `qmdBackend(group.path/memory)` — every member writes to and reads from the same store. Personal notes go to `IDENTITY.md` via `home_write`.
|
|
154
|
+
- **No runs/events tables, no stats CLI.** The runs/events audit layer was dropped in favor of pi's session JSONL files (which are the authoritative transcript). There is no `bazilion run list/show/cancel/prune` and no `bazilion stats`. Cancel is `bazilion agent cancel <id>` (keyed by agentId).
|
|
155
|
+
- **Bootstrap auth lives in `auth.json`.** The daemon reads it once at startup (`getCtx().authToken`) and uses it as the PBKDF2 seed for the secrets table. The CLI reads it as its loopback bearer. The `bootstrap` row in `web_tokens` cannot be revoked — `DELETE /api/tokens/:id` rejects a hash match against the auth token.
|
|
156
|
+
- **First-run gate** — `isSetupComplete(db)` returns true iff at least one enabled provider has ≥1 curated model. Web middleware redirects non-API routes to `/welcome` while the gate is closed; API routes return 409. Allowed prefixes during setup: `/welcome`, `/login`, `/config`, `/api/config`, `/api/auth`, `/api/health`, `/api/login`. Crossing the threshold triggers `ensureSetupSeeded(db, paths)` which creates the `default` profile (skillsMode: `'all'`) + `default` group (at `~/.bazilion/groups/default/`).
|
|
157
|
+
- **Spawn-time skill override is gone.** Skills come from the profile only — `skillsMode: 'all'` attaches every installed skill at spawn, `'selected'` uses `profile_default_skills`. Per-agent tweaks happen post-spawn via `bazilion agent skill add/rm` (or the per-agent skills card on the detail page).
|
|
158
|
+
- **Web client constants live in `apps/web/src/lib/wire-constants.ts`** (`DEFAULT_GROUP_ID`, `DEFAULT_PROFILE_ID`, `REASONING_LEVELS`). `apps/web/src/lib/daemon-client.ts` is server-only — Vite's import-protection rejects it from any client-bundled module.
|
|
159
|
+
- **OpenClaw skill model: prompt-only.** Skills under `~/.bazilion/skills/<name>/` get their SKILL.md body injected into the system prompt of every agent they're attached to; helper scripts run via the agent's generic `bash` tool. No framework `entry:` extension, no trust gate.
|
|
160
|
+
- **qmd memory backend** (`apps/daemon/src/runtime/memory/qmd.ts`) — wraps `@tobilu/qmd`'s `searchLex` (BM25) for all memory routes. One `.qmd-index.sqlite` per group. Hybrid/vector paths are intentionally not enabled (pulls `node-llama-cpp` and multi-GB GGUF models; excluded in `pnpm.onlyBuiltDependencies`).
|
|
161
|
+
- **Heartbeats / cron triggers** (`agent_triggers` table; `apps/daemon/src/lib/scheduler.ts`) — in-process tick loop (default 5s, `BAZILION_SCHEDULER_TICK_MS`; disable with `BAZILION_SCHEDULER=off`) pinned to `globalThis[Symbol.for('bazilion.scheduler')]`. Interval kind uses `last_fired_at + every ≤ now` with `created_at` as baseline; cron kind parses 5-field expressions via `apps/daemon/src/lib/cron.ts`. Firing reuses `runAgentTurn`; `last_fired_at` is updated *before* the run kicks off so a restart mid-fire doesn't double-trigger. CLI: `bazilion trigger add|list|rm|enable|disable`.
|
|
162
|
+
- **Inbox / messaging surfaces** — the `send_message` / `read_inbox` / `wait_for_reply` tools (`apps/daemon/src/runtime/tools/messaging.ts`) are wired with `MessagingHost` injection: in the daemon (compact/context routes) the host is `createDbMessagingHost(db)`; in the worker the host is `createIpcMessagingHost()` which proxies every method through Node IPC. Outside-the-loop surfaces: `GET /api/agents/:id/messages?unread=1` (list), `POST /api/agents/:id/messages` (now accepts `replyTo`), `GET|PATCH /api/messages/:id` (detail + mark-read). CLI: `bazilion inbox list <agent> [--unread]`, `inbox show <id>`, `inbox read <id>`. Web: `/agents/:id/inbox`.
|
|
163
|
+
- **`web_fetch` hardening: Readability + markdown + cache + SSRF guard** — `@mozilla/readability` over `linkedom`, output as markdown (or text via `extract_mode`). SSRF guard at `apps/daemon/src/runtime/tools/web-ssrf.ts` blocks loopback/private/link-local + DNS rebinding (re-validates resolved IPs, pins them into undici's `Agent.connect.lookup`). 15-min in-memory LRU per `${mode}|${url}` (100-entry cap). UA spoofs desktop Safari. 20s default timeout, 3 max redirects.
|
|
164
|
+
- **ChatGPT OAuth / `openai-codex` provider** — credentials in `secrets:OPENAI_CODEX_OAUTH`. CLI runs the loopback flow (port 1455) client-side and PUTs credentials to `/api/auth/openai`; web `/config` has a "Connect ChatGPT" card. `apps/daemon/src/lib/api-key.ts:resolveAgentApiKey` is the single helper every session-creating call site uses to pre-fetch the access token; for daemon-side sessions it also wires a refresher for mid-turn JWT swaps. Worker turns don't get the refresher today (they'd need a new IPC method).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrizio Rullo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# bazilion
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
5
|
+
Multi-agent runtime inspired by [OpenClaw](https://docs.openclaw.ai). Profiles are templates, agents are instances spawned from a profile into a single group (the collaboration context — one filesystem root, one USER.md, one roster, one shared memory), skills attach on the fly, and agents can talk to each other through a DB-backed mailbox.
|
|
6
|
+
|
|
7
|
+
Local-only. TypeScript + Node monorepo (pnpm + tsx + vitest). The web UI lives at `apps/web` (TanStack Start + React 19 + Tailwind v4 + shadcn/ui) and pairs with the standalone Hono daemon at `apps/daemon`. The CLI talks over HTTP, so keep `bazilion serve` running while you work in another terminal.
|
|
8
|
+
|
|
9
|
+
## Status
|
|
10
|
+
|
|
11
|
+
Whole-run subprocess isolation with worker↔daemon Node-IPC for messaging, ChatGPT OAuth, qmd memory (group-shared), scheduler/triggers, profile skills mode, and groups (one-to-one agent membership). The daemon is the **single owner of `~/.bazilion`** — config + secrets live in the SQLite DB, the only other file at the root is `auth.json` (the bootstrap bearer). See `docs/architecture.md` for the engineer-to-engineer reference.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# Install dependencies (Node 22.12+ and pnpm 10+ required)
|
|
17
|
+
pnpm install
|
|
18
|
+
|
|
19
|
+
# Start the daemon — on first run it auto-bootstraps ~/.bazilion
|
|
20
|
+
# (creates dirs, runs migrations, mints the bootstrap token, writes auth.json).
|
|
21
|
+
# The bootstrap message prints the token before the HTTP port binds.
|
|
22
|
+
pnpm tsx apps/cli/src/index.ts serve
|
|
23
|
+
|
|
24
|
+
# In another terminal, start the web UI (Vite dev server on 4322)
|
|
25
|
+
cd apps/web && pnpm dev
|
|
26
|
+
# → http://127.0.0.1:4322 — paste the bootstrap token to log in
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Open `http://127.0.0.1:4322` in a browser. On a fresh install every page redirects to `/welcome` until you finish first-run setup: enable a provider on `/config` and list at least one model for it. The moment both conditions hold, a `default` profile + `default` group (at `~/.bazilion/groups/default/`) are auto-created wired to that model. The default profile uses `skillsMode: 'all'` so spawned agents inherit every installed skill out of the box. The homepage unlocks with a one-click spawn dropdown.
|
|
30
|
+
|
|
31
|
+
From there:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
# Homepage: click "+ new ▾" in the sidebar and pick the default profile to
|
|
35
|
+
# spawn an agent, or select any other profile you've created.
|
|
36
|
+
|
|
37
|
+
# From the CLI — one-shot or interactive:
|
|
38
|
+
pnpm tsx apps/cli/src/index.ts agent spawn --profile default --name first
|
|
39
|
+
# → spawned agent <uuid> (first)
|
|
40
|
+
|
|
41
|
+
pnpm tsx apps/cli/src/index.ts agent chat <uuid> # readline REPL
|
|
42
|
+
pnpm tsx apps/cli/src/index.ts agent chat <uuid> --message "say hi"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Credentials can also come from the environment if you'd rather not use `/config` (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `LMSTUDIO_URL`/`LMSTUDIO_API_KEY`, `OLLAMA_URL`, etc.) — but you still need to enable the provider and list its models in the web UI (or via the provider-state / provider-models APIs) to clear the first-run gate.
|
|
46
|
+
|
|
47
|
+
## CLI commands
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
bazilion serve [--port N] [--host H] # boot the daemon (auto-bootstraps on first run; HTTP API on 4321)
|
|
51
|
+
bazilion uninstall [--yes] [--all] # wipe state (two-tier: data vs full)
|
|
52
|
+
bazilion doctor # diagnose your install
|
|
53
|
+
bazilion auth openai login|logout|status # ChatGPT OAuth (Plus/Pro/Team accounts)
|
|
54
|
+
bazilion profile create|list|show|edit|update|delete # manage profile templates
|
|
55
|
+
bazilion group add|list|rm # register groups (always under ~/.bazilion/groups/<slug>/)
|
|
56
|
+
bazilion group user-md show|set|clear # per-group USER.md (read-only to agents)
|
|
57
|
+
bazilion agent spawn|list|show|archive|unarchive|delete # agent lifecycle
|
|
58
|
+
bazilion agent edit <id> [--model …] [--reasoning …] # patch agent settings
|
|
59
|
+
bazilion agent chat <id> [--message X] # interactive REPL or one-shot
|
|
60
|
+
bazilion agent cancel <id> # abort an in-flight turn
|
|
61
|
+
bazilion agent move <id> <group> # move an agent to a different group
|
|
62
|
+
bazilion agent skill add|rm <id> <name> # attach/detach a skill on an agent
|
|
63
|
+
bazilion agent chat-reset|chat-trim|chat-context|chat-compact <id>
|
|
64
|
+
bazilion skill list|import|rm # skill library (import --from openclaw)
|
|
65
|
+
bazilion memory write|read|search|list|rm <agent> # group-shared memory accessed via the agent
|
|
66
|
+
bazilion send <from> <to> <message> # mailbox send
|
|
67
|
+
bazilion inbox list|show|read # inspect agent inboxes
|
|
68
|
+
bazilion trigger add|list|rm|enable|disable # heartbeats / cron triggers
|
|
69
|
+
bazilion provider list|enable|disable|models|test # provider config + smoke test
|
|
70
|
+
bazilion config get|set # service config (URLs, IDs, secrets)
|
|
71
|
+
bazilion login --server URL --token T # save a remote daemon's coordinates
|
|
72
|
+
bazilion token create|list|revoke|show-local # web tokens for API/CLI clients
|
|
73
|
+
bazilion backup create [output.tar.gz] # tar ~/.bazilion to a file
|
|
74
|
+
bazilion backup restore <file.tar.gz> # extract a backup (stop the daemon first)
|
|
75
|
+
bazilion completion bash|zsh|fish # print a shell completion script
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Concepts
|
|
79
|
+
|
|
80
|
+
- **Profile** — a template (`SOUL.md`, `IDENTITY.md`, `AGENTS.md`, `TOOLS.md`, `HEARTBEAT.md`, optional `BOOTSTRAP.md`, default model, skills mode + default skills). Profiles are agent classes. `skillsMode: 'all'` attaches every installed skill at spawn, `'selected'` uses the curated `defaultSkills` list. The auto-seeded `default` profile uses `'all'` so a fresh install ships with every skill wired up; user-created profiles default to `'selected'`. Delete `default` freely if you'd rather only keep your own.
|
|
81
|
+
- **Group** — a collaboration context: one filesystem root, one USER.md, one roster, one shared memory. Every agent belongs to exactly one group, chosen at spawn time. The agent's coding tools (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`, supplied by [pi-coding-agent](https://www.npmjs.com/package/@mariozechner/pi-coding-agent)) are rooted at the group directory. USER.md is read-only to agents — edit it via `bazilion group user-md set` or the web UI. First-run setup seeds a `default` group at `~/.bazilion/groups/default/`. Groups always live under `~/.bazilion/groups/<slug>/`; pass `--link <existing-path>` to `bazilion group add` to materialize the slot as a symlink to your existing project tree instead of as a fresh directory.
|
|
82
|
+
- **Agent** — an instance spawned from a profile into a group. Has a private home (`~/.bazilion/agents/<id>/` — its copy of the templates, plus pi's append-only session JSONL under `sessions/`) reachable via the `home_*` tools, and one group membership reachable via the coding tools. UUIDs as ids.
|
|
83
|
+
- **Skill** — a directory under `~/.bazilion/skills/<name>/` with a `SKILL.md` (standard OpenClaw / Anthropic agent-skill format). Imported via `bazilion skill import --from openclaw` (or any path). The body is injected into the system prompt of every agent the skill is attached to; helper scripts shipped alongside the markdown are invoked by the agent via its generic `bash` tool (no framework-level entrypoint and no trust gate — see CLAUDE.md for why we removed both).
|
|
84
|
+
- **Memory** — **group-shared** BM25 index rooted at `<groupPath>/memory/`. Every agent in the group reads + writes the same store. The current backend is `qmdBackend` (BM25 over markdown via [@tobilu/qmd](https://github.com/tobi/qmd)). Use it for project knowledge — codebase notes, decisions, things the user told you about the work; for personal notes about an agent (preferences, persona quirks), use `home_write` on `IDENTITY.md` instead.
|
|
85
|
+
- **Mailbox** — `messages` table. Agents talk to each other via `send_message` / `read_inbox` / `wait_for_reply` tools, via `bazilion send` from the CLI, or from outside the loop: `bazilion inbox list <agent> [--unread]`, `bazilion inbox show <id>`, `bazilion inbox read <id>`, or the web UI at `/agents/<id>/inbox`. The worker delegates these tool calls to the daemon over Node IPC — workers don't hold their own SQLite handle.
|
|
86
|
+
- **Trigger** — a heartbeat (interval in seconds) or cron expression that periodically wakes an agent with a stored message. An in-process scheduler ticks every 5 s (overridable via `BAZILION_SCHEDULER_TICK_MS`; disable with `BAZILION_SCHEDULER=off`) and fires due triggers through the same code path as user chat. Example: `bazilion trigger add <agent> --every 300 --message "check your inbox"`.
|
|
87
|
+
|
|
88
|
+
## Tree
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
bazilion/
|
|
92
|
+
├── docs/ # engineer-to-engineer references
|
|
93
|
+
│ ├── architecture.md # components, flows, invariants
|
|
94
|
+
│ └── agent-engine.md # the LLM turn loop, end to end
|
|
95
|
+
├── apps/
|
|
96
|
+
│ ├── cli/ # bazilion binary
|
|
97
|
+
│ ├── daemon/ # Hono HTTP API (booted by `bazilion serve`)
|
|
98
|
+
│ ├── web/ # TanStack Start UI (pairs with apps/daemon)
|
|
99
|
+
│ └── mobile/ # Expo / React Native app (LAN/Tailscale pairing)
|
|
100
|
+
└── packages/
|
|
101
|
+
├── api-types/ # hermetic HTTP/IPC wire types (zero deps)
|
|
102
|
+
└── client/ # cross-origin HTTP client used by CLI + mobile
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The daemon's data layer (`apps/daemon/src/core/`: DB, repos, profile/agent/group ops, skills) and LLM/runtime stack (`apps/daemon/src/runtime/`: providers, tools, memory, worker subprocess) live inside the daemon — they're not separate packages.
|
|
106
|
+
|
|
107
|
+
## Tests
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
pnpm test # vitest across the whole tree
|
|
111
|
+
pnpm typecheck # tsc --noEmit on the non-web tree
|
|
112
|
+
pnpm lint # biome
|
|
113
|
+
pnpm format # biome --write
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## ChatGPT OAuth (use your ChatGPT Plus/Pro/Team account)
|
|
117
|
+
|
|
118
|
+
Bazilion has two OpenAI integrations. The classic one (`openai` provider) authenticates with an API key and hits `api.openai.com`. The second (`openai-codex` provider) signs in with your ChatGPT account via OAuth and talks to the ChatGPT backend that Codex CLI uses — so Plus/Pro/Team accounts can run chat turns against `gpt-5.x` / `gpt-5.x-codex` models inside Bazilion the same way they do in Codex.
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
# CLI: runs the browser flow locally (loopback on :1455), then uploads the
|
|
122
|
+
# resulting credentials to the server. Works even against a remote bazilion.
|
|
123
|
+
bazilion auth openai login
|
|
124
|
+
bazilion auth openai status # connected? when does the access token expire?
|
|
125
|
+
bazilion auth openai logout # wipe stored credentials
|
|
126
|
+
|
|
127
|
+
# Web UI: /config has a "Connect ChatGPT" card that does the same thing, but
|
|
128
|
+
# spawns the browser on the server's machine (fine when you're local; use the
|
|
129
|
+
# CLI from a remote client).
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
After connecting, enable `openai-codex` on `/config` and curate at least one model (e.g. `gpt-5.1`, `gpt-5.1-codex-max`, `gpt-5.2-codex`, `gpt-5.3-codex`). Credentials are stored AES-256-GCM-encrypted in the daemon's `secrets` table (key derived from the bootstrap token in `auth.json`); the access token auto-refreshes via the stored refresh token.
|
|
133
|
+
|
|
134
|
+
## Uninstalling
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
# Interactive — asks two y/N prompts (data-tier, then full-wipe)
|
|
138
|
+
bazilion uninstall
|
|
139
|
+
|
|
140
|
+
# Non-interactive equivalents
|
|
141
|
+
bazilion uninstall --yes # wipe DB + agent/profile/group data only
|
|
142
|
+
bazilion uninstall --yes --all # also remove auth.json, logs/, skills/
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Two tiers: the **data tier** (`bazilion.db*`, `profiles/`, `agents/`, `groups/`) is the factory-reset path — useful during alpha when the DB schema moves. The **full wipe** (`--all`) additionally removes `auth.json`, logs, and the skill library, leaving nothing behind under `~/.bazilion/`. Symlinked groups (registered via `--link`) only have their slot under `~/.bazilion/groups/` removed; the symlink target is never touched.
|
|
146
|
+
|
|
147
|
+
## Stack notes
|
|
148
|
+
|
|
149
|
+
- **SQLite driver**: `node:sqlite` (Node 22+ built-in). Wrapped in `apps/daemon/src/core/db/client.ts` with a manual `BEGIN/COMMIT/ROLLBACK` `transaction()` helper since `node:sqlite` has no callable wrapper of its own.
|
|
150
|
+
- **Daemon owns the DB**: workers spawned per turn don't hold their own SQLite handle. Anything they need at request time (agent record, provider gate, secrets) is pre-resolved by the daemon and passed via stdin; live messaging tool calls (`send_message` / `read_inbox` / `wait_for_reply`) round-trip back to the daemon over Node IPC (the `'ipc'` channel on `child_process.spawn`).
|
|
151
|
+
- **Native modules**: qmd pulls `better-sqlite3` and a handful of tree-sitter grammars (small native compiles on install). `node-llama-cpp` is a qmd transitive dep but intentionally excluded from build in `pnpm.onlyBuiltDependencies` — qmd's BM25 search doesn't need it, and enabling it would require downloading multi-GB GGUF models.
|
|
152
|
+
- **Skills format**: standard agent-skill `SKILL.md` (YAML frontmatter with `name` / `description`, free-form body). OpenClaw skills drop in unchanged via `bazilion skill import --from openclaw`.
|
|
153
|
+
- **Session loop + coding tools**: [pi-coding-agent](https://www.npmjs.com/package/@mariozechner/pi-coding-agent) owns the per-turn agent loop, transcript storage (JSONL session files under `~/.bazilion/agents/<id>/sessions/`), compaction, and the file-IO toolset (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`).
|
|
154
|
+
- **LLM providers**: routed through [pi-ai](https://www.npmjs.com/package/@mariozechner/pi-ai) — Anthropic, OpenAI, OpenAI Codex (ChatGPT OAuth), Google AI Studio, Google Vertex, Azure OpenAI, AWS Bedrock, Mistral, Groq, Cerebras, xAI, Z.AI, Hugging Face, OpenRouter, Vercel AI Gateway, LM Studio, Ollama. Model strings are `provider:model`.
|
|
155
|
+
|
|
156
|
+
## Exposing beyond loopback
|
|
157
|
+
|
|
158
|
+
By default, `bazilion serve` binds `127.0.0.1:4321` — local-only. To use bazilion from another machine (Tailscale, LAN, etc.), put a TLS-terminating reverse proxy in front. Don't expose the daemon directly; it has no TLS and no rate limiting.
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
# On the server, bind to loopback (default) and keep the proxy local.
|
|
162
|
+
bazilion serve
|
|
163
|
+
|
|
164
|
+
# Mint a per-client token (plaintext shown exactly once — copy it now).
|
|
165
|
+
bazilion token create "laptop"
|
|
166
|
+
|
|
167
|
+
# On the client machine — stores the server + token in ~/.bazilion/auth.json.
|
|
168
|
+
bazilion login --server https://bazilion.example.com --token <token>
|
|
169
|
+
|
|
170
|
+
# Revoke when the client is lost or retired.
|
|
171
|
+
bazilion token list
|
|
172
|
+
bazilion token revoke <id>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Note: the bootstrap token (the row labelled `bootstrap`, written to `auth.json` by the daemon's first-run bootstrap) **cannot be revoked** from the API or web UI — revoking it would lock the local CLI out of its own daemon. Mint additional tokens for any other client.
|
|
176
|
+
|
|
177
|
+
Minimal Caddyfile (`caddy run --config Caddyfile`):
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
bazilion.example.com {
|
|
181
|
+
reverse_proxy 127.0.0.1:4321
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Caddy handles the TLS cert via Let's Encrypt automatically. For Tailscale, point the hostname at your tailnet node and use Tailscale's MagicDNS + HTTPS certs. The `web_tokens` table + cookie check runs behind the proxy, so every request still needs a valid token — the proxy only adds transport security.
|
|
186
|
+
|
|
187
|
+
If you *do* want the daemon to bind a non-loopback address directly (dev/test only), pass `--host 0.0.0.0` to `bazilion serve`. Anyone who can reach that port can try tokens, so do not ship it without a proxy.
|
|
188
|
+
|
|
189
|
+
## What's deferred
|
|
190
|
+
|
|
191
|
+
- **Hard skill sandboxing** — skills run with the user's full FS access (no seccomp / bubblewrap / containers). Bazilion is single-user local; skills under `~/.bazilion/skills/` are user-owned by definition. Revisit if a marketplace or multi-user install ever happens.
|
|
192
|
+
- **qmd vector/hybrid search** — BM25 is wired; the semantic path (embeddings + LLM rerank) is disabled to avoid the multi-GB GGUF model download. Enable opt-in later.
|
|
193
|
+
- **Mempalace memory backend** — out of scope for v1.
|
|
194
|
+
- **`generate_image` / vision input** — the chat pane already renders markdown images, but agent-invokable image generation and user image uploads aren't wired.
|
|
195
|
+
- **Worker-side OAuth refresh** — long worker turns that exceed the openai-codex JWT lifetime fail on refresh. The daemon-side compact/context paths still get lazy refresh; only the worker subprocess relies on the initial token carrying the whole turn. See `apps/daemon/src/lib/api-key.ts`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bazilion/cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bazilion": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@bazilion/api-types": "workspace:*",
|
|
11
|
+
"@bazilion/client": "workspace:*",
|
|
12
|
+
"@mariozechner/pi-ai": "^0.69.0",
|
|
13
|
+
"citty": "^0.1.6",
|
|
14
|
+
"qrcode-terminal": "^0.12.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/adm-zip": "^0.5.8",
|
|
18
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
19
|
+
"adm-zip": "^0.5.17"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
export interface AuthFile {
|
|
4
|
+
token: string
|
|
5
|
+
remote?: { server: string; token: string } | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function readAuthFile(authFile: string): AuthFile {
|
|
9
|
+
if (!existsSync(authFile)) {
|
|
10
|
+
throw new Error(`${authFile} not found. Start the daemon with \`bazilion serve\` first.`)
|
|
11
|
+
}
|
|
12
|
+
const raw = readFileSync(authFile, 'utf8')
|
|
13
|
+
const parsed = JSON.parse(raw) as Partial<AuthFile>
|
|
14
|
+
if (typeof parsed.token !== 'string' || !parsed.token) {
|
|
15
|
+
throw new Error(`${authFile} is missing the "token" field`)
|
|
16
|
+
}
|
|
17
|
+
return { token: parsed.token, remote: parsed.remote ?? null }
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type BazilionClient, createClient as createPackageClient } from '@bazilion/client'
|
|
2
|
+
import { readAuthFile } from './auth-file.ts'
|
|
3
|
+
import { resolveCliPaths } from './paths.ts'
|
|
4
|
+
|
|
5
|
+
export type { BazilionClient } from '@bazilion/client'
|
|
6
|
+
export { ApiClientError } from '@bazilion/client'
|
|
7
|
+
|
|
8
|
+
export interface ClientConfig {
|
|
9
|
+
serverUrl: string
|
|
10
|
+
token: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function loadClientConfig(): ClientConfig {
|
|
14
|
+
const envUrl = process.env.BAZILION_SERVER
|
|
15
|
+
const envToken = process.env.BAZILION_TOKEN
|
|
16
|
+
if (envUrl && envToken) return { serverUrl: envUrl, token: envToken }
|
|
17
|
+
|
|
18
|
+
const paths = resolveCliPaths()
|
|
19
|
+
const auth = readAuthFile(paths.authFile)
|
|
20
|
+
// `bazilion login` writes `remote`; when present it targets another host
|
|
21
|
+
// (Tailscale, LAN). Env vars still win so CI / ad-hoc invocations can
|
|
22
|
+
// override without editing auth.json.
|
|
23
|
+
if (auth.remote?.server && auth.remote.token) {
|
|
24
|
+
return {
|
|
25
|
+
serverUrl: envUrl ?? auth.remote.server,
|
|
26
|
+
token: auth.remote.token,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
serverUrl: envUrl ?? 'http://127.0.0.1:4321',
|
|
31
|
+
token: auth.token,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createClient(cfg: ClientConfig = loadClientConfig()): BazilionClient {
|
|
36
|
+
return createPackageClient({ serverUrl: cfg.serverUrl, token: cfg.token })
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render `rows` as space-aligned columns. All rows must have the same column
|
|
3
|
+
* count; empty cells are treated as `""` for width purposes. Column spacing is
|
|
4
|
+
* two spaces — wider than a tab, tighter than tabular CSV tooling expects.
|
|
5
|
+
*
|
|
6
|
+
* Used by list commands so `status` / `kind` / `name` columns don't jump when
|
|
7
|
+
* one row is `idle` (4) and another is `archived` (8). Does not truncate —
|
|
8
|
+
* long cells win the column width. Callers that want a bounded width should
|
|
9
|
+
* slice before passing.
|
|
10
|
+
*/
|
|
11
|
+
export function columnize(rows: string[][], gap = ' '): string[] {
|
|
12
|
+
if (rows.length === 0) return []
|
|
13
|
+
const colCount = rows[0]?.length ?? 0
|
|
14
|
+
const widths: number[] = new Array<number>(colCount).fill(0)
|
|
15
|
+
for (const row of rows) {
|
|
16
|
+
for (let i = 0; i < colCount; i++) {
|
|
17
|
+
const cell = row[i] ?? ''
|
|
18
|
+
const prev = widths[i] ?? 0
|
|
19
|
+
if (cell.length > prev) widths[i] = cell.length
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return rows.map((row) =>
|
|
23
|
+
row
|
|
24
|
+
.map((cell, i) => {
|
|
25
|
+
// Don't pad the last column — trailing whitespace is pointless and
|
|
26
|
+
// breaks `| wc`-style pipes.
|
|
27
|
+
if (i === colCount - 1) return cell
|
|
28
|
+
return (cell ?? '').padEnd(widths[i] ?? 0)
|
|
29
|
+
})
|
|
30
|
+
.join(gap),
|
|
31
|
+
)
|
|
32
|
+
}
|