# beaver-agent My real beaver-gateway setup (paired with the protocol from beaver.kotikot.com). Uses Claude Code and Raycast subscriptions for agents, has some MCPs set up. You can use this as-is or modify to match your needs (config.py). ## Requirements - Docker + Docker Compose - Obsidian Sync (for `obsidian-headless`) - claude.ai sub - raycast sub - Firefly III and its PAT ## First launch ### 1. Create `.env` and `raycast.json` ```bash cp .env.example .env # every empty value is documented inline; secrets: openssl rand -hex 32 nvim .env # build raycast config on your mac (beta by default, consider checking raycast-api for instructions) raycast-api init # copy config.json to your deployment server docker compose up -d ``` ### 2. Set up Obsidian Sync ```bash docker exec -it beaver-obsidian ob login docker exec -it beaver-obsidian ob sync-setup --vault "yourvault" docker restart beaver-obsidian ``` Check: ```bash docker exec beaver-obsidian ls /vault ``` Only markdown is synced by default. To sync everything: ```bash docker exec beaver-obsidian ob sync-config \ --file-types image,audio,video,pdf,unsupported docker restart beaver-obsidian ``` ### 3. Set up claude On your mac run `claude setup-token` and put the result into `.env` as `CLAUDE_CODE_OAUTH_TOKEN`. That is the whole auth: no `/login` inside the container, no dialogs. The token draws from your subscription limits like regular Claude Code; rotate it by running `claude setup-token` again. Claude agents run on the Claude Agent SDK. Prompts are granules under `мета/бобер/промпты/` in the vault - plain markdown, the `` wrappers and the order live in `config.py`; skills are the folders under `мета/бобер/скиллы/` (each becomes a plugin, keep `name:` in SKILL.md latin). The vault must be synced before the gateway can start. The model process runs as `beaver-runner` with a whitelisted environment; the vault is read-only for it except `мета/бобер` and `💬 чаты`. ### 4. Mint a token Open admin at `http://localhost:62992` (or `https:///admin/` if Caddy), sign in with `ADMIN_USER` / `ADMIN_PASS`, go to **Tokens → Create**, scope `*` for first run. ### 5. Smoke test ```bash docker exec beaver-gateway ls /vault | head docker logs beaver-gateway | grep -i "agent registered" curl http://localhost:62990/v1/models \ -H "Authorization: Bearer " ``` ## Where to plug things in The admin dashboard renders ready-to-copy URLs and snippets for each of these - any Anthropic client: `http://localhost:62990` or `https:///anthropic`, model = agent name (`beaver-opus-high` etc) - MCP clients (Claude Desktop, Raycast extension): just find in admin - Obsidian companion plugin: paste the dashboard's **plugin base** (`http://localhost:62993` raw, or `https:///md` behind Caddy) into the plugin's "Base URL" setting - **Admin UI:** `http://localhost:62992` or `https:///admin`, login from `ADMIN_USER` / `ADMIN_PASS` ## Exposing to the internet Reference config in `caddy`. Designed for: gateway on rpi, Caddy on a server with a public IP, wired through tailscale. ```bash cd caddy cp Caddyfile.example Caddyfile cp .env.example .env # only for cloudflare # replace ; point upstreams at wherever the gateway is reachable docker compose up -d ``` Set `PUBLIC_BASE_URL=https://` in beaver-agent's `.env`. For per-subdomain layout, rewrite the Caddyfile and pass full `public_base_url=...` per frontend in `config.py`. ## Useful commands ```bash # shell into the gateway container (claude CLI, bun, python with venv all live here) docker exec -it beaver-gateway bash # what obsidian-sync has pulled docker exec beaver-obsidian ob status # force a one-off sync (don't wait for the continuous tick) docker exec beaver-obsidian ob sync # per-service logs docker compose logs -f gateway docker compose logs -f obsidian-headless # pull the latest beaver-gateway (when GATEWAY_REF=main) docker compose build gateway && docker compose up -d gateway # reload config.py without a full rebuild docker compose restart gateway ``` ## Gotchas - **claude in the container doesn't see the vault** - `cwd=VAULT` in `config.py` resolves to `/vault` *inside* the container, not on the host. Don't change it. - **a claude turn fails immediately** - check `docker logs beaver-gateway` for the `claude[]:` stderr lines. Usually auth: redo step 3. - **gateway restarts in a loop right after first `up`** - `config.py` reads prompt granules from `/vault/мета/бобер/промпты`; until Obsidian Sync has pulled the vault they are missing. Finish step 2, it settles. - **the model cannot write into `мета/бобер`** - the entrypoint grants `beaver-runner` an ACL on the two rw sub-mounts once at start; files that Sync creates later inherit it through the default ACL. If `setfacl` is unsupported on the volume it falls back to `chown`, and then files Sync writes afterwards as root stay read-only for the model until the next restart.