# 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