
Install skills, commands, and MCP servers from marketplaces and Agent Plugins (AP) sources into your coding agents: Claude Code, Codex CLI, OpenCode, and Pi.

**Current version:** 0.4.0, which is what this page documents. equipr is pre-1.0, so the surface is still settling.

---

## What Is This?

A single Go binary that:

- **Fetches** a source once, whether that is a git repository, an npm package, an archive URL, or a local path
- **Resolves** what is inside it: the plugins, and the skills, commands, and MCP servers each plugin holds
- **Places** every component where its target agent expects to find it, by copy or by symlink
- **Records** what it did, so `update`, `status`, `doctor`, and `uninstall` work from facts instead of guesses

The more interesting part is what equipr refuses to do.

It never registers itself as a native plugin in any agent. No `/plugin`, no global npm install, nothing that turns up in an agent's own plugin list; it writes to personal and global config surfaces and stops there. It also never touches a config key it does not own. Every write into a shared agent config is a targeted single-key merge, which is why your hand-edits, your comments, and your key ordering all survive it.

### Why It Exists

Skills and MCP servers are distributed as git repositories, npm packages, and archives, but every coding agent expects them in a different place, in a different shape. Claude Code reads `~/.claude/skills/`. OpenCode reads `~/.config/opencode/skills/` and uses a different MCP config schema. The manual answer is copying directories around and hand-editing shared JSON and TOML config files that also hold your credentials.

Doing that by hand is fine once. It stops being fine at four agents, a dozen skills, and a config file you have already customized.

---

## Quick Start

### 1. Install

```bash
brew install maquina-app/tap/equipr
```

### 2. Add a source

```console
$ equipr add https://github.com/coreyhaines31/marketingskills
Fetching https://github.com/coreyhaines31/marketingskills
Added marketingskills (marketplace, fetched via git) with 1 plugin(s)
  - marketing-skills 2.10.0
```

### 3. See what it holds

```console
$ equipr list
marketingskills     marketplace
  marketing-skills  2.10.0  49 skills
```

### 4. Install a component into your agents

```console
$ equipr install marketingskills/marketing-skills:seo-audit --yes
Installed 1 component(s) into 3 agent(s):
  [claude-code] seo-audit (copy -> ~/.claude/skills/seo-audit)
  [opencode] seo-audit (symlink -> ~/.config/opencode/skills/seo-audit)
  [pi] seo-audit (symlink -> ~/.pi/agent/skills/seo-audit)
```

Three agents rather than four, because Codex was not installed on the machine this run was captured on.

Without `--yes`, `install` is interactive: pick agents, then pick components. Components all start checked, and so do the agents whose binary is on `$PATH`, so pressing enter through both installs everything into every agent you actually have.

### 5. Check your installs

```console
$ equipr status
STATE  COMPONENT     SOURCE/PLUGIN                     AGENT        MECH     TARGET
ok     seo-audit(s)  marketingskills/marketing-skills  claude-code  copy     ~/.claude/skills/seo-audit
ok     seo-audit(s)  marketingskills/marketing-skills  opencode     symlink  ~/.config/opencode/skills/seo-audit
ok     seo-audit(s)  marketingskills/marketing-skills  pi           symlink  ~/.pi/agent/skills/seo-audit

$ equipr doctor
No issues found.
```

---

## Installation

Every release publishes binaries for **darwin, linux, and windows on amd64 and arm64**. macOS binaries are ad-hoc codesigned, because an unsigned Mach-O binary is killed on Apple Silicon.

```bash
# Homebrew (macOS and Linux)
brew install maquina-app/tap/equipr

# Install script: /usr/local/bin when that is writable, otherwise ~/.local/bin
curl -fsSL https://github.com/maquina-app/equipr/releases/latest/download/install.sh | sh

# From source (Go 1.24+), into any directory on your PATH
go build -o ~/.local/bin/equipr ./cmd/equipr
```

`go install ./cmd/equipr` works too, but it puts the binary in `$(go env GOPATH)/bin`, which is not on everyone's `PATH`.

Debian and RPM packages and raw archives are on the [releases page](https://github.com/maquina-app/equipr/releases).

Verify the install:

```console
$ equipr --version
equipr version 0.3.3
```

`equipr doctor` reports on the health of your installs. It says nothing about the binary itself, and with nothing installed yet it prints `No issues found.` and exits 0.

---

## Concepts

| Term | What it means |
|------|---------------|
| **origin** | Where content comes from, as you type it into `add`. Auto-detected as git, npm, archive, or local path. |
| **source** | One added origin, fetched into the cache and recorded in the registry. Either a marketplace or a single AP package. |
| **source-id** | The short handle a source is addressed by, derived from the origin, usually the repository or directory name. |
| **plugin** | One installable unit inside a source. An AP source has exactly one; a marketplace can have many. |
| **component** | One installable thing inside a plugin: a skill, a command, or an MCP server. |
| **agent** | An install target: `claude-code`, `codex`, `opencode`, or `pi`. |
| **mechanism** | How a component is placed: `copy` or `symlink`. |

### Addressing Grammar

Everything nests, and every command addresses one of these three levels:

```
<source-id>                       marketingskills
<source-id>/<plugin>              marketingskills/marketing-skills
<source-id>/<plugin>:<component>  marketingskills/marketing-skills:seo-audit
```

### Source Types

| Type | Detected by | Contains |
|------|-------------|----------|
| Marketplace | `.claude-plugin/marketplace.json` at the root | Many plugins, in subdirectories |
| Agent Plugins package | `plugin.json` at the root | Exactly one plugin |

### Origin Kinds

There are three kinds: `git`, `npm`, and `archive`. They are resolved in a fixed order, and the order does more work than the individual rules:

1. An explicit `npm:` prefix wins immediately: **npm**
2. Anything ending in `.zip`, `.tar.gz`, or `.tgz`: **archive**. The test is on the string suffix alone, so a local `./pkg.zip` classifies here too. It is checked before git, which is why `https://host/pkg.zip` resolves as an archive
3. Contains `://`, starts with `git@`, or ends in `.git`: **git**
4. Exists on disk: **npm** if it is a directory holding `package.json` and no `.git`, otherwise **git**. Any existing path qualifies, including a plain file
5. Nothing above matched and nothing exists at that path: **npm**. This is the fallback, and it is how a bare package name like `express` resolves

A local path is not a fourth kind. Steps 2 and 4 sort it into one of the three, and step 4 holds a trap: if a directory named `express` happens to exist in your working directory, `equipr add express` takes it as that directory rather than the npm package. Write `npm:express` to force the package.

An origin can also name a subdirectory, either as a fragment or as a forge tree URL:

```bash
equipr add https://github.com/owner/repo#plugins/foo
equipr add https://github.com/owner/repo/tree/main/plugins/foo
```

The `/tree/<ref>/<path>` form is http(s) only, and equipr clones the default branch shallowly. A tree URL naming some other branch is reported back to you with the `#` form as the way forward, rather than being quietly resolved against the default branch.

### Component Types

| Type | Where it lives in a plugin |
|------|----------------------------|
| skill | A directory under `skills/` holding a `SKILL.md` |
| command | A `.md` file under `commands/` |
| MCP server | An entry in the plugin's `mcp.json` |

Commands are normalized to `<name>/SKILL.md` for every agent, so a plugin's commands and skills land in the same place and are discovered the same way.

---

## Commands

| Command | Purpose |
|---------|---------|
| `equipr add <origin>` | Fetch an origin, resolve its contents, register it |
| `equipr list [source-id \| source-id/plugin]` | List the registry: sources, their plugins, and what each holds |
| `equipr show <source-id>[/plugin]` | Details for a source or one plugin |
| `equipr install <source>/<plugin>[:<component>]` | Place a plugin's components into agents |
| `equipr update` | Re-fetch every source that has installs and re-apply each recorded component |
| `equipr remove <source-id>` | Drop a source from the registry |
| `equipr uninstall <source>/<plugin>[:<component>]` | Remove installed files and records |
| `equipr doctor` | Read-only health check |
| `equipr status` | Read-only report of every recorded install |

### add

Re-running `add` on a source that already exists refreshes it. Git sources pull; npm and archive sources are re-fetched.

```bash
equipr add https://github.com/coreyhaines31/marketingskills
```

### list

`-c` / `--components` expands each plugin to its component names. Passing `<source>/<plugin>` prints what `show` prints.

```bash
equipr list marketingskills --components
```

### install

Interactive by default: pick agents, then pick components. Components start checked; agents start checked when their binary is on `$PATH` (see [Detection](#detection)). Use `-a` to pin targets and `--yes` to skip the prompts.

```bash
equipr install marketingskills/marketing-skills:seo-audit --yes -a claude-code
```

### update

Takes no arguments. It operates on every source that has installs.

```bash
equipr update --dry-run
```

`--dry-run` still re-fetches, so it can report "would update v1 to v2", but it writes nothing and never prompts.

### remove and uninstall

The two are deliberately separate:

- `remove` deregisters the source but leaves the installed files and their records in place, so `uninstall` can still clean them up. `doctor` reports those records as `orphaned-record`.
- `remove --purge` does it all at once: drops the records, deletes the installed files, and removes the cache clone.
- `uninstall` removes files and records for a plugin or a single component, and works on orphaned records after a `remove`.

```bash
equipr uninstall marketingskills/marketing-skills:seo-audit
equipr remove marketingskills --purge
```

### doctor

Read-only. Reports four conditions: `orphaned-record`, `vanished-config-dir`, `broken-symlink`, and `modified-cache`. **It exits 5 when it finds issues** and 0 when clean, so scripts checking `$?` should expect that.

### status

A report of every recorded install: state (`ok`, `modified`, or `missing`), component, source and plugin, agent, mechanism, and target path. Writes nothing.

---

## Flags

### Persistent

One flag is registered on the root command and applies everywhere.

| Flag | Effect |
|------|--------|
| `-y`, `--yes` | Assume yes to all prompts: install into every present agent, install all components, resolve conflicts as overwrite |

`--json` is **not** persistent, despite looking like it should be. It is registered on each of the nine commands that produce results, and `completion` and `help` never register it. So `equipr --json` on its own is an error, and so is `equipr completion zsh --json`.

### Per Command

| Command | Flag | Values | Default |
|---------|------|--------|---------|
| `install` | `-a`, `--agent` (repeatable) | `claude-code`, `codex`, `opencode`, `pi` | Prompt |
| `install` | `-c`, `--component` | A component name, the same thing as the `:name` suffix | Prompt (all with `--yes`) |
| `install` | `--mechanism` | `copy`, `symlink` | The per-agent default |
| `install` | `--on-conflict` | `prompt`, `overwrite`, `skip`, `fail` | `prompt` |
| `update` | `--on-conflict` | `prompt`, `overwrite`, `skip`, `fail` | `prompt` |
| `update` | `--dry-run` | Report only, write nothing | Off |
| `remove` | `--purge` | Also delete records, files, and cache | Off |
| `list` | `-c`, `--components` | Expand plugins to component names | Off |

### Choosing a Mechanism

| Mechanism | Pick it when |
|-----------|--------------|
| `symlink` | You want the agent to track the cache, so `update` is instant and the source stays the single copy on disk |
| `copy` | You want the installed version pinned and independent of the cache, or the agent does not reliably follow links |

The per-agent defaults exist for a reason: whole-directory symlinks proved unreliable for Claude Code and Codex, while OpenCode and Pi dereference links at any depth. Symlinks are always created per component, never for a whole directory.

### Choosing a Conflict Policy

`--on-conflict` applies when a target has been locally modified since it was installed.

| Value | Behavior | Use it for |
|-------|----------|-----------|
| `prompt` | Ask per file | Interactive use; the default |
| `overwrite` | Discard local edits | When the source is the truth |
| `skip` | Keep local edits and move on | When you have deliberately customized a skill |
| `fail` | Abort the whole run on the first modification | Scripts and CI, where a surprise should stop the pipeline |

### Exit Codes

Stable, and safe to script against.

| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | Generic or usage error |
| `2` | Not found |
| `3` | Conflict |
| `4` | Environment error |
| `5` | `doctor` found issues |

---

## Agents

| Agent | Detected by | Components land in | MCP config |
|-------|-------------|--------------------|------------|
| `claude-code` | `~/.claude/` or `claude` on `$PATH` | `~/.claude/skills/<name>/` | `~/.claude.json` |
| `codex` | `~/.codex/` or `codex` on `$PATH` | `~/.codex/skills/<name>/` | `~/.codex/config.toml` |
| `opencode` | `~/.config/opencode/` or `opencode` on `$PATH` | `~/.config/opencode/skills/<name>/` | `~/.config/opencode/opencode.json` |
| `pi` | `~/.pi/agent/` or `pi` on `$PATH` | `~/.pi/agent/skills/<name>/` | `~/.config/mcp/mcp.json` |

Default mechanism: `copy` for `claude-code` and `codex`, `symlink` for `opencode` and `pi`.

### Detection

An agent is offered when **either** signal holds: a config directory, or a binary on `$PATH`. Both are needed, because a freshly installed agent has no config directory until its first run, and a config directory outlives an uninstalled agent. The picker labels each agent with the evidence it found (`configured`, `on PATH`, or both), so a surprising entry explains itself.

Being offered and being selected are different things. Since 0.4.0 the picker pre-checks only the agents whose binary is on `$PATH`. A config-only agent is listed but starts unchecked, because a leftover config directory outlives an uninstalled agent and enter should not push skills into a directory nothing reads. The exception: when nothing at all is on `$PATH`, every row starts checked, so that enter is never a silent no-op. `--yes` is unaffected and still installs into every agent present, config-only ones included.

### MCP Server Writes

MCP servers are never written as a whole file. equipr merges a single key into the agent's existing config, atomically. It writes to a temporary file, then renames it, preserving the file mode. JSON goes through sjson and gjson, TOML through a dedicated writer, specifically so key order, formatting, and comments in a file you hand-edited survive the write.

Agent schemas differ, and equipr writes each in its native shape. OpenCode uses an `mcp` key with an explicit `type` and `command` as an **array**, where Claude Code uses `mcpServers` with string inference.

---

## Files and State

| Path | Holds |
|------|-------|
| `$XDG_CONFIG_HOME/equipr/config.toml` | Reserved; nothing reads it yet |
| `$XDG_STATE_HOME/equipr/sources.json` | The registry: every added source and what is in it |
| `$XDG_STATE_HOME/equipr/installs.json` | Install records: component, agent, target path, content hash |
| `$XDG_STATE_HOME/equipr/equipr.lock` | Lock file, held across mutating operations |
| `$XDG_CACHE_HOME/equipr/sources/<id>/` | The fetched tree, per source |

On macOS those XDG defaults resolve under `~/Library/Application Support/equipr` and `~/Library/Caches/equipr`. On Linux, `~/.local/state/equipr` and `~/.cache/equipr`.

### Hand-Editing

**Use the commands, not an editor.** `sources.json` and `installs.json` are rewritten wholesale under the lock, and `installs.json` records a content hash per target that the conflict check compares against, so editing it by hand makes `status` and `doctor` report the wrong thing.

Deleting the cache directory is the one safe destructive act: `add` or `update` re-fetches it.

---

## JSON Output

Nine commands accept `--json`: `add`, `list`, `show`, `install`, `update`, `remove`, `uninstall`, `doctor`, and `status`. Results go to stdout and progress to stderr, so stdout stays parseable.

**Script against `--json`, not the plain-text output.** The text layout is still moving: `list` was restructured in 0.3.2 and `status` in 0.3.3, both on the same day.

`equipr list --json`, trimmed. The real `skills` array holds 49 entries:

```json
{
  "sources": [
    {
      "id": "marketingskills",
      "type": "marketplace",
      "kind": "git",
      "origin": "https://github.com/coreyhaines31/marketingskills",
      "plugin_count": 1,
      "plugins": [
        {
          "name": "marketing-skills",
          "version": "2.10.0",
          "description": "Marketing skills for AI agents — conversion optimization, copywriting, SEO, paid ads, ad creative, and growth",
          "author": "Corey Haines",
          "skills": ["ab-testing", "ad-creative", "ads", "ai-seo", "..."]
        }
      ]
    }
  ]
}
```

`equipr status --json`, trimmed to one of three entries:

```json
{
  "entries": [
    {
      "source_id": "marketingskills",
      "plugin": "marketing-skills",
      "component": "seo-audit",
      "component_type": "skill",
      "agent": "claude-code",
      "mechanism": "copy",
      "target": "~/.claude/skills/seo-audit",
      "version": "2.10.0",
      "state": "ok"
    }
  ]
}
```

---

## Shell Completion

Cobra-generated completion is available for four shells: bash, zsh, fish, and powershell.

```bash
equipr completion zsh > "${fpath[1]}/_equipr"     # then restart your shell
equipr completion bash > /etc/bash_completion.d/equipr
equipr completion fish > ~/.config/fish/completions/equipr.fish
```

`equipr completion <shell> --help` prints the install instructions for that shell.

Completion covers commands and flags. Source, plugin, and component names are resolved at runtime, so they are not completed.

---

## Recent Changes

| Version | Change |
|---------|--------|
| 0.4.0 | Local paths that are neither a git repository nor an npm package are rejected up front with a message naming the problem, instead of failing later as a git error. The install picker pre-checks only agents found on `$PATH` |
| 0.3.4 | MIT license added. No change to commands, flags, output, or JSON |
| 0.3.3 | `status` columns sized from the rows |
| 0.3.2 | `list` nests plugins under sources with component counts, gained `--components`, and accepts `<source>/<plugin>` |
| 0.3.1 | Install prompts pre-check every option; agents detected by config directory **or** binary on `$PATH`; `skills/` and `commands/` walked recursively; marketplace entries rooted at `"./"` resolve |

Before 0.3.1, install prompts started with nothing selected, so pressing enter installed nothing.

---

## Troubleshooting

### Nothing was installed

The picker toggles with **space** and submits with **enter**. Components start checked, so enter installs all of them. Deselect everything and press enter, though, and nothing installs; equipr says as much. Two other ways to end up with nothing: on 0.3.0 and earlier every row started unselected and the error wrongly claimed the plugin had no components, so upgrade or pass `--yes`; and since 0.4.0 a config-only agent starts unchecked, so an install can succeed while skipping an agent you expected it to reach.

### An agent you do not use is offered

A leftover config directory counts as configured, even with no binary installed. Check the label in the picker: `configured` on its own means no binary was found on `$PATH`, and since 0.4.0 that row starts unchecked. Delete the stale directory, or pin your targets with `-a`. Note that `--yes` ignores the distinction and installs into every agent present.

### A plugin shows no components

Most often it ships only an `agents/` directory, holding Claude Code subagents, which equipr does not model yet. equipr handles skills, commands, and MCP servers. That is a known gap in equipr; the source itself is fine.

### Adding a local path is rejected

Origin classification resolves any existing path it does not recognize to git, so a directory that is not a git repository, a plain file, and a local archive are each rejected before anything is fetched, with a message naming the actual problem:

```console
$ equipr add ./plaindir
equipr: ./plaindir is neither a git repository nor an npm package directory; a local
source must be one or the other (run `git init` there, or point equipr at a remote origin)

$ equipr add ./notes.txt
equipr: ./notes.txt is a file, not a source; add the directory that holds plugin.json
or .claude-plugin/marketplace.json

$ equipr add ./pkg.zip
equipr: ./pkg.zip is a local archive; equipr fetches archives over http(s) only, so
extract it and add the directory instead
```

Exit code 1 in all three cases. Before 0.4.0 the first of these surfaced as a raw `git clone: repository does not exist`, which read like a bug in equipr and was not one.

### A marketplace reports fewer plugins than it has

An entry whose `source` is a remote reference equipr cannot resolve in-tree (a `github`, `npm`, or `archive` object form) is skipped and named in the output, rather than failing the whole marketplace. The fix belongs upstream in the marketplace.

### doctor reports modified-cache

The cached tree no longer matches the digest recorded when it was added, which usually means something outside equipr touched the cache. Run `equipr add <origin>` to re-fetch and clear it.

### doctor exits 5

Exit code 5 is documented behavior: doctor found something. Nothing crashed.

### Records survive remove

By design. `remove` deregisters the source but leaves files and records so `uninstall` can still clean them, and `doctor` reports them as `orphaned-record`. Use `remove --purge` for the one-shot version.

### Two components with the same name fail the whole source

Components are named by their own directory or file, which is not unique across a nested tree, and skills and commands share one flat install namespace. Rather than silently overwriting one with the other, `add` fails and names both paths. The fix belongs upstream in the source.

---

## License

MIT. Copyright (c) 2026 Mario Alberto Chávez.

---

## Next Steps

<div class="not-prose mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
  <a href="https://github.com/maquina-app/equipr" target="_blank" rel="noopener" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      GitHub Repository
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      Source code, releases, issues, and contribution guidelines.
    </p>
  </a>

  <a href="/blog/2026/08/equipr-cross-agent-skill-manager/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Announcement
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      Why equipr exists, and what it refuses to do.
    </p>
  </a>

  <a href="/documentation/ai-tools/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      AI Tools
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      MCP servers and Claude Code plugins, the kind of thing equipr installs.
    </p>
  </a>

  <a href="https://agent-plugins.org/specification" target="_blank" rel="noopener" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Agent Plugins Specification
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      The plugin format equipr resolves from a source.
    </p>
  </a>

  <a href="https://agentskills.io/specification" target="_blank" rel="noopener" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Agent Skills Specification
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      The skill format behind every SKILL.md equipr installs.
    </p>
  </a>
</div>
