Skip to main content

equipr: skills and MCP servers for coding agents

CLI that installs skills, commands, and MCP servers from marketplaces and Agent Plugins sources into Claude Code, Codex CLI, OpenCode, and Pi.

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

brew install maquina-app/tap/equipr

2. Add a source

$ 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

$ equipr list
marketingskills     marketplace
  marketing-skills  2.10.0  49 skills

4. Install a component into your agents

$ 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

$ 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.

# 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.

Verify the install:

$ 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:

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.

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

list

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

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). Use -a to pin targets and --yes to skip the prompts.

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

update

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

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.
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:

{
  "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:

{
  "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.

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:

$ 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