Skip to main content

Crate agentgear

Crate agentgear 

Source
Expand description

A canonical install / uninstall / self-heal lifecycle for the Claude Code plugin a Rust binary ships, so a setup subcommand replaces the user typing /plugin marketplace add + /plugin install. The same plugin tree can fan out to 24 other coding agents (codex, gemini, opencode, cursor, goose, …), each behind a cargo feature.

For Claude Code the lifecycle orchestrates the claude CLI (≥ 2.1.196) as its transaction boundary; it never forges Claude Code’s on-disk registry state. Non-Claude backends need no CLI at all: each translates the plugin’s components (MCP servers, hooks, commands, agents) into that tool’s own config files via atomic read-modify-write merges that leave the user’s entries untouched, and only runs when the tool is detected on the machine.

The derive-driven example stays ignore: the macro reads a plugin.json tree and the emitted guard needs AGENTGEAR_GUARD, neither of which a doctest has. A compiled example of the derive-free value types follows below.

use agentgear::{PluginHost, Scope, Source};

#[derive(PluginHost)]
#[plugin(name = "claudix", agents = ["claude", "codex", "gemini"])]
struct ClaudixHost;

ClaudixHost::install(Scope::User, Source::Embedded)?; // all detected agents
ClaudixHost::install_into(Scope::User, Source::Embedded, &["gemini"])?; // one
ClaudixHost::self_heal()?; // SessionStart entrypoint

The value types need no derive, so this block is a real, compiled doctest. It builds a Source/Scope, then reads an Outcome and an AgentReport’s per-agent results.

use agentgear::{AgentReport, AgentResult, AgentStatus, Outcome, Scope, Source};

let _source = Source::Path("./plugin".into());
let _scope = Scope::Project { path: ".".into() };

let outcome = Outcome::Updated { from: Some("0.1.0".into()), to: "0.2.0".into() };
let line = match outcome {
    Outcome::Installed => "installed".to_string(),
    Outcome::Updated { from, to } => format!("updated {from:?} -> {to}"),
    other => other.to_string(),
};
assert_eq!(line, "updated Some(\"0.1.0\") -> 0.2.0");

let result = AgentResult { agent: "claude", status: AgentStatus::Converged(Outcome::Installed) };
assert!(matches!(result.status, AgentStatus::Converged(_)));

// AgentReport is #[non_exhaustive]; only a lifecycle call builds one, so this
// reader is compile-checked against the live signatures without an instance.
fn summarize(report: &AgentReport) -> bool {
    for entry in &report.results {
        let _ = (entry.agent, &entry.status);
    }
    let _merged: Outcome = report.merged();
    report.is_healthy()
}
let _ = summarize as fn(&AgentReport) -> bool;

The host also authors a one-line build.rs: fn main() { agentgear::build::assert_plugin_version(); }.

Five runnable hosts live in the repo’s examples/. hello-mcp is the minimal Claude-only host; kitchen-sink exercises every component type across seven harnesses; multi-installer builds an agent picker through backend_for; hooks-everywhere fans one hook event across many harnesses; from-github is a zero-embed host tracking a GitHub source.

§Feature flags

default = ["derive", "claude", "embed"]: the PluginHost derive macro, the Claude Code backend, and baking the plugin tree into the binary as a compressed blob so setup works offline. Turning embed off (paired with embed = false on the derive) ships a zero-embed binary for a host that installs from a GitHub or path Source instead.

Every other coding agent is its own feature, named by its backend id; all-agents enables all 25 at once. Only four pull an extra dependency:

feature (= backend id)extra dependency
codex, kimitoml_edit (toml config)
omp, gooseserde_norway (yaml config)
opencode, gemini, cursor, cline, devin, qwen-code, copilot-cli, vscode-copilot, jetbrains-copilot, kiro, zed, openclaw, kilo, antigravity, antigravity-cli, pi, amp, crush, droid, augmentnone

Backends are selected per host binary with the derive’s agents = [...] list; backend_for resolves an enabled id to its AgentBackend, and AGENT_IDS is the roster of ids compiled into the current build, so a host can drive its own picker UI without hardcoding the list. The full set of 25 ids: plugin-native claude and copilot-cli (full lifecycle through the tool’s own CLI), plus the 23 config-merge backends codex, opencode, gemini, cursor, cline, devin, qwen-code, vscode-copilot, jetbrains-copilot, kimi, kiro, zed, omp, openclaw, kilo, antigravity, antigravity-cli, pi, goose, amp, crush, droid, augment. Design rationale and the full per-backend reference live in the project wiki.

Re-exports§

pub use statusline::StatusLineDecl;

Modules§

build
Host-facing build helper. The host authors a one-line build.rs:
prelude
A ready-to-glob prelude for host binaries.
statusline
The host-owned status-line surface: the StatusLineDecl a host declares through #[plugin(statusline_fn = ...)], plus the runtime helpers its own status-line subcommand calls.

Structs§

AgentReport
Per-agent results of one lifecycle fan-out, one entry per configured agent in plugin.agents order (agents excluded by an explicit install_into filter get no entry — they were never asked for). The merged-Outcome lifecycle methods collapse this to first-change-wins; a host that wants to tell its user which agents were installed, skipped, or failed reads the *_report variants and prints this (its Display is a ready setup summary, one line per agent).
AgentResult
One agent’s slice of a lifecycle fan-out.
Capabilities
What an agent backend can host. Each surface flag is true iff the backend’s reconcile actually writes/manages that surface for a plugin declaring it (a conditionally-gated surface — e.g. one a native registry may already cover — still counts, since the backend can translate it). Lets setup report “codex: mcp only” instead of silently dropping features, and is the runtime truth behind the README’s supported-agents matrix.
Desired
What a reconcile should converge to. Held separately from Scope because a backend converges the same desired state across scopes.
DoctorCheck
One inspected thing and how it fared.
DoctorReport
An ordered health report: one DoctorCheck per thing inspected, rendered by its Display into a [ ok ]/[warn]/[fail] list.
HookBinding
One hook binding: a command wired to an event, optionally matcher-gated.
MarkdownDoc
A markdown component (a command or agent) with its frontmatter split out.
McpServer
One MCP server the plugin declares.
Plugin
A resolved plugin descriptor. Built by PluginHost::descriptor from the derive-emitted metadata; passed to backends.
PluginComponents
A plugin tree parsed into a harness-agnostic shape. Lossless: a backend that lacks a surface skips that field, the parser never drops one.
SkillDir
A skill directory and its files, ready to copy into a harness’s skills dir.

Enums§

AgentStatus
What one agent’s slice of the fan-out did.
BackendState
What probe classifies a plugin’s per-agent state as. Drives self_heal’s marker × state table (never resurrect, never re-enable, repair drift).
CheckStatus
A single check’s verdict. Only CheckStatus::Fail makes a report unhealthy.
Error
Everything a lifecycle call can fail with. Environment problems the user can fix (missing or old claude/copilot) are distinct variants from genuine bugs (io/json), so a caller renders a fix-hint rather than a stack trace.
McpKind
An MCP server’s transport.
Outcome
What a lifecycle op actually did. non_exhaustive: adding a case is additive.
Scope
Where a plugin is installed. Local is intentionally absent (design §API): binary-driven install of user-wide tooling has no coherent local-scope story. non_exhaustive so a future variant lands without a semver major.
SkipReason
Why an agent was skipped rather than converged.
Source
Runtime origin of the plugin tree, defaulted from the derive attrs but overridable per call. One binary can ship an embedded tree yet still let users track a GitHub ref so claude plugin update pulls new plugin versions without waiting on a binary release.

Constants§

AGENTGEAR_CLIENT_TOKEN
The portability token every backend expands to ITS OWN canonical client id (claude, codex, copilot-cli, …). Unlike ${CLAUDE_PLUGIN_ROOT} — which only Claude Code expands, so is_portable flags it as a skip — this token is known to agentgear and expanded per harness in an executable command, so an author writes the client id once and each harness sees its own. Plugin-native backends (CC/copilot) copy the whole tree and substitute it everywhere (see materialize); the config-translating backends expand only the command surfaces they render — hook commands and mcp command/args (with_client).
AGENT_IDS
Every agent id compiled into this build, in the order backend_for resolves them: one entry per enabled backend feature. A host enumerates these to know which backends it can target without hardcoding the roster — every id here resolves to Some through backend_for, pinned by a test. Feature-gated, so a default build holds only ["claude"] and all-agents holds all 25.

Traits§

AgentBackend
One coding agent’s translation of the plugin. reconcile is the single shape install/update/self_heal reduce to; each backend decides what “converged” means for its harness. The trait is unsealed, so an out-of-crate crate can add a backend and drive it via Plugin::components plus a direct reconcile/remove (the derive’s agents list only names built-in ids).
PluginHost
Implemented by the #[derive(PluginHost)] macro. The consts carry the compile-time metadata; the provided methods are the lifecycle the host calls.

Functions§

backend_for
Resolve a backend by id. Each non-CC arm is feature-gated so a default build ships only Claude; all-agents (fixture + docker legs) lights every arm. Public so a host can enumerate its AGENTS (detect/capabilities) to build its own setup UI.
current_pointer
The current@<client> pointer path [crate::materialize] publishes for plugin_name, resolved like [data_root] but without materializing anything. A host reads its registered marketplace source and compares it against this to spot a divergent registration with a plain filesystem read, never a CLI spawn.

Type Aliases§

Result
The crate’s result alias over Error.

Derive Macros§

PluginHostderive
Derive PluginHost for a host unit struct, wiring the plugin’s install/update/self-heal lifecycle from one #[plugin(..)] attribute.