The acceptance bar for anything Pulp can install (a `pulp tool` registry entry, `pulp add` package, or any downloadable). Validate the FULL lifecycle — install AND uninstall, from OUTSIDE a Pulp checkout, with the installed-user's binary — before its README or docs ship. TRIGGER when adding/editing tools/packages/tool-registry.json, a new binary_download/python_pip/npm_package tool, a `pulp add` importer, or writing docs that tell users to install something.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add danielraffel/pulp --skill installable-tools --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Installable Tools?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/danielraffel-installable-tools)More formats (shields.io, HTML) on the badges page.
---
name: installable-tools
description: The acceptance bar for anything Pulp can install (a `pulp tool` registry entry, `pulp add` package, or any downloadable). Validate the FULL lifecycle — install AND uninstall, from OUTSIDE a Pulp checkout, with the installed-user's binary — before its README or docs ship. TRIGGER when adding/editing tools/packages/tool-registry.json, a new binary_download/python_pip/npm_package tool, a `pulp add` importer, or writing docs that tell users to install something.
requires:
scripts:
- tools/packages/tool-registry.json
- experimental/pulp-rs/src/tool_registry.rs
---
# installable-tools — validate before you promote
A thing Pulp can install is not "done" when the code compiles or when it works
in your repo checkout. It is done when a **user who installed Pulp** can install
it, use it, and uninstall it — from their own project directory, not the Pulp
source tree. Ship the README only after that is proven.
## Why this skill exists
`pulp tool install trace-processor` shipped as a first-class tool (with a whole
showcase README promoting it) but worked **only inside a Pulp git checkout**:
the registry is resolved by walking up from cwd for
`tools/packages/tool-registry.json`, which installed users don't have, so it
errored `Tool registry not found`. It got that far because every validation ran
from inside the repo. Uninstall had a second latent problem — it never validated
the tool id, so `uninstall ../../x` could delete outside the managed tree.
Both are the kind of gap that only a from-the-user's-seat, full-lifecycle test
catches. This skill is that checklist.
## The bar (all four, every time)
1. **Install from OUTSIDE any checkout.** `cd` to a scratch dir whose parents
contain no `tools/packages/tool-registry.json`, point `PULP_HOME` at a throwaway
dir, and run the install with the **user-facing `pulp`** (the installed binary
or a fresh `./build/pulp`), not a raw in-repo build invocation.
2. **Use it.** Confirm the installed artifact actually runs / is found by the
command that consumes it (`pulp trace query`, etc.).
3. **Uninstall from OUTSIDE any checkout.** It must remove exactly what it
installed, **name the path it deleted**, and leave a clear message when
nothing matched.
4. **Prove uninstall is safe.** A hostile id (`..`, `../x`, `a/b`, absolute
path, empty) must be **refused before any deletion**, with a plant-a-victim
test asserting nothing outside the managed tree was touched.
Only after 1–4 pass do you write or update the README/docs that tell users to
run the command.
## Registry resolution (why standalone works)
`pulp tool` resolves its registry as: repo `tools/packages/tool-registry.json`
(walk up from cwd) **first** — so a Pulp dev's edits show without a rebuild —
else the copy compiled into the CLI (`EMBEDDED_REGISTRY_JSON` /
`tool_registry::resolve_registry`). Adding a tool to the JSON is enough for the
Rust-native verbs (`list`/`info`/`path`/`doctor`/`uninstall`, and any tool whose
install is handled Rust-side) to work standalone. **Known boundary:** archive
tools whose install delegates to `pulp-cpp` (tar/zip extraction — uv, deno,
ffmpeg) still need the registry reachable by the C++ side; a bare-binary or
self-fetching tool (like trace-processor, which routes to its verified fetcher)
does not. If you add a delegated archive tool, validate its standalone install
explicitly or thread the registry to the delegate.
Large or separately licensed tools that must never join `pulp tool install
--all` set `explicit_install_only: true` and provide a named Rust-side
installer. Chrome for Testing is the reference: its complete versioned archive
is SHA-256 pinned, extracted transactionally below
`$PULP_HOME/tools/chrome-for-testing/<version>/<platform>/`, and selected only
through the exact `current.json` manifest. Imports never trigger its download.
Test install, repeat install, forced update, `doctor --run`, and uninstall from
outside the checkout; `doctor --run` must use a bounded probe such as
`--version`, never launch a long-lived GUI.
## Friendly aliases (discoverability)
A tool may list `aliases` in its registry entry so a natural name resolves to the
canonical id — `pulp tool install perfetto` reaches `trace-processor`. Resolution
happens once at the `run()` dispatch boundary (`ToolRegistry::canonical_id`), so
every verb (install/info/uninstall/path/doctor/update) accepts the alias. This is
what lets a Claude Code plugin user in a Pulp project say "install perfetto" and
have it work. Add an alias when the tool's product name differs from its id;
keep it exact-match (no fuzzy matching that could mis-resolve).
## Uninstall safety (deleting is sensitive)
`uninstall_tool` (`tool_registry.rs`) is the one place that calls
`remove_dir_all`. Two independent guards, both tested:
- `validate_tool_id` rejects any id that is not a single safe path component.
- After joining a validated id, the target must be a **direct child** of a
managed root (`tools/<id>`, `tools/python-envs/<id>`, `tools/npm-packages/<id>`).
It returns the removed `PathBuf` so the command can tell the user exactly what
was deleted. Never delete silently; never widen the id contract without adding a
hostile-id test.
## The recipe
```bash
BIN=./build/pulp # or the installed ~/.pulp/bin/pulp
H=$(mktemp -d) # throwaway PULP_HOME — never the real one
cd "$(mktemp -d)" # scratch cwd, no registry above it
PULP_HOME="$H" "$BIN" tool info <id> # resolves? (embedded fallback)
PULP_HOME="$H" "$BIN" tool install <id> # installs from here?
PULP_HOME="$H" "$BIN" tool uninstall <id> # removes + names the path?
PULP_HOME="$H" "$BIN" tool uninstall ../x # REFUSED (exit 2)?
rm -rf "$H"
```
Unit-test equivalents live in `experimental/pulp-rs/src/tool_registry.rs`
(`resolve_registry_falls_back_to_embedded_outside_a_checkout`,
`uninstall_tool_rejects_hostile_ids_without_deleting`) and
`experimental/pulp-rs/src/cmd/tool.rs`.
## Docs that promote an installable
When a README/guide tells a user to install something, the exact command in it
must be the one that works for an **installed** user (validated per above). If
two commands do the same thing, show one — don't paste a commented-out alias
(it copies badly). Document how to **remove** it too, and warn that removal
deletes files.
## Related extend surfaces
`packages`, `kits`, `content`, and `installable-tools` are Pulp's four ways to
extend a project or machine, and they share one lifecycle contract: **add is
validated, remove is confirmed + confined to the surface's own area + names what
it deleted, and both add and remove ship tests.** Pick the right surface and read
the shared contract in
[extending-pulp.md](../../../docs/reference/extending-pulp.md).
- [`packages`](../packages/SKILL.md) — third-party audio DSP libraries → a project
- [`kits`](../kits/SKILL.md) — reusable Pulp code/UI/templates → a project
- [`content`](../content/SKILL.md) — data-only packs (presets/samples) → an installed plugin
- [`installable-tools`](../installable-tools/SKILL.md) — machine-level dev/agent tooling under `~/.pulp/tools/`, plus the shared validate-and-uninstall-from-outside-a-checkout bar
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!