@leclaudeforge/claude-plugin-cli
v0.1.0
Published
Command-line tool to install, update, remove and roll back Claude Code add-ons — hooks, skills, agents, plugins — locally or globally.
Maintainers
Readme
Claude Plugin CLI
TypeScript/Node.js CLI for managing Claude Code add-ons locally and globally. Provides lifecycle management (add, list, update, remove, rollback) for four add-on types: hooks, skills, agents, and plugins.
Overview
The claude plugin CLI connects to the ClaudeForge marketplace for plugin discovery and publication. The new claude plugin addon sub-command group provides local/global add-on management — NPM-like workflows for Claude Code's own configuration directories.
Installation & Setup
cd cli && npm install
npm run build
npm run test
# Link for local development (or use via workspace)
npm linkCommands
addon add — Install or scaffold an add-on
Install from a source directory (with addon.json):
claude plugin addon add <source-dir> --scope <local|global> [--force]<source-dir>: Directory containingaddon.jsonand the add-on files.--scope local: Install to./.claude/(project-local).--scope global: Install to~/.claude/(user-global).--force: Overwrite an existing add-on with the same name; the prior version is snapshotted.
Example: Install a skill from a local directory.
claude plugin addon add ./my-skill --scope localScaffold a new add-on (create path):
claude plugin addon add <name> --type <hook|skill|agent|plugin> --scope <local|global> [--lang <ts|py|go|rust>]<name>: Name of the new add-on (used as the identifier; no--typeor--lang→ install mode).--type: Required for scaffold mode. One of:agent,skill,hook,plugin.--lang: Template language. Defaults totypescript. Options:typescript,python,go,rust.--scope local|global: Required; see install mode above.
Constraint: --scope local --type plugin is rejected — plugins are global-only.
Example: Create a new TypeScript skill locally.
claude plugin addon add my-analyzer --type skill --scope local --lang typescriptaddon list — List installed add-ons
claude plugin addon list [--scope <local|global>]--scope local|global: Optional. If omitted, lists both local and global scopes with headers.
Output format per add-on: type name version [stored: v1, v2, ...]
Example: List all installed add-ons.
claude plugin addon listExample: List global add-ons only.
claude plugin addon list --scope globaladdon update — Update an add-on to a new version
claude plugin addon update <source-dir> --scope <local|global><source-dir>: Directory with the newaddon.jsonand files.--scope: Required.
The command compares the new version (from the source addon.json) with the live installed version:
- If same version, it's a no-op:
"Already at version X.Y.Z. Nothing to update." - If newer, the prior version is snapshotted to the version store, and the new version is installed live.
The live add-on is only mutated after the snapshot succeeds, ensuring recoverability.
Example:
claude plugin addon update ./my-skill --scope localaddon remove — Remove an installed add-on
claude plugin addon remove <name> --type <hook|skill|agent|plugin> --scope <local|global><name>: The add-on's name (from itsaddon.json).--type: Required. Must match the installed add-on's type.--scope: Required.
Removes the add-on's files from the live directory and unregisters any hook entries from settings.json.
Example:
claude plugin addon remove my-hook --type hook --scope globaladdon rollback — Restore a prior version
claude plugin addon rollback <name> --type <hook|skill|agent|plugin> --scope <local|global> [--to <version>]<name>,--type,--scope: Required; identify the add-on.--to <version>: Optional. Restore to a specific semver (e.g.,1.0.5). If omitted, restores the most recent prior snapshot.
The add-on is restored from the version store. The current live version is snapshotted first, so rollback is reversible.
Example:
claude plugin addon rollback my-agent --type agent --scope local --to 1.2.0Scopes & Installation Paths
Add-ons are installed to one of two scopes:
| Scope | Base Directory | Note |
|-------|---|---|
| local | ./.claude/ | Project-local; overrides global. |
| global | ~/.claude/ | User-wide default. |
Per-type placement within a scope:
| Type | Live Install Path | Notes |
|------|---|---|
| agent | <scope>/.claude/agents/<name>.md | Single markdown file with YAML frontmatter. No subdirs. |
| skill | <scope>/.claude/skills/<name>/ | Directory tree; should include SKILL.md. |
| hook | <scope>/.claude/hooks/<script> + <scope>/.claude/settings.json | Script file(s) plus a merged hook entry in settings.json (required for activation). |
| plugin | ~/.claude/plugins/<name>/ | Global only. Local scope rejected. Bundle directory. |
Metadata: Add-on metadata (manifest, placed file list, stored versions) is kept in a per-scope sidecar at .addons/<type>/<name>.json to avoid polluting the live directories.
addon.json Manifest Format
Every add-on is self-describing via an addon.json file at its source root.
Example
{
"name": "my-auth-hook",
"version": "1.0.0",
"type": "hook",
"supportedScopes": ["local", "global"],
"files": ["hooks/auth.sh", "README.md"],
"hook": {
"event": "PreToolUse",
"matcher": "Bash",
"command": "hooks/auth.sh",
"type": "command"
}
}Fields
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| name | string | Yes | Non-empty; identifies the add-on. |
| version | string | Yes | Strict semver: MAJOR.MINOR.PATCH[+-prerelease]. |
| type | string | Yes | One of: hook, plugin, skill, agent. |
| supportedScopes | array or "both" | Yes | Array of "local" and/or "global", or shorthand "both" (expands to both). Plugins must be ["global"] or equivalent. |
| files | array | Yes | Relative paths (no absolute, no ..); at least one required. Per-type semantics below. |
| hook | object | Conditional | Required iff type === "hook"; forbidden otherwise. See below. |
Per-Type File Semantics
Agent:
filesmust contain exactly one entry.- Should be a
.mdfile (warning if not). - Example:
["my-agent.md"]
Skill:
filesis a directory tree (multiple entries OK).- Should include
SKILL.mdor<subdir>/SKILL.md(warning if missing). - Example:
["SKILL.md", "utils/helpers.ts", "prompts/default.md"]
Hook:
fileslists script(s):["hooks/auth.sh"]or["hooks/pre.sh", "hooks/post.sh"].- The manifest must include a
hookobject (below) that references one of these files.
Plugin:
filesis the bundle tree.- Should include
.claude-plugin/plugin.json. - Example:
[".claude-plugin/plugin.json", "assets/icon.png"]
Hook Object (required for type: "hook")
{
"event": "PreToolUse",
"matcher": "Bash",
"command": "hooks/auth.sh",
"type": "command"
}| Field | Notes |
|-------|-------|
| event | Claude Code hook event name (e.g., PreToolUse, PostToolUse, SessionStart). |
| matcher | Tool name or regex pattern to match (e.g., Bash, ^Bash.*). |
| command | Relative path to the hook script, as placed in the live directory (e.g., hooks/auth.sh). Must reference one of the files entries. |
| type | Hook entry type; defaults to "command" if absent. |
Version History & Rollback
Every update or add --force snapshots the prior live version to a version store before mutating the live directory. This enables rollback.
Version Store Location
~/.claude-plugins/addon-store/<scope>/<type>/<name>/<version>/
├── addon.json # Manifest as installed at that version
├── files/… # Snapshot of placed files
└── settings-entry.json # For hooks: the exact settings.json entry registeredRetention Policy
By default, the CLI retains the last 5 versions per add-on and automatically prunes older snapshots after each successful snapshot or update.
list Output
local scope:
agent my-agent v2.1.0 [stored: 2.0.5, 2.0.1, 1.9.0]
skill my-skill v1.0.0 (no prior snapshots)
global scope:
plugin gpt-helper v3.0.0 [stored: 2.9.1, 2.8.0, 2.7.0]Flags & Validation
Required Flags
--scope local|global: Mandatory for all mutating commands (add,update,remove,rollback). Optional forlist(defaults to both).
Conditional Flags
--type: Required forremoveandrollback. Optional foradd(triggers scaffold mode if provided without a source manifest).--lang: Optional foraddin scaffold mode; defaults totypescript.--force: Optional foradd(overwrites existing add-on, snapshotting the prior version).--to: Optional forrollback(target version; defaults to most recent prior snapshot).
Validation Rules
- Plugin is global-only.
--scope local --type pluginis rejected before any I/O. - Agent is single-file. Validation rejects agents with zero or more than one file entry.
- Scope isolation.
remove,update, androllbackoperate within the specified scope only. - Manifest safety. Files must be relative paths; absolute paths,
..segments, and null bytes are rejected.
Atomicity & Crash Safety
add & update (Write Path)
- Validate the source
addon.json. - If updating an existing add-on, snapshot the current live version to the version store.
- Stage all placed files to a temp directory (within the same scope root for atomic rename).
- Atomically swap the staged files into the live location.
- For hooks, merge the hook entry into
settings.json(done last, so reverted first on failure). - On any failure, restore from the snapshot or
.bak, revert thesettings.jsonmerge, and report.
remove (Delete Path)
- Read the add-on's sidecar metadata (which files were placed).
- Delete only those files (never blindly delete the owner dir).
- Unregister the hook entry from
settings.jsonif present. - Prune now-empty owner directories.
Crash Recovery
- Version snapshots are written atomically (temp + rename).
- Hook merges are idempotent: re-adding the same hook is a no-op (no duplicates).
- Missing files tolerate idempotence: remove/rollback succeed even if a file was already deleted by the user (e.g., hand-deletion of
agents/my-agent.md).
Testing
# Run all tests
npm test
# Run tests for a specific module
npm test -- addon-manifest
# Watch mode
npm test -- --watchTests use vitest with fake *FsPort implementations to avoid real filesystem I/O.
Architecture
cli/src/addon/manifest.ts— Manifest types, validation, scope normalization.cli/src/addon/scope.ts— Scope resolution (local./.claude/vs. global~/.claude/).cli/src/addon/placement.ts— Per-type placement logic (where files land, sidecar location).cli/src/addon/settings.ts— Hook registration insettings.json(idempotent merge/unmerge).cli/src/addon/store.ts— Version store: snapshot, list, read, rollback (port-based I/O).cli/src/addon/scaffold-source.ts— Scaffold generation (reusesgeneratePluginTemplatefrom plugin-template).cli/src/addon/lifecycle.ts— Core add-on lifecycle engine (add,list,update,remove,rollback).cli/src/commands/addon-*.ts— Command wrappers (parse args, call lifecycle, format output).
All modules are injection-friendly via port interfaces (LifecycleFsPort, SettingsFsPort, VersionStore) for testability.
Examples
Create and install a local hook
# Scaffold a TypeScript hook
claude plugin addon add my-logger --type hook --scope local --lang typescript
# Later, update it
claude plugin addon update ./path/to/my-logger --scope local
# View all local add-ons
claude plugin addon list --scope local
# Rollback to the prior version
claude plugin addon rollback my-logger --type hook --scope localInstall a global skill
# Install from a directory with addon.json
claude plugin addon add ~/skills/my-analyzer --scope global
# Force-overwrite (snapshotting the prior version)
claude plugin addon add ~/skills/my-analyzer --scope global --force
# List versions
claude plugin addon list --scope global
# Restore a specific prior version
claude plugin addon rollback my-analyzer --type skill --scope global --to 1.5.0Notes
- Hooks are inactive until registered. A hook script placed in
hooks/has no effect; the hook entry must exist insettings.json(the CLI handles this automatically onadd). - Project-local overrides global. If both
./.claude/agents/foo.mdand~/.claude/agents/foo.mdexist, the local version takes precedence in Claude Code. - No circular dependencies. Add-ons cannot depend on or require other add-ons; each is independent.
- No remote registry. The
addongroup manages local/global installs only. The separateinstall/publish/searchcommands talk to the ClaudeForge marketplace.
Development
See ../README.md for setup, testing, and build commands.
Adding a New Add-On Type
- Update
AddonTypeincli/src/addon/manifest.ts. - Add per-type validation rules to
validatePerTypeFilesConstraints. - Update
placement.tswith the new type's file mapping. - Add tests for the new type in
addon-manifest.test.tsandcommand-addon-*.test.ts. - Document the new type in this README (section Per-Type File Semantics).
