agentgear
Ship a coding-agent plugin straight from your Rust binary: Claude Code plus 24 other coding agents. One setup command installs into every agent it detects; a SessionStart hook self-heals the install after a version bump.
Rust library and derive macro for shipping a coding-agent plugin from a binary. For Claude Code and GitHub Copilot CLI it orchestrates each tool's own plugin-management CLI and never forges its on-disk registry state. For the other 23 agents it read-modify-writes each tool's own config file, touching only the entries it wrote.

Five Rust tools that each ship a Claude Code plugin were hand-rolling the same lifecycle: layout, version stamping, marketplace registration, self-heal, uninstall. Each drifted. This crate solves the hard parts once (atomic on-disk state, partial-failure recovery, schema-bump survival, concurrency) behind a derive macro.
$ mytool setup
Installed
$ mytool doctor
[ ok ] host binary on PATH: `mytool` resolves on PATH
[ ok ] claude version: 2.1.201 (Claude Code)
[ ok ] plugin registered: mytool@mytool v0.4.0 (enabled)
[ ok ] marketplace registered: `mytool` registered
[ ok ] manifest validates: ~/.local/share/mytool/current --strict clean
[ ok ] current tree matches embedded: hashes match
[ ok ] hook commands on PATH: all referenced bare commands resolve
Why
- One binary, 25 agents.
agents = [...] in the derive picks the targets. Claude Code and GitHub Copilot CLI get the full plugin lifecycle; the other 23 get config-merge: mcp servers, hooks, commands, agent defs translated into each tool's own config file. Only installed tools are touched.
- One dependency, one derive. Add the crate, write a
#[derive(PluginHost)] struct and a one-line build.rs. The binary then gets install / update / uninstall / self_heal / doctor.
- Know which agent did what. Each lifecycle call has a
_report twin (install_report, update_report, uninstall_report, self_heal_report) returning an AgentReport: one AgentResult per configured agent, each Converged (with its own Outcome), Skipped (with a SkipReason), or Failed (with the detail). A host reads it to tell the user which of its 25 agents actually installed instead of guessing from one collapsed result. The plain install / update / uninstall / self_heal calls still return a single merged Outcome for a host that only wants pass/fail.
- The CLI is the source of truth. Every mutation goes through
claude plugin …, so a Claude Code registry schema bump never breaks the crate. It reads state back through list --json for drift checks.
- Self-heal that respects the user. A SessionStart hook repairs a broken install without overriding a deliberate choice: it never resurrects an uninstall, re-enables a disable, or downgrades a newer install.
- Tells the model when to reload. After an out-of-band
setup update, a UserPromptSubmit hook surfaces a restart-pending flag so the model tells the user to run /reload-plugins; the next self_heal clears it once the new version is loaded.
- Atomic materialize. The plugin tree ships as a compressed blob (a pure-Rust brotli archive, roughly a quarter of the raw text size). It decompresses into a content-keyed versioned directory with an atomic pointer flip, so a crash mid-install leaves the previous state intact.
- Compile-time version guard. A
build.rs helper fails the build when plugin.json and CARGO_PKG_VERSION disagree, because Claude Code caches on the plugin version and a no-bump change is a silent no-op.
How it works
install acquires a shared lock, then converges the claude plugin registry to the embedded plugin version:
| step | action |
|---|
| materialize | write the baked tree to ~/.local/share/<name>/versions/<ver>/, generate marketplace.json, flip the atomic current pointer |
| register | claude plugin marketplace add <current> (Claude Code copies it into its cache) |
| install | claude plugin install <name>@<marketplace>, verified via list --json |
| stamp | record a marker so self_heal can tell an install it owns from one it should leave alone |
self_heal runs from the plugin's own SessionStart hook and reduces to the same reconcile, driven by the stamp marker and list --json state. Full state table: How it works.