spm-cli 0.1.1

Skill package manager — declare AI skills in ai.json, materialize them for Claude/Copilot without polluting your repo.
spm-cli-0.1.1 is not a library.

spm — skill package manager

Declare AI skills as git dependencies in ai.json, and spm wires them into your AI tool (Claude Code and GitHub Copilot CLI) without ever committing skills to your repo. Anything spm materializes into the working tree is gitignored — no symlinks, no skills under version control.

How it works

ai.json ──resolve──▶ ai.lock ──fetch──▶ ~/.spm/store/<repo>@<sha>   (global cache, one clone per commit)
                                              │
                                              └─project──▶ materialized where the vendor expects it (see below)
  • ai.json — you author it, commit it. Declares target vendors + skill deps.
  • ai.lock — generated, commit it. Pins every version selector to an immutable commit SHA → reproducible installs.
  • Global store (~/.spm/store) — each repo@commit fetched once, shared across all projects.
  • Vendor projection — spm copies the store's skills into wherever each vendor loads them from. Nothing spm generates is committed to your repo.
  • Registration differs per vendor:
    • Claude — spm assembles a self-contained plugin marketplace in the project-local, gitignored .spm/claude/ dir and writes a pointer to it into .claude/settings.local.json (gitignored by convention). The dir sits outside .agents/skills/ so Copilot's scanner never picks it up. Declarative, per-project, zero VCS footprint.
    • Copilot CLI — spm copies the resolved skills into a project-local directory, .agents/skills/spm-managed-skills/<name>/, where Copilot CLI auto-discovers them (.agents/skills/**/SKILL.md). That directory is added to the project's .gitignore (with an explanatory comment) so the materialized skills stay truly local and are never committed. No user-global state, no copilot CLI required.

On a fresh clone, teammates run spm install — it rebuilds their own store and re-registers from ai.lock. Same model as node_modules.

ai.json

{
  "targets": ["claude", "copilot"],
  "skills": {
    "pdf-tools": { "git": "https://github.com/org/skills", "tag": "v1.2.0", "path": "skills/pdf" },
    "reviewer":  { "git": "https://github.com/me/reviewer", "branch": "main" },
    "pinned":    { "git": "https://github.com/x/y",         "commit": "a1b2c3d" }
  }
}

targets lists one or more vendors (claude, copilot) — skills resolve once and project into each independently.

Schema & validation

ai.json is described by a JSON Schema at schema/ai.schema.json (draft-07). spm embeds it and validates every ai.json on load, reporting all violations at once with their JSON path:

error: in ai.json: ai.json does not match schema:
  at /skills/x: {"git":"u"} is not valid under any of the schemas listed in the 'oneOf' keyword

Add a "$schema" reference for editor autocompletion/validation:

{ "$schema": "./schema/ai.schema.json", "targets": ["claude"], "skills": {} }

Version selectors (exactly one per skill):

field meaning locked to
tag git tag (annotated tags deref to commit) resolved SHA
branch branch tip at install/update time resolved SHA
commit exact commit itself

path (optional) selects a subdirectory — for monorepos holding many skills.

Repo URLs (HTTPS & SSH)

git accepts any URL the system git understands:

spm add https://github.com/org/repo --tag v1.0.0            # HTTPS
spm add git@github.com:org/repo.git --branch main           # SSH (scp-style)
spm add ssh://git@github.com/org/repo.git --branch main     # SSH (url form)

SSH auth goes through your ssh-agent / keys — spm never handles credentials. Private HTTPS repos use your git credential helper. spm runs git with GIT_TERMINAL_PROMPT=0, so a missing credential fails with a clear error instead of hanging on a prompt (helpers and ssh-agent still work).

Installation

spm ships as a single self-contained binary (needs the system git on PATH at runtime).

From npm (recommended) — the zero-setup path on every platform. It puts spm on your PATH with no manual steps:

npm i -g @camunda8/spm
spm --help

@camunda8/spm is a thin launcher that pulls in the matching prebuilt binary for your OS/CPU via an optional dependency (@camunda8/spm-<os>-<cpu>), so nothing is compiled or downloaded outside npm. Supported: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64. Update with npm i -g @camunda8/spm@latest.

From crates.io — build and install from source via Cargo (needs a Rust toolchain). The crate is spm-cli; the installed binary is spm:

cargo install spm-cli

Prebuilt binary — the repo is internal, so release assets require authentication. Download with the GitHub CLI (you must be signed in via gh auth login and be a Camunda org member), then put the binary on your PATH:

# pick the asset for your platform (see list below); example: Apple Silicon macOS
gh release download --repo camunda/spm-cli \
  --pattern 'spm-aarch64-apple-darwin' --output spm
chmod +x spm && sudo mv spm /usr/local/bin/

--repo camunda/spm-cli with no tag grabs the latest release; add v0.1.0 as the first positional arg to pin a specific version.

Assets: spm-x86_64-unknown-linux-gnu, spm-aarch64-unknown-linux-gnu, spm-x86_64-apple-darwin, spm-aarch64-apple-darwin, spm-x86_64-pc-windows-msvc.exe.

From source:

git clone https://github.com/camunda/spm-cli && cd spm-cli
make install                  # release build → /usr/local/bin/spm
make install PREFIX=~/.local  # or a custom prefix
# or: cargo install --path .

Commands

spm init [--target claude|copilot ...]             # scaffold ai.json (repeatable / comma-separated)
spm add <git> (--tag|--branch|--commit <v>) \      # add + install a skill
        [--path <subdir>] [--name <local-name>]
spm remove <name>                                  # drop a skill
spm update [name]                                  # re-resolve branches/tags to latest
spm install                                        # rebuild from ai.lock (after clone)
spm list                                           # show skills + pinned commits
spm clean                                          # remove generated vendor config

Design notes

  • Cross-OS: shells out to the system git (no libgit2 build deps); no symlinks; all paths via std::path. Runs on Linux, macOS, Windows.
  • SPM_HOME overrides the store/vendor root (default ~/.spm) — used by tests.
  • Vendor adapters: adding a target means implementing one Vendor trait (src/vendor/). claude assembles a plugin-marketplace layout (marketplace.jsonplugin.jsonskills/<name>/SKILL.md) into the gitignored project-local .spm/claude/ and points to it; copilot copies skills into the gitignored project-local .agents/skills/spm-managed-skills/. Both keep their materialized files out of VCS via the shared src/gitignore.rs helper.

Development

make check runs the full CI gate locally (fmt-check + clippy + test).

To cut a release, bump the crate version (the single source of truth for crates.io, npm, and the GitHub Release) with make bumpPART=patch|minor|major (default patch) or VERSION=X.Y.Z. Since main is protected, make bump-pr does the bump on a branch and opens the PR for you. See RELEASE.md for the full procedure.

A pre-commit hook (fmt + clippy) installs itself automatically via cargo-husky — just run cargo test (or cargo build) once after cloning and the hook lands in .git/hooks. The hook source lives in .cargo-husky/hooks/. Bypass a single commit with git commit --no-verify.