Files
harness-tools/README.md
T

528 lines
20 KiB
Markdown

# harness-tools
Personal [Pi](https://github.com/earendil-works/pi-mono) extensions, skills, global agent instructions, sandbox profiles, and supporting services.
## What this repository installs
[`config/pi-links.json`](config/pi-links.json) is the source of truth for the normal installation. The link manager exposes:
- global agent instructions for Pi, Codex, and Claude;
- 34 shared skills under `~/.agents/skills/`;
- the `manage-pi-links` and `nono-profiles` project-only skills under `.pi/skills/`;
- five global Pi extensions under `~/.pi/agent/extensions/`:
- **dictate**: local Linux voice dictation;
- **effort-controls**: `/effort` reasoning selection and `/fast` priority mode;
- **pi-workflow**: persisted, gated feature workflows;
- **prompt-snippets**: per-message prompt rule toggles;
- **update**: `/update` and `--update` support.
The repository also contains `copy-all`, `file-search`, and `subagents` extensions, but the normal manifest does not install them globally. The `pptx`, `stop-slop`, `subagents`, and `xlsx` skills are also intentionally excluded. Archived resources under `__archived/` are never installed. Update `config/pi-links.json` deliberately if that policy changes.
## Fresh-machine setup
The commands below reproduce the normal setup. They assume Linux, a systemd user session, and the current canonical checkout path. Pi and the shared skills work on other platforms, but the nono launcher and Dictate sections are Linux-specific.
### 1. Install the base tools
Required:
- Git with access to `git.jb.codes`;
- Node.js 22.19 or newer with npm;
- Pi Coding Agent;
- a configured model provider.
Install Pi using its official npm package:
```bash
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version
```
A Bun global install also works, but record its executable path later when configuring `pi-sbx`.
Start Pi once and authenticate with a subscription or API-key provider:
```bash
pi
```
Run `/login` inside Pi. Pi stores its user state under `${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}`. When Pi runs through nono, prefer nono credential routes so real API keys do not enter the sandbox; see [Sandboxed Pi credentials](#sandboxed-pi-credentials).
### 2. Clone the repository
Several current profiles and instruction imports contain `/srv/data/code/harness-tools`, so using that path requires no repository edits:
```bash
if [ ! -e /srv/data/code ]; then
sudo install -d -o "$USER" -g "$(id -gn)" /srv/data/code
fi
test -d /srv/data/code && test -w /srv/data/code || {
echo '/srv/data/code already exists but is not a writable directory' >&2
exit 1
}
git clone ssh://git@git.jb.codes:7722/jb/harness-tools.git \
/srv/data/code/harness-tools
cd /srv/data/code/harness-tools
```
The setup creates `/srv/data/code` only when absent. If it already exists, keep its current ownership and obtain access through the machine's normal administration policy.
If the checkout lives elsewhere, replace the machine-specific paths before syncing:
- `docs/global.CLAUDE.md`;
- `profiles/nono/pi-local.json`;
- `profiles/nono/pi-orca-local.json`;
- `profiles/nono/pi-work.json`;
- the copied `~/.config/pi-sbx/config.json` described below.
Dictate currently targets `/run/user/1000/pi-dictate.sock` in both the extension and profile. It therefore expects UID 1000. Change and review the socket path before installation if `id -u` reports another UID.
### 3. Install repository dependencies
The linked TypeScript extensions resolve runtime dependencies from this checkout, so `node_modules` is required even though the extensions have no build step:
```bash
cd /srv/data/code/harness-tools
npm ci
npm run typecheck
```
Use `npm ci` after pulling a lockfile change. Node.js must satisfy the version in [`package.json`](package.json).
### 4. Install instructions, skills, and extensions
Preview the exact changes first, then apply them:
```bash
cd /srv/data/code/harness-tools
./scripts/pi-links.mjs sync --dry-run
./scripts/pi-links.mjs sync
./scripts/pi-links.mjs check
```
A successful check currently reports 44 links and zero local package entries. The manager:
- preserves unrelated global resources;
- refuses to overwrite regular files, directories, or links owned outside this repository;
- uses absolute links for global resources and relative links for project-only skills.
Resolve any reported collision manually instead of deleting it blindly. Run `/reload` in an active Pi session, or restart Pi, after the sync.
Do not also run `pi install /path/to/harness-tools` for the normal setup. That installs the root as a Pi package and applies the broader `package.json` resource globs, which is different from the curated `config/pi-links.json` policy and can duplicate resources. It remains useful for a temporary package-development test in an isolated Pi agent directory.
### 5. Verify Pi
```bash
pi
```
Inside Pi:
1. Run `/reload`.
2. Confirm `/effort`, `/snippets`, `/update`, and `/workflow` are available.
3. Ask Pi to list its available skills and confirm a shared skill such as `tdd` appears.
4. From this repository, confirm the project-only `manage-pi-links` and `nono-profiles` skills appear.
## Additional Pi packages from Gitea
Two extensions are maintained in separate Gitea repositories rather than `harness-tools`. Install them as Pi-managed Git packages after completing the canonical link sync above. Pi clones each package into its agent directory and installs its package dependencies.
Run these commands from a normal host terminal. A restrictive nono session may intentionally lack the SSH key or agent socket needed to clone private repositories.
### Verify Gitea SSH access
Both repositories use SSH on port 7722. Confirm access without changing either checkout:
```bash
GIT_TERMINAL_PROMPT=0 git ls-remote \
ssh://git@git.jb.codes:7722/jb/pi-interactive-subagents.git HEAD
GIT_TERMINAL_PROMPT=0 git ls-remote \
ssh://git@git.jb.codes:7722/jb/pi-todo-herdr.git HEAD
```
Each command should print a commit hash and `HEAD`. Configure the machine's SSH key and `git.jb.codes` host key before continuing if either command fails.
### Install both packages
```bash
pi install ssh://git@git.jb.codes:7722/jb/pi-interactive-subagents.git
pi install ssh://git@git.jb.codes:7722/jb/pi-todo-herdr.git
pi list
```
Confirm both Gitea URLs appear under `User packages`, then restart Pi or run `/reload`. These package entries are independent of `config/pi-links.json`, so `./scripts/pi-links.mjs check` still reports zero local package entries.
Do not globally enable `harness-tools/extensions/subagents` at the same time as `pi-interactive-subagents`. They provide overlapping subagent tools. The curated harness-tools manifest leaves its in-repository implementation disabled for this reason.
### Configure interactive subagents
[`pi-interactive-subagents`](https://git.jb.codes/jb/pi-interactive-subagents) creates asynchronous child Pi sessions in Herdr tabs or tmux panes. It requires one terminal backend:
- **Herdr:** install its Pi integration, then launch Pi inside a Herdr-managed pane;
- **tmux:** launch Pi inside tmux when Herdr is unavailable.
For Herdr:
```bash
herdr integration install pi
herdr integration status
herdr
```
Start Pi in a Herdr pane. The extension detects `HERDR_ENV=1` and opens each subagent in an unfocused tab. The `pi-local` nono profile already declares the reviewed Herdr socket used on the original machine; update that machine-specific path if necessary.
For tmux:
```bash
tmux new -A -s pi 'pi'
```
The package bundles `scout`, `researcher`, and `worker` agent definitions. Their configured provider, model, thinking level, and tool allowlist live in the package's `agents/` directory. Authenticate the configured provider and confirm the named model is available. To override a bundled definition without editing Pi's managed Git clone, create a same-named file in `${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/agents/` or a trusted project's `.pi/agents/`; project definitions take precedence over global definitions, which take precedence over package definitions.
Inside Pi, verify and try the extension:
1. Run `/reload`.
2. Ask Pi to call `subagents_list`.
3. Confirm `scout`, `researcher`, and `worker` are listed.
4. Run `/subagent scout Summarize this repository` from a disposable test checkout.
5. Confirm a Herdr tab or tmux pane opens and the completed result returns to the parent session.
The status widget is enabled by the package's example configuration when no local `config.json` exists. Copy and edit `config.json.example` beside the installed package only when a status override is needed. Treat Pi's managed Git checkout as disposable because package updates may reset it; prefer global or project agent overrides for durable customization.
### Configure Pi Todo Herdr
[`pi-todo-herdr`](https://git.jb.codes/jb/pi-todo-herdr) provides the `set_tasks`, `update_task`, `rm_task`, `list_task`, and `get_task` tools plus a session-local Pi task widget. Herdr is optional for the tools and widget.
Inside Pi after `/reload`:
1. Confirm the five task tools are available.
2. Run `/todo` to toggle the widget.
3. Ask Pi to create a small three-step test plan, update one task, and list the tree.
For Herdr sidebar task metadata, back up and edit the existing configuration:
```bash
cp -a ~/.config/herdr/config.toml \
~/.config/herdr/config.toml.before-pi-todo-herdr
"${EDITOR:-vi}" ~/.config/herdr/config.toml
```
If `[ui.sidebar.agents]` already exists, edit its existing `rows` value and add `"$task_progress"`, `"$task"`, and `"$task_count"` where desired. Do not add a duplicate TOML table header. If the table does not exist, append the complete block:
```toml
[ui.sidebar.agents]
rows = [["state_icon", "workspace", "tab"], ["$ask", "$ask_count"], ["$task_progress", "$task", "$task_count"]]
```
Apply and verify the change:
```bash
herdr server reload-config
herdr status client
```
### Update or remove the Gitea packages
Use the same source strings stored by Pi:
```bash
pi update ssh://git@git.jb.codes:7722/jb/pi-interactive-subagents.git
pi update ssh://git@git.jb.codes:7722/jb/pi-todo-herdr.git
```
Remove them with:
```bash
pi remove ssh://git@git.jb.codes:7722/jb/pi-interactive-subagents.git
pi remove ssh://git@git.jb.codes:7722/jb/pi-todo-herdr.git
```
Run `/reload` after an update or removal.
### Optional development checkouts
A Pi-managed package clone is enough for normal use. For source development, use separate durable checkouts so Pi package updates cannot discard work:
```bash
mkdir -p /srv/data/repo-store
git clone ssh://git@git.jb.codes:7722/jb/pi-interactive-subagents.git \
/srv/data/repo-store/pi-interactive-subagents
git clone ssh://git@git.jb.codes:7722/jb/pi-todo-herdr.git \
/srv/data/repo-store/pi-todo-herdr
npm ci --prefix /srv/data/repo-store/pi-interactive-subagents
npm test --prefix /srv/data/repo-store/pi-interactive-subagents
npm ci --prefix /srv/data/repo-store/pi-todo-herdr
npm run typecheck --prefix /srv/data/repo-store/pi-todo-herdr
npm test --prefix /srv/data/repo-store/pi-todo-herdr
```
Both repositories are separate projects. Keep their changes and releases out of the `harness-tools` checkout.
## Companion tools used by installed skills
The link sync installs skill instructions, not every external program those skills may call. Install the companion tools you intend to use.
### OpenSrc for dependency research
`code-research` and `package-source` require the `opensrc` CLI and intentionally use `/srv/data/opensrc` as the single source cache:
```bash
npm install -g --prefix "$HOME/.local" opensrc
if [ ! -e /srv/data/opensrc ]; then
sudo install -d -o "$USER" -g "$(id -gn)" /srv/data/opensrc
fi
test -d /srv/data/opensrc && test -w /srv/data/opensrc || {
echo '/srv/data/opensrc already exists but is not a writable directory' >&2
exit 1
}
if [ ! -e "$HOME/.opensrc" ] && [ ! -L "$HOME/.opensrc" ]; then
ln -s /srv/data/opensrc "$HOME/.opensrc"
fi
command -v opensrc
readlink -f "$HOME/.opensrc"
```
The setup preserves existing directory ownership and any existing `~/.opensrc`. If that path already exists, inspect it instead of replacing it. Completion requires it to resolve to `/srv/data/opensrc`.
If `~/.opensrc` already exists, inspect it instead of replacing it. Completion requires it to resolve to `/srv/data/opensrc`.
### Browser Harness
The `browser-harness` skill requires `uv`, Chrome or Chromium, and the Browser Harness CLI:
```bash
uv tool install --python 3.12 --upgrade --force browser-harness
browser-harness --doctor
browser-harness <<'PY'
print(page_info())
PY
```
For a Browser Use cloud session, authenticate separately with `browser-harness auth login`. Local browser control does not require a cloud key.
### Artifact presentation
The `artifact-presenter` skill uses [Herdr](https://herdr.dev) and [mcat](https://github.com/Skardyy/mcat). Install both and verify:
```bash
command -v herdr
command -v mcat
herdr --version
mcat --version
```
On Arch-based systems, mcat is available as the `mcat-bin` AUR package. Follow Herdr's installation guide for its CLI. The skill falls back to reporting the artifact path when either program is unavailable.
### PDF work
The `pdf` skill calls tools as needed rather than maintaining a repository environment. For its common workflows, install the relevant subset of:
- Python packages: `pypdf`, `pdfplumber`, `reportlab`, `pymupdf`, `pytesseract`, and `pdf2image`;
- system tools: Poppler (`pdftotext`, `pdfimages`, and `pdftoppm`), `qpdf`, and Tesseract OCR.
The intentionally excluded `pptx` and `xlsx` skills have additional office and rendering dependencies and are not part of the normal setup.
## Optional Linux sandbox setup
The repository-managed [nono](https://github.com/always-further/nono) profiles and `pi-sbx` launcher are optional, but they reproduce the normal restricted Pi runtime.
### Install nono and its Pi package
Install the `nono` CLI using its upstream instructions, then pull the parent Pi profile:
```bash
nono pull nolabs-ai/pi
nono profile show nolabs-ai/pi
```
### Link the repository profiles
```bash
cd /srv/data/code/harness-tools
mkdir -p ~/.config/nono/profiles
for profile in pi-local pi-orca-local pi-harness-tools pi-work; do
source="$PWD/profiles/nono/$profile.json"
destination="$HOME/.config/nono/profiles/$profile.json"
if [ -L "$destination" ] && \
[ "$(readlink -f "$destination")" = "$(readlink -f "$source")" ]; then
continue
fi
if [ -e "$destination" ] || [ -L "$destination" ]; then
echo "Refusing to replace existing profile: $destination" >&2
exit 1
fi
ln -s "$source" "$destination"
done
for profile in pi-local pi-orca-local pi-harness-tools pi-work; do
nono profile validate "$profile"
done
```
The loop is rerunnable for links already targeting this checkout and refuses every other collision.
These profiles inherit from one another, so install and validate all four even if only `pi-local` is used initially. Review their filesystem paths, Orca integration paths, socket grants, network allowlist, and work-specific Git path on every new machine.
### Install the routed launcher
`pi-sbx` is a Fish script. Install Fish, then link the launch commands and systemd slice:
```bash
cd /srv/data/code/harness-tools
mkdir -p ~/.local/bin ~/.config/pi-sbx ~/.config/systemd/user
safe_link() {
source="$1"
destination="$2"
if [ -L "$destination" ] && \
[ "$(readlink -f "$destination")" = "$(readlink -f "$source")" ]; then
return 0
fi
if [ -e "$destination" ] || [ -L "$destination" ]; then
echo "Refusing to replace existing path: $destination" >&2
return 1
fi
ln -s "$source" "$destination"
}
safe_link "$PWD/scripts/pi-sbx.fish" "$HOME/.local/bin/pi-sbx"
safe_link "$PWD/scripts/pi-sbx-profile.py" "$HOME/.local/bin/pi-sbx-profile"
safe_link "$PWD/scripts/pi-orca.fish" "$HOME/.local/bin/pi-orca"
safe_link "$PWD/scripts/orca-nono-profile.py" "$HOME/.local/bin/orca-nono-profile"
safe_link "$PWD/systemd/user/pi-agents.slice" \
"$HOME/.config/systemd/user/pi-agents.slice"
if [ ! -e "$HOME/.config/pi-sbx/config.json" ]; then
cp "$PWD/config/pi-sbx.example.json" "$HOME/.config/pi-sbx/config.json"
chmod 600 "$HOME/.config/pi-sbx/config.json"
else
echo 'Keeping existing ~/.config/pi-sbx/config.json'
fi
systemctl --user daemon-reload
```
The setup preserves an existing launcher, systemd unit, or routing configuration unless it already links to this checkout. Resolve reported collisions manually after inspecting both paths.
Edit `~/.config/pi-sbx/config.json` before launching. Set:
- `nodeExecutable` to `readlink -f "$(command -v node)"`;
- `piExecutable` to the real Pi executable path;
- `piAgentDirectory` to the matching Pi state directory;
- directory routes to paths that exist on this machine;
- profile-specific runtimes only when a different Node ABI or Pi state directory is intentional.
The checked-in example contains paths from the original machine and is a template, not a portable final configuration. Separate Pi agent directories are required when profiles use incompatible Node ABIs with native dependencies.
Verify and launch:
```bash
pi-sbx-profile list
pi-sbx-profile show
pi-sbx-profile set pi-local /path/to/a/project
pi-sbx-profile resolve /path/to/a/project
pi-sbx
```
See [`docs/nono.md`](docs/nono.md) for routing behavior, multi-repository grants, Internet tiers, profile editing, and troubleshooting denials.
### Sandboxed Pi credentials
The base `nolabs-ai/pi` package supports mediated credential routes. Store secrets in the OS keychain or another nono-supported secret source, then enable only the required route names in a reviewed child profile. For example, supported built-in route names include `openai`, `anthropic`, `gemini`, `github`, and `gitlab`.
Do not place real provider keys in files readable by sandboxed Pi if the goal is credential mediation. Consult the installed package guide after `nono pull`:
```text
~/.config/nono/packages/nolabs-ai/pi/README.md
```
## Optional Dictate service
Dictate is Linux-only and requires:
- PipeWire with `pw-record`;
- systemd user services;
- an NVIDIA GPU with a working CUDA runtime;
- `uv`;
- network access for the initial locked dependency and model download;
- UID 1000 unless the hardcoded socket path is changed first.
Install the host service from the repository root:
```bash
cd /srv/data/code/harness-tools
./services/dictate/install.sh
systemctl --user status pi-dictate.socket pi-dictate.service
```
The installer provisions the locked Python environment, verifies the pinned model, installs host-owned systemd units, and enables socket activation. Initial storage is several gigabytes. To reuse an existing model archive or preview the operation:
```bash
./services/dictate/install.sh --model-source /path/to/parakeet-tdt-0.6b-v3.nemo
./services/dictate/install.sh --dry-run --model-source /path/to/model.nemo
```
Restart a sandboxed Pi after profile or socket changes. In Pi, press `Alt+M` to start or stop dictation and `Alt+N` to cancel. See [`extensions/dictate/README.md`](extensions/dictate/README.md) for privacy, dictionary, model, and troubleshooting details.
## Maintenance
After pulling repository changes:
```bash
cd /srv/data/code/harness-tools
git pull --rebase
npm ci
./scripts/pi-links.mjs sync --dry-run
./scripts/pi-links.mjs sync
./scripts/pi-links.mjs check
npm run typecheck
```
Run `/reload` after skill, extension, instruction, or separately installed Gitea package changes. Re-run `./services/dictate/install.sh` when Dictate's host service or protocol changes.
Before removing repository-managed exposure, preview exactly what will be deleted:
```bash
./scripts/pi-links.mjs prune --dry-run
```
Only run `./scripts/pi-links.mjs prune` after reviewing every removal. The manager prunes repository-owned exposure and leaves unrelated global resources alone.
## Development and checks
Extensions are TypeScript modules loaded directly by Pi; no build step is required.
```bash
npm ci
npm run typecheck
npm test
```
For a one-off extension development run:
```bash
pi -e ./extensions/effort-controls
pi -e ./extensions/update
```
## Repository layout
```text
config/ Installation and launcher manifests
docs/ Usage guides, plans, and ADRs
extensions/ Pi extensions
profiles/nono/ Repository-managed nono profiles
scripts/ Link manager and sandbox launch helpers
services/ Host-side services used by extensions
skills/ Pi skills, one SKILL.md per skill
systemd/user/ Repository-managed systemd user units
```
## License
MIT