Compare commits

...
Sign in to create a new pull request.

12 commits

Author SHA1 Message Date
9841c24ec9 Merge pull request 'Document the Vaultwarden fetch contract' (#12) from secrets-out-of-the-box into main
Some checks failed
Crowdin Sync / sync (push) Has been cancelled
Reviewed-on: #12
2026-06-23 06:57:41 +02:00
7da997402a Merge pull request 'feat(pkg): FreeBSD host baseline — ffmpeg, py311-pillow, python311 (hermes)' (#17) from feat/freebsd-hermes-runtime-pkgs into main 2026-06-21 10:29:02 +02:00
Sam & Claude
39dbee3b61 feat(pkg): FreeBSD host baseline — ffmpeg, py311-pillow, python311 (hermes runtime)
Some checks failed
CI / ci (pull_request) Has been cancelled
Keep the host baseline in sync with clawdie-iso pkg-list-host.txt (Tier 1 hermes
deps). ffmpeg (media/voice), py311-pillow (hermes core Pillow dep via
--system-site-packages venv), python311 explicit (python3 = 3.11).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:26:38 +02:00
393b0c76c7 Merge pull request 'docs(vault): update domedog findings — end-to-end PASS, bug found+fixed' (#16) from docs/vault-domedog-proof-passed into main
Some checks failed
Crowdin Sync / sync (push) Has been cancelled
Reviewed-on: #16
2026-06-20 07:11:32 +02:00
Sam & Claude
c2d5b5224f docs(vault): update domedog findings — end-to-end PASS, bug found+fixed
Some checks failed
CI / ci (pull_request) Has been cancelled
Rewrite the findings to reflect the corrected outcome after the first draft
misdiagnosed unlock as a password failure:

- End-to-end chain PASS: login + unlock + fetch DEEPSEEK_API_KEY + write .env
  + re-lock. Master password was correct all along; the earlier 'decryption
  failed' was a stale logged-in session side effect.
- Document the one real bug: clawdie-vault-fetch failed at 'bw config server'
  when already logged in ('Logout required' treated as fatal). Fixed in
  clawdie-iso fix/vault-fetch-bw-config-when-logged-in.
- Record setup state on domedog (bw path, staged helper, bootstrap file).
- Carry forward the not-wired-yet follow-up: runtime consumption (agent reads
  the fetched .env at launch) is the next milestone.

Checks: prettier clean; git diff --check.

Co-Authored-By: Hermes & Sam <hello@clawdie.si>
2026-06-20 07:06:44 +02:00
85ea20c5ba Merge pull request 'docs(vault): domedog connectivity findings — login works, unlock fails (Sam & Claude)' (#15) from docs/vaultwarden-domedog-findings into main
Some checks are pending
Crowdin Sync / sync (push) Waiting to run
2026-06-19 19:07:09 +02:00
Sam & Claude
8c78369adf docs(vault): domedog connectivity findings — login works, unlock fails (Sam & Claude)
Some checks failed
CI / ci (pull_request) Has been cancelled
Tested the vault-fetch verification flow from domedog against
vault.smilepowered.org. bw login --apikey succeeds (authenticated as
samo.blatnik@gmail.com) but bw unlock fails with a decryption error —
BW_PASSWORD in the bootstrap env doesn't match the vault's master key.
Action: update BW_PASSWORD. PR #65 code assessed as solid, no merge blocker.
2026-06-19 19:01:20 +02:00
151f366cc2 Merge pull request 'VAULTWARDEN-SETUP: document name-based retrieval contract' (#14) from fix/vault-fetch-contract-docs into main
Some checks are pending
Crowdin Sync / sync (push) Waiting to run
2026-06-19 18:36:40 +02:00
Sam & Claude
47c661bbc7 docs(vault): align VAULTWARDEN-SETUP with clawdie-vault-fetch contract
Some checks failed
CI / ci (pull_request) Has been cancelled
The fetch helper (clawdie-iso) retrieves secrets by item NAME via
'bw get password' and no longer scopes by collection ID, but this doc still
taught the old contract (collectionid + jq). New agents following it would store
items the helper cannot read, and its verification test would fail.

- Document the retrieval contract: one login item per secret, ITEM NAME = env
  var name, value in the password field. Item names must be unique in the
  visible vault (fetch is fail-closed on ambiguity).
- Rewrite the Verification Test to use clawdie-vault-fetch end-to-end, with a
  raw 'bw get password' fallback for hosts without the helper yet.
- Rewrite 'Retrieve a secret' to fetch by name + prefer --write-env upsert.
- Drop the hard-coded collection UUID from the fetch path.

Companion to clawdie-iso fix(vault): wire seed bootstrap -> vault-fetch path.

Checks: npx prettier@3 --check (clean); git diff --check.

Co-Authored-By: Hermes & Sam <hello@clawdie.si>
2026-06-19 18:26:43 +02:00
3f50711ff8 Merge pull request 'chore(freebsd): align host baseline with Python 3.12' (#13) from chore/python312-baseline into main 2026-06-17 16:16:53 +02:00
e1d4fd4441 chore(freebsd): align host baseline with Python 3.12 (Sam & Pi)
Some checks failed
CI / ci (pull_request) Has been cancelled
---
Build: FAIL | Tests: FAIL
2026-06-17 14:57:19 +02:00
Sam & Claude
622bdee32f docs: document clawdie-vault-fetch contract in Vaultwarden setup
Some checks failed
CI / ci (pull_request) Has been cancelled
Adds the runtime-fetch section the seam depends on: item-naming
convention (item name = env var name, value in password field),
the ~/.config/vault-bootstrap.env drop, helper usage and exit-code
semantics. The manual CLI flow remains the floor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 08:46:08 +02:00
11 changed files with 252 additions and 85 deletions

View file

@ -341,8 +341,8 @@ clawdie-iso/
Derived from `setup/environment.ts` host baseline + desktop-installer + DE packages:
**Clawdie host baseline:**
`node24`, `npm`, `bsddialog`, `bastille`, `git`, `tmux`, `python311`, `uv`,
`ripgrep`, `fd-find`, `rsync`, `postgresql18-client`, `py311-pillow`, `dejavu`
`node24`, `npm`, `bsddialog`, `bastille`, `git`, `tmux`, `python312`, `uv`,
`ripgrep`, `fd-find`, `rsync`, `postgresql18-client`, `dejavu`
**Xorg baseline:**
`xorg-minimal`, `xf86-video-intel`, `drm-kmod`,

View file

@ -53,7 +53,7 @@ See [docs/public/architecture/warden.md](docs/public/architecture/warden.md).
git clone https://codeberg.org/Clawdie/Clawdie-AI.git /home/clawdie/clawdie-ai
cd /home/clawdie/clawdie-ai
pkg install node24 npm git python312 py312-uv rsync
pkg install node24 npm git python312 uv rsync
npm install
npm install -g @earendil-works/pi-coding-agent
# If setup.sh did not launch onboarding automatically:
@ -102,12 +102,14 @@ The current FreeBSD deployment depends on:
### `controlplane`
Role:
- main Clawdie control-plane jail
- Telegram intake
- scheduling
- Warden task dispatch
Profile:
- `freebsd-jail`
- `thick`
- `vnet`
@ -116,10 +118,12 @@ Profile:
### `db`
Role:
- PostgreSQL memory backend
- persistent service
Profile:
- `freebsd-jail`
- `thick`
- `vnet`
@ -134,19 +138,23 @@ Profile:
## Snapshot Policy
Manual milestone snapshots:
- human-named
- day-first
- month abbreviation
Examples:
- `@postgres18-ready-08.mar.2026`
- `@fresh-08.mar.2026`
Automatic snapshots:
- handled by Sanoid
- keep Sanoid's internal `autosnap_...` naming
Current automated Sanoid targets:
- `zroot/clawdie-runtime/jails/db`
- `zroot/clawdie-runtime/jails/controlplane`

View file

@ -155,12 +155,12 @@ just setup-cms # npm run setup -- --step cms
- Internet access for `pkg` and `npm`
- `bash` and `git` available before first run
- **`just`** — command runner (`pkg install just` on FreeBSD; preinstalled on Clawdie ISO)
- **At least one agent CLI on `PATH`**: one of `pi`, `aider`, `claude`, `codex`, or `gemini`. The controlplane harness uses Aider+Pi as the primary driver. Onboarding fails fast if none are present (see [`doc/AGENT-CLI-VALIDATION.md`](doc/AGENT-CLI-VALIDATION.md) for the validated install paths). The Clawdie ISO ships claude/gemini/pi via the npm-globals bundle, aider via `py311-aider_chat` pkg, and codex via `pkg install codex`.
- **At least one agent CLI on `PATH`**: one of `pi`, `aider`, `claude`, `codex`, or `gemini`. The controlplane harness uses Aider+Pi as the primary driver. Onboarding fails fast if none are present (see [`doc/AGENT-CLI-VALIDATION.md`](doc/AGENT-CLI-VALIDATION.md) for the validated install paths). The Clawdie ISO ships claude/gemini/pi via the npm-globals bundle, provisions Aider in a Python 3.12 venv, and includes codex via `pkg install codex`.
Recommended explicit host baseline before first run:
```sh
sudo pkg install -y bash git bastille node24 npm tmux btop python311 uv ripgrep fd-find rsync postgresql18-client py311-pillow dejavu py311-aider_chat edk2-bhyve just
sudo pkg install -y bash git bastille node24 npm tmux btop python312 uv ripgrep fd-find rsync postgresql18-client dejavu edk2-bhyve just
```
The `edk2-bhyve` package provides UEFI firmware required for optional browser-vm support
@ -169,15 +169,16 @@ The `edk2-bhyve` package provides UEFI firmware required for optional browser-vm
On FreeBSD, use `fd-find`. It provides the `fd` command that `pi` expects and
avoids colliding with the unrelated `fd` file manager package.
The host baseline also includes `py311-pillow` and `dejavu` so tmux screenshot
capture works without a separate `uv pip install Pillow` step.
Python package-flavored extras stay out of the baseline until the FreeBSD
quarterly repository publishes Python 3.12 flavors. Install Aider/Pillow-style
tools into explicit Python 3.12 venvs when needed.
If the host still has both Python 3.11 and 3.12 installed, pin `uv` to 3.11
If the host still has multiple Python minors installed, pin `uv` to 3.12
explicitly until the generic `python3` path is cleaned up:
```sh
uv venv --python 3.11
uv run --python 3.11 <command>
uv venv --python 3.12
uv run --python 3.12 <command>
```
### Operator Glasspane
@ -215,7 +216,7 @@ First use:
```bash
# 1. Install the recommended FreeBSD host baseline
sudo pkg install -y bash git bastille node24 npm tmux btop python311 uv ripgrep fd-find rsync postgresql18-client py311-pillow dejavu py311-aider_chat just
sudo pkg install -y bash git bastille node24 npm tmux btop python312 uv ripgrep fd-find rsync postgresql18-client dejavu just
# 2. Clone the repository
git clone https://codeberg.org/Clawdie/Clawdie-AI.git /home/clawdie/clawdie-ai

View file

@ -30,26 +30,22 @@ export PATH=/opt/clawdie/cargo/bin:$PATH
## Working install (tested)
1) Install Aider from packages (adds many deps but works):
1. Create a project-local Python 3.12 venv. The FreeBSD quarterly repo still
publishes only older Python-flavored Aider packages, so keep Aider out of the
host pkg baseline and install it into a venv:
```sh
sudo pkg install py311-aider_chat
python3.12 -m venv /home/clawdie/clawdie-ai/tmp/aider-venv
```
2) Create a project-local venv that can override litellm:
```sh
python3.11 -m venv --system-site-packages /home/clawdie/clawdie-ai/tmp/aider-venv
```
3) Upgrade Aider + pin litellm to the expected version inside the venv:
2. Upgrade Aider + pin litellm to the expected version inside the venv:
```sh
/home/clawdie/clawdie-ai/tmp/aider-venv/bin/pip install --no-user --no-deps --upgrade --ignore-installed aider-chat==0.86.2
/home/clawdie/clawdie-ai/tmp/aider-venv/bin/pip install --no-user --no-deps --upgrade --ignore-installed litellm==1.81.10
```
4) Align tree-sitter with the system tree-sitter-languages package (fixes repo-map crash):
3. Align tree-sitter with the system tree-sitter-languages package (fixes repo-map crash):
```sh
env TMPDIR=/home/clawdie/clawdie-ai/tmp \
@ -63,7 +59,7 @@ FreeBSD ships `tree_sitter_languages` from packages (1.10.2) which expects
TypeError: __init__() takes exactly 1 argument (2 given)
```
4) Run Aider (use the venv binary):
4. Run Aider (use the venv binary):
```sh
env AIDER_ANALYTICS_DISABLE=1 /home/clawdie/clawdie-ai/tmp/aider-venv/bin/aider \

View file

@ -0,0 +1,81 @@
# Vaultwarden Connectivity — domedog Findings (2026-06-19, updated)
**Host:** domedog (`domedog.pro`, Linux)
**Agent:** Claude
**PRs under evaluation:** clawdie-iso #65 (merged as #67) + clawdie-ai #14 (doc contract)
## Test results — end-to-end PASS ✅
Full chain proven: bootstrap creds → `bw` login → unlock → fetch → `.env` → re-lock.
| Step | Command | Result |
| ---------------- | --------------------------------------------------------- | --------------------------------------------------- |
| Server reachable | `curl -sI https://vault.smilepowered.org/` | ✅ HTTP 200 (Rocket/Vaultwarden) |
| bw CLI installed | `bw --version` | ✅ 2026.5.0 |
| API key login | `bw login --apikey` (via `BW_CLIENTID`/`BW_CLIENTSECRET`) | ✅ Logged in as `samo.blatnik@gmail.com` |
| Vault unlock | `bw unlock --passwordenv BW_PASSWORD` | ✅ Unlock succeeds — master password is correct |
| Fetch item | `clawdie-vault-fetch --keys DEEPSEEK_API_KEY` | ✅ Resolved 1 of 1 key |
| Write to `.env` | `--write-env /tmp/smoke.env` | ✅ Written 0600, value correct (35 chars, `sk-...`) |
| Re-lock on exit | trap cleanup | ✅ Vault re-locked automatically |
## The test item
A `DEEPSEEK_API_KEY` login item was created in the `agent-secrets` collection,
with the **item name = the env var name** and the **value in the password
field**, per the documented contract. The helper fetched it cleanly with
`bw get password DEEPSEEK_API_KEY` — confirming the name-based retrieval
contract works.
## One real bug found and fixed
**`clawdie-vault-fetch` failed when `bw` was already logged in.**
`bw config server "$SERVER"` refuses with `Logout required before server config
update` when the CLI is already authenticated. The helper treated that as fatal
(`exit 1`), which broke **every repeat run** on a host that is already logged in
— including the very case the helper exists for (refresh `.env` from the vault
on demand).
The `bw login` block already tolerated the analogous "already logged in" case.
Fix (clawdie-iso, branch `fix/vault-fetch-bw-config-when-logged-in`): mirror
that pattern for `bw config` — capture stderr/stdout and tolerate
`logout required` / `already configured` / `already set`, failing only on a
real error. Verified: the fixed helper runs cleanly from the logged-in state
(previously exited 1 at the config step).
## Correction to the earlier draft
An earlier draft of this doc reported `bw unlock` failing with
`Decryption failed` and concluded the master password was wrong. That was
incorrect: the unlock failure was a side effect of running the flow against a
**stale logged-in session**. After `bw logout` and a clean re-run, unlock
succeeds with the **same** master password — it was never wrong. The only
defect was the `bw config` intolerance documented above.
## Setup state on domedog
- `bw` 2026.5.0 installed at `~/.nvm/versions/node/v22.22.0/bin/bw`.
- Helper staged at `~/.colibri/clawdie-vault-fetch` (the fixed version).
- Bootstrap env at `~/.config/vault-bootstrap.env` (0600) — `BW_CLIENTID`,
`BW_CLIENTSECRET`, and `BW_PASSWORD` all correct and verified by a successful
fetch.
- Server set to `https://vault.smilepowered.org`.
## Not wired yet (documented follow-ups)
- **Runtime consumption:** the helper fetches into a `.env`, but nothing yet
loads that `.env` into a running agent's environment at launch. This is the
next milestone (soul load + harness launch).
- **Auto-refresh:** no scheduled/firstboot caller of the helper yet — it is run
manually. The `bw config` bug fix is a prerequisite for auto-refresh to be
reliable.
## PR #65 / #14 assessment
**Code quality:** solid. `clawdie-vault-fetch` has trap-based lock-on-exit,
headless `--apikey` login, tolerates "already logged in", sensible exit codes
(0/1/3/4), and a `--write-env` upsert that preserves untouched keys at 0600.
The one repeat-run bug (`bw config` intolerance) is fixed in a follow-up.
**No blockers** to the runtime-consumption milestone. The secret→`.env` path is
proven; what remains is having an agent read it.

View file

@ -1,10 +1,25 @@
# Vaultwarden Setup for Agents
Secrets store: `vault.smilepowered.org` (Vaultwarden, self-hosted).
Organization: **Clawdie** (39727691-3403-4c50-89b8-d5f24310e79c).
Collection: `agent-secrets` (94ba61b8-633c-454e-b749-f115617eeac3).
Secrets store: `vault.smilepowered.org` (Vaultwarden, self-hosted).
Organization: **Clawdie**.
Collection: `agent-secrets` (where agents' secrets are organized).
Agents use the `bw` CLI to retrieve secrets programmatically — no passwords in chat.
Agents retrieve secrets programmatically via the `clawdie-vault-fetch` helper or
the `bw` CLI — no passwords in chat.
## Retrieval contract (important)
Every secret is stored as **one login item whose ITEM NAME is exactly the env
var name** (e.g. `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`), with the **value in
the password field**. This is what `clawdie-vault-fetch` depends on:
- `bw get password <ITEM-NAME>` returns the value raw — no `jq` needed.
- Fetch is **fail-closed on ambiguity**: if two visible items share a name, the
fetch errors out instead of guessing. **Item names must therefore be unique**
across the agent account's visible vault (not just within `agent-secrets`), so
do not reuse a name in a personal collection.
- The helper does **not** scope by collection ID. The `agent-secrets` collection
is for operator organization; uniqueness is enforced at fetch time by name.
> **Note:** `bw` CLI only accepts **personal** API keys (from Account Settings).
> Organization API keys are for the REST API, not the CLI. Do not use them here.
@ -24,11 +39,13 @@ access Vaultwarden, but those credentials can't be stored in Vaultwarden itself
as the only copy.
**Operator's role:**
1. Create a Vaultwarden user account for the agent.
2. Invite the user to the Clawdie organization with access to `agent-secrets`.
3. Share the master password via a secure channel (file drop, not chat).
**Agent's role:**
1. Generate personal API key: Account Settings → Security → Keys → View API Key.
2. Write `BW_CLIENTID` (starts with `user.`), `BW_CLIENTSECRET`, and
`BW_PASSWORD` to a 0600 bootstrap file: `~/.config/vault-bootstrap.env`.
@ -44,30 +61,35 @@ operator must create the user account and deliver the master password out-of-ban
## Verification Test
After onboarding, verify with this smoke test — run from the agent host:
After onboarding, verify with this smoke test — run from the agent host. The
helper does the login/unlock/lock lifecycle and exits non-zero on failure.
```sh
# 1. Preferred: use the helper end-to-end. It reads
# ~/.config/vault-bootstrap.env (0600) and prints KEY=VALUE lines for every
# key it resolved. Exit 3 = no bootstrap file (skip); 1 = broken; 4 = no bw.
clawdie-vault-fetch
# 2. Resolve one specific key and write it into .env (0600, upsert):
clawdie-vault-fetch --write-env ~/.env --keys "OPENROUTER_API_KEY"
```
If at least one `KEY=VALUE` line prints, onboarding is complete — the helper
locks the vault on exit. To verify the raw `bw` path instead (e.g. the helper is
not installed yet):
```sh
# 1. Load bootstrap env without echoing secrets.
set -a
. ~/.config/vault-bootstrap.env
set +a
# 2. Configure, login, unlock, and capture a raw session token.
bw config server https://vault.smilepowered.org
bw login --apikey
BW_SESSION="$(bw unlock --raw --passwordenv BW_PASSWORD)"
export BW_SESSION
# 3. List items in agent-secrets collection.
bw list items --session "$BW_SESSION" --collectionid 94ba61b8-633c-454e-b749-f115617eeac3 >/dev/null
# 4. Retrieve the hermes-debby Forgejo username.
bw list items --session "$BW_SESSION" --search "hermes-debby" | jq -r '.[0].login.username'
# Expected output: hermes-debby
bw get password OPENROUTER_API_KEY --session "$BW_SESSION" # prints the value
bw lock
```
If the username resolves: Vaultwarden onboarding complete. Lock the vault (`bw lock`).
If the value resolves: Vaultwarden onboarding complete.
## Setup
@ -88,6 +110,7 @@ bw config server https://vault.smilepowered.org
Generate your personal API key: Account Settings → Security → Keys → View API Key.
You'll receive:
- `BW_CLIENTID` (starts with `user.`)
- `BW_CLIENTSECRET`
@ -120,10 +143,18 @@ bw unlock --passwordenv BW_PASSWORD
### 5. Retrieve a secret
Fetch by item **name** (the env var name); the value lives in the password
field. This is the same path `clawdie-vault-fetch` uses:
```sh
bw list items --session "$BW_SESSION" --search "hermes-debby" | jq '.[0].login'
# or get by ID
bw get item <item-id> --session "$BW_SESSION"
bw get password OPENROUTER_API_KEY --session "$BW_SESSION"
```
To upsert resolved secrets into your `.env` without copy-paste, prefer the
helper:
```sh
clawdie-vault-fetch --write-env ~/.env
```
### 6. Lock when done
@ -132,10 +163,64 @@ bw get item <item-id> --session "$BW_SESSION"
bw lock
```
## Runtime fetch: `clawdie-vault-fetch`
The manual flow above is the operator/agent CLI path. For a host to pull its own
provider keys **out of the box**, the image ships a small language-neutral
helper, `clawdie-vault-fetch` (`/usr/local/bin/`), that the post-install setup
flow shells out to and the live USB can run directly. It depends only on `bw`
no node module, no `jq`.
### Item-naming convention (the contract)
For a secret to be auto-fetchable, store it in `agent-secrets` as a **login item
whose name is exactly the env var name**, with the value in the **password
field**:
| Item name | Field | Becomes |
| -------------------- | -------- | ---------------------- |
| `ANTHROPIC_API_KEY` | password | `ANTHROPIC_API_KEY=…` |
| `OPENAI_API_KEY` | password | `OPENAI_API_KEY=…` |
| `OPENROUTER_API_KEY` | password | `OPENROUTER_API_KEY=…` |
| `ZAI_API_KEY` | password | `ZAI_API_KEY=…` |
The default key set mirrors clawdie-ai's `PROVIDER_KEY_BY_PROVIDER` (anthropic,
openai, openrouter, zai, deepseek, gemini, groq). `bw get password <NAME>`
returns the raw value, so no JSON parsing is involved.
### Bootstrap drop (the one secret that can't live in the vault)
The helper reads `~/.config/vault-bootstrap.env` (mode 0600) for the headless
credentials — exactly the file from the [Bootstrap Flow](#bootstrap-flow) above:
```sh
BW_CLIENTID=user....
BW_CLIENTSECRET=...
BW_PASSWORD=<master-password>
```
**No bootstrap file → the helper exits cleanly and does nothing**, so a host with
no vault access still uses the manual setup wizard. That is the floor; the vault
fetch only ever adds.
### Usage
```sh
clawdie-vault-fetch # print KEY=VALUE lines to stdout
clawdie-vault-fetch --write-env FILE # upsert results into FILE (0600), keys preserved
clawdie-vault-fetch --bootstrap FILE # explicit bootstrap env file
clawdie-vault-fetch --keys "A B C" # override the key-name list
```
Exit codes let a caller tell "skip" from "broken": `0` ran cleanly · `1` vault
configured but login/unlock/fetch failed · `3` no bootstrap config (fall back to
manual) · `4` `bw` not installed. The helper always `bw lock`s on exit and never
logs secret values.
## Current items in agent-secrets
| Name | Type | Purpose |
|------|------|---------|
| Name | Type | Purpose |
| -------------------- | ----- | -------------------------------------------------------- |
| hermes-debby Forgejo | login | Hermes's code.smilepowered.org password (browser access) |
## Rules

View file

@ -9,19 +9,24 @@ tmux
btop
bsddialog
codex
# python3 is 3.11 (FreeBSD PYTHON_DEFAULT); python312 available as python3.12.
python311
python312
uv
ripgrep
fd-find
rsync
postgresql18-client
dnsmasq
# hermes runtime: ffmpeg (media + voice-transcription), py311-pillow (Pillow
# core dep; venvs use --system-site-packages so the system pkg satisfies it).
ffmpeg
py311-pillow
dejavu
rust
# Controlplane harness (Aider + Pi multi-agent orchestrator)
py311-aider_chat
# Controlplane harness helpers. Aider is installed into a venv until the
# quarterly FreeBSD repo publishes a Python 3.12 package flavor.
just
# Wayland display stack — used by worker jails (cage) and operator sessions

View file

@ -71,8 +71,8 @@ install_host_pkg_baseline() {
tmux)
command -v tmux >/dev/null 2>&1 || missing_pkgs+=("$pkg")
;;
python311)
command -v python3 >/dev/null 2>&1 || missing_pkgs+=("$pkg")
python312)
command -v python3.12 >/dev/null 2>&1 || missing_pkgs+=("$pkg")
;;
uv)
command -v uv >/dev/null 2>&1 || missing_pkgs+=("$pkg")
@ -92,9 +92,6 @@ install_host_pkg_baseline() {
rust)
command -v rustc >/dev/null 2>&1 || missing_pkgs+=("$pkg")
;;
py311-pillow)
python3 -c "import PIL" >/dev/null 2>&1 || missing_pkgs+=("$pkg")
;;
dejavu)
[ -f /usr/local/share/fonts/dejavu/DejaVuSansMono.ttf ] || missing_pkgs+=("$pkg")
;;

View file

@ -38,7 +38,7 @@ export class NoAgentCliError extends Error {
[
'No agent CLI found on PATH.',
'Clawdie needs pi and aider for the Aider+Pi controlplane harness.',
'Install via the ISO bundle or: pkg install py311-aider_chat, npm install -g @earendil-works/pi-coding-agent.',
'Install via the ISO bundle or: npm install -g @earendil-works/pi-coding-agent; install Aider in a Python 3.12 venv if needed.',
'Alternative CLIs (claude, codex, gemini) are also accepted.',
].join(' '),
);

View file

@ -51,7 +51,7 @@ const HOST_PREREQUISITE_CHECKS: Record<
dnsmasq: { key: 'DNSMASQ', check: () => commandExists('dnsmasq') },
tmux: { key: 'TMUX', check: () => commandExists('tmux') },
btop: { key: 'BTOP', check: () => commandExists('btop') },
python311: { key: 'PYTHON3', check: () => commandExists('python3') },
python312: { key: 'PYTHON3', check: () => commandExists('python3.12') },
uv: { key: 'UV', check: () => commandExists('uv') },
ripgrep: { key: 'RIPGREP', check: () => commandExists('rg') },
'fd-find': { key: 'FD', check: () => commandExists('fd') },
@ -60,20 +60,8 @@ const HOST_PREREQUISITE_CHECKS: Record<
tailscale: { key: 'TAILSCALE', check: () => commandExists('tailscale') },
just: { key: 'JUST', check: () => commandExists('just') },
rust: { key: 'RUST', check: () => commandExists('rustc') },
'py311-aider_chat': { key: 'AIDER', check: () => commandExists('aider') },
node24: { key: 'NODE', check: () => commandExists('node') },
npm: { key: 'NPM', check: () => commandExists('npm') },
'py311-pillow': {
key: 'PILLOW',
check: () => {
try {
execSync('python3 -c "import PIL"', { stdio: 'ignore' });
return true;
} catch {
return false;
}
},
},
dejavu: {
key: 'DEJAVU_FONT',
check: () =>
@ -111,11 +99,9 @@ export async function run(_args: string[]): Promise<void> {
const service = commandExists('service');
const sudo = commandExists('sudo');
const jailConf = fs.existsSync('/etc/jail.conf');
const hasPython311 = commandExists('python3.11');
const hasPython312 = commandExists('python3.12');
const python3Version = commandVersion('python3');
const python3NeedsPinning =
hasPython311 && hasPython312 && python3Version.includes('3.12');
const python3NeedsPinning = hasPython312 && !python3Version.includes('3.12');
// Check for pi binary (path from env or default 'pi')
const piBin = PI_TUI_BIN;
@ -257,7 +243,6 @@ export async function run(_args: string[]): Promise<void> {
const psql = getPrereq('PSQL');
const node = getPrereq('NODE');
const npm = getPrereq('NPM');
const pillow = getPrereq('PILLOW');
const dejavuFont = getPrereq('DEJAVU_FONT');
const seatd = getPrereq('SEATD');
const weston = getPrereq('WESTON');
@ -322,13 +307,11 @@ export async function run(_args: string[]): Promise<void> {
psql: psql.present,
node: node.present,
npm: npm.present,
pillow: pillow.present,
dejavuFont: dejavuFont.present,
seatd: seatd.present,
cage: cage.present,
weston: weston.present,
vmBhyve: vmBhyve.present,
hasPython311,
hasPython312,
python3Version,
python3NeedsPinning,
@ -349,7 +332,7 @@ export async function run(_args: string[]): Promise<void> {
{
python3Version,
},
'Multiple Python versions detected. Pin uv to Python 3.11 until python3 is repointed to 3.11.',
'Python 3.12 is installed but python3 is not pinned to 3.12; use an explicit python3.12/uv pin for setup commands.',
);
}
@ -381,12 +364,11 @@ export async function run(_args: string[]): Promise<void> {
PYTHON3: python3.present,
PYTHON3_STATUS: python3.status,
PYTHON3_INSTALL_CMD: python3.installCmd,
PYTHON311: hasPython311,
PYTHON312: hasPython312,
PYTHON3_VERSION: python3Version || 'unknown',
UV_PYTHON_PIN_REQUIRED: python3NeedsPinning,
UV_PYTHON_HINT: python3NeedsPinning
? 'uv venv --python 3.11 && uv run --python 3.11 <command>'
? 'uv venv --python 3.12 && uv run --python 3.12 <command>'
: 'not_required',
NODE: node.present,
NODE_STATUS: node.status,
@ -409,9 +391,6 @@ export async function run(_args: string[]): Promise<void> {
PSQL: psql.present,
PSQL_STATUS: psql.status,
PSQL_INSTALL_CMD: psql.installCmd,
PILLOW: pillow.present,
PILLOW_STATUS: pillow.status,
PILLOW_INSTALL_CMD: pillow.installCmd,
DEJAVU_FONT: dejavuFont.present,
DEJAVU_FONT_STATUS: dejavuFont.status,
DEJAVU_FONT_INSTALL_CMD: dejavuFont.installCmd,

View file

@ -408,12 +408,21 @@ function printStep(
}
function hasPiAuthProvider(provider: string): boolean {
const authFile = path.join(process.env.HOME || '', '.pi', 'agent', 'auth.json');
const authFile = path.join(
process.env.HOME || '',
'.pi',
'agent',
'auth.json',
);
try {
const parsed = JSON.parse(fs.readFileSync(authFile, 'utf-8')) as Record<string, unknown>;
const parsed = JSON.parse(fs.readFileSync(authFile, 'utf-8')) as Record<
string,
unknown
>;
const entry = parsed?.[provider];
if (typeof entry === 'string') return entry.trim().length > 0;
if (entry && typeof entry === 'object') return Object.keys(entry).length > 0;
if (entry && typeof entry === 'object')
return Object.keys(entry).length > 0;
return false;
} catch {
return false;
@ -460,8 +469,12 @@ function printLlmStatus(envFile: string): void {
console.log(
`\n ${COLS.warn}No LLM provider auth found. Configure one after install and restart:${COLS.reset}`,
);
console.log(` ${COLS.skipped} Recommended: run pi, then /login and select ChatGPT Plus/Pro (Codex).${COLS.reset}`);
console.log(` ${COLS.skipped} Or add an API key such as ANTHROPIC_API_KEY=sk-ant-...${COLS.reset}`);
console.log(
` ${COLS.skipped} Recommended: run pi, then /login and select ChatGPT Plus/Pro (Codex).${COLS.reset}`,
);
console.log(
` ${COLS.skipped} Or add an API key such as ANTHROPIC_API_KEY=sk-ant-...${COLS.reset}`,
);
console.log(
` ${COLS.skipped} sudo service ${SERVICE_NAME} restart${COLS.reset}`,
);
@ -509,7 +522,7 @@ function printAiderTip(): void {
);
if (!hasAider) {
console.log(
` install: ${COLS.skipped}pkg install -y py311-aider_chat${COLS.reset}`,
` install: ${COLS.skipped}python3.12 -m venv /opt/clawdie/venv/aider && /opt/clawdie/venv/aider/bin/pip install aider-chat${COLS.reset}`,
);
}
console.log(` docs : ${COLS.skipped}https://aider.chat/docs/${COLS.reset}`);
@ -570,7 +583,9 @@ export async function run(argv: string[]): Promise<void> {
const opts = parseArgs(argv);
const projectRoot = process.cwd();
const envFile = path.join(projectRoot, '.env');
const envContent = fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf-8') : '';
const envContent = fs.existsSync(envFile)
? fs.readFileSync(envFile, 'utf-8')
: '';
if (getPlatform() !== 'freebsd') {
console.error('install orchestrator is FreeBSD only.');