Compare commits

..

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
12 changed files with 252 additions and 95 deletions

View file

@ -10,15 +10,6 @@ Use this skill for the static site generator that builds tenant homes and tenant
This skill is also responsible for the transition from the current plain HTML
site to the future Astro + Strapi setup in the `cms` jail.
Legacy public sites still exist outside Astro. In particular:
- `html/osa/` is the live source for `osa.smilepowered.org`
- `html/clawdie/` is still a hand-edited public landing tree
Do not assume every public HTML change belongs in `bootstrap/cms/*`. If the
requested copy already exists under `html/`, edit that source directly unless
the task is an explicit migration into Astro.
## Scope
This skill covers:
@ -100,7 +91,6 @@ Repo paths:
- Landing source: `bootstrap/cms/clawdie-si/`
- Docs source: `bootstrap/cms/clawdie-docs/`
- Public docs source of truth: `docs/public/`
- Legacy OSA landing source: `html/osa/`
Inside the `cms` jail:

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.');