cat ./plugins.md

Plugin authoring

Plugins are the next layer above skills/agents/commands — one installable unit that bundles several catalogue entries plus optional hooks and MCP servers. skill_pool exposes them as a per-tenant Claude Code marketplace, served from the same Axum router.

What is a plugin?

A plugin is Claude Code's packaging primitive: it bundles one or more skills, agents, commands, hooks, MCP servers, LSP servers, and monitors into one installable unit (official reference). A marketplace is the catalog Claude Code consumes via /plugin marketplace add <url>.

skill_pool already distributes skills, agents, and commands as atomic units. Plugins sit one layer above: a curator picks N already-registered atomic pieces, packages them into one plugin, and exposes the result through their tenant's marketplace.

PrimitiveWhat it isHow it ships
SkillSKILL.md Claude can invoke by name~/.claude/skills/ or inside a plugin
AgentMarkdown declaring a subagent~/.claude/agents/ or inside a plugin
CommandMarkdown Claude treats as a slash command~/.claude/commands/ or inside a plugin
PluginDirectory with .claude-plugin/plugin.json bundling any of the above/plugin install <name>@<marketplace>
A plugin in skill_pool is a composition, not a duplicate. Bundled entries remain addressable, versioned catalogue rows; the plugin row just points at them.

End-to-end install flow

sequenceDiagram autonumber actor Curator actor Dev as Developer participant Portal as skill_pool portal participant API as skill_pool server participant CC as Claude Code Curator->>Portal: compose plugin · pick skills/agents/commands Portal->>API: POST /v1/plugins API-->>Portal: plugin row · marketplace entry generated Dev->>CC: /plugin marketplace add https://acme.skills.example CC->>API: GET /.claude-plugin/marketplace.json API-->>CC: list of plugins (tenant-scoped) Dev->>CC: /plugin install acme-toolkit CC->>API: git clone /git/plugins/acme-toolkit.git API-->>CC: bundle (skills · agents · commands · hooks · MCP) Note over CC: cached at ~/.claude/plugins/cache/acme/acme-toolkit/<ver>/

Three sourcing modes

ModeCurator effortBytes stored by skill_pool?Egress required from dev?
InternalHigh (assemble in portal)YesNo (skill_pool only)
ExternalLow (paste URL)NoYes (upstream URL)
MirrorMedium (paste URL + refresh)YesNo (skill_pool only)

Author your first plugin

# 1) Lay out the plugin directory
$ mkdir -p acme-platform-toolkit/.claude-plugin
$ cd acme-platform-toolkit

# 2) Write the manifest
$ cat > .claude-plugin/plugin.json << 'JSON'
{
  "name": "acme-platform-toolkit",
  "version": "1.0.0",
  "description": "Acme platform team's review + migration + observability skills",
  "author": "platform@acme.com",
  "skills": [
    { "slug": "code-reviewer",      "version": "1.4.0" },
    { "slug": "sqlx-migrations",    "version": "2.0.1" },
    { "slug": "axum-tracing",       "version": "0.3.0" }
  ],
  "agents":   [{ "slug": "platform-on-call",  "version": "0.2.0" }],
  "commands": [{ "slug": "/postmortem-stub", "version": "1.0.0" }],
  "hooks": {
    "SessionStart": [{ "type": "command", "command": "${PLUGIN_DIR}/hooks/load-acme-ctx.sh" }]
  }
}
JSON

# 3) Publish
$ skill-pool publish . --kind plugin --version 1.0.0
  validating manifest...                            OK
  resolving 3 skills · 1 agent · 1 command...       OK  (all in tenant)
  secret scan (PLUGIN_DIR/hooks/)...                OK
  packing...                                        OK (12.4 KB)
  publishing to marketplace...                      OK
  OK acme-platform-toolkit@1.0.0 published
       https://acme.skills.example/.claude-plugin/marketplace.json
       https://acme.skills.example/git/plugins/acme-platform-toolkit.git

# 4) Install in Claude Code
User: /plugin marketplace add https://acme.skills.example
User: /plugin install acme-platform-toolkit
  OK installed at ~/.claude/plugins/cache/acme/acme-platform-toolkit/1.0.0

Manifest schema (key fields)

FieldTypeNotes
namestringSlug; unique per tenant + version
versionsemverImmutable; republish requires a bump
skills[] · agents[] · commands[]array{slug, version} pairs into the existing catalogue
hooksobjectSessionStart, Stop, SessionEnd per Claude Code spec
mcp_serversarrayEmbedded MCP server definitions
monitors · themes · settingsobjectClaude Code passthrough fields

Full schema: docs/plugin-manifest-schema.md.

Mirror & refresh worker

Mirrored plugins carry pull_interval_secs (default 24h). A background worker wakes every 60s, finds plugins whose last_pulled_at + pull_interval_secs is in the past, pulls up to N in parallel, and updates the local git endpoint. Pull failures keep the last-good copy serving and surface a warning chip in the portal.

When SKILL_POOL_REDIS_URL is set, the refresh uses a durable Redis queue with DLQ. Without it, refreshes spawn an in-process tokio task per call (fail-open, no retry across restarts).

Troubleshooting

SymptomLikely causeFix
/plugin marketplace add returns 404Tenant subdomain not resolvedVerify Host header / DNS; curl /.claude-plugin/marketplace.json from the dev machine
/plugin install hangslibgit2 HTTP-transport quirkIssue #73 tracks this; current workaround is to use a system git client
Republish rejected as 409 conflictThat (slug, version) already exists and is not archivedBump the version, or archive the prior row first
Mirror stays on stale refWorker can't reach upstreamCheck /v1/plugins/<slug>/health; portal shows last error

$ api reference → full docs/plugins.md