🎒 loadout
The package manager for AI coding agent artifacts.
Stop manually copying skills, rules, agents, and commands into every coding agent's config directory. loadout handles discovery, transformation, and installation across Claude Code, Cursor, and OpenCode — so your CLI or project setup script doesn't have to.
📦 Installation
# uv (recommended)
uv add loadout
# pip
pip install loadout
# With interactive agent selection prompt
uv add "loadout[interactive]"
pip install "loadout[interactive]"
🤔 Why loadout?
Every team building on top of AI coding agents hits the same problem: distributing custom skills, rules, and commands to developers' machines. The config directories differ, the file formats differ, and the logic for "install this skill to that agent" gets copy-pasted across tooling.
loadout extracts that logic into a single, typed, tested library with an extensible adapter pattern. You write your artifacts once. loadout puts them where they belong.
- 🌐 Agent-agnostic — one artifact definition works across all supported agents
- 🔍 Auto-detection — discovers which agents are installed on the system
- 📁 Convention + configuration — scan for marker files, or define a manifest
- 🔌 Adapter pattern — add support for new agents without touching core logic
- 🪝 Lifecycle hooks — plug in your own logging, progress bars, or analytics
- 🛡️ Fully typed — strict mypy, Pydantic models, protocol classes
⚡ Quick start
🚀 Install everything to every detected agent
from loadout import install_all
summary = install_all("./my-artifacts", force=True)
print(f"✅ Installed: {len(summary.installed)}")
print(f"⏭️ Skipped: {len(summary.skipped)}")
print(f"❌ Failed: {len(summary.failed)}")
💬 Interactive mode (checkbox prompt)
uv add "loadout[interactive]"
from loadout import install_interactive
summary = install_interactive("./my-artifacts")
🎛️ Full control
from loadout import discover_artifacts, detect_agents, install
artifacts = discover_artifacts("./my-artifacts")
agents = detect_agents()
summary = install(artifacts, agents, force=True)
Three tiers — pick the one that fits your UX. 🎯
🤖 Supported agents
| Agent | Config dir | Skills | Rules | Agents | Commands |
|---|---|---|---|---|---|
| Claude Code | ~/.claude/ |
✅ | ✅ | ✅ | ✅ |
| Cursor | ~/.cursor/ |
✅ | ✅ (.mdc) | — | — |
| OpenCode | ~/.opencode/ |
✅ | — | — | ✅ |
loadout auto-detects which agents are present and only installs to agents that support each artifact type. Unsupported combinations are cleanly skipped. ✨
🔎 Artifact discovery
📂 Convention-based (marker files)
Drop marker files into your artifact directories and loadout will find them:
my-artifacts/
login-skill/
SKILL.md # ← marker: this directory is a skill
helper.py
utils.py
security/
auth-rule/
RULE.md # ← marker: this file is a rule
setup-agent/
AGENT.md # ← marker: this file is an agent
deploy/
COMMAND.md # ← marker: this file is a command
Marker files double as the artifact content — the SKILL.md is the skill. Categories are derived from directory structure (security/auth-rule/ → category security).
📋 Manifest-based (loadout.yaml)
For explicit control, add a loadout.yaml to the root of your artifacts directory:
artifacts:
- name: login-skill
type: skill
path: login-skill
- name: auth-rule
type: rule
path: security/auth-rule/RULE.md
category: security
description: "Enforces authentication checks"
When a manifest is present, marker-file scanning is skipped entirely — you have full control over what gets installed.
📝 Frontmatter support
Artifact files can include YAML frontmatter for metadata:
---
description: Handles user login flows
globs:
- "src/auth/**"
always_apply: true
---
# Login Skill
Your skill content here...
🔌 Custom adapters
Need to support a new coding agent? Implement the AgentAdapter interface and register it:
from pathlib import Path
from loadout import (
AgentAdapter,
Artifact,
ArtifactType,
DetectedAgent,
InstallResult,
get_default_registry,
install_all,
)
class WindsurfAdapter(AgentAdapter):
@property
def agent_name(self) -> str:
return "windsurf"
@property
def display_name(self) -> str:
return "Windsurf"
@property
def config_dir_name(self) -> str:
return ".windsurf"
def supported_artifact_types(self) -> set[ArtifactType]:
return {ArtifactType.SKILL, ArtifactType.RULE}
def detect(self) -> DetectedAgent | None:
config_dir = Path.home() / self.config_dir_name
if config_dir.is_dir():
return DetectedAgent(
name=self.agent_name,
config_dir=config_dir,
display_name=self.display_name,
)
return None
def get_target_path(self, artifact: Artifact, config_dir: Path) -> Path:
# Your path resolution logic
...
def transform_content(self, artifact: Artifact, content: str) -> str:
# Your content transformation logic
return content
def transform_filename(self, artifact: Artifact, filename: str) -> str:
return filename
def install(self, artifact: Artifact, agent: DetectedAgent, force: bool = False) -> InstallResult:
# Your install logic
...
# Register and use 🎉
registry = get_default_registry()
registry.register(WindsurfAdapter())
summary = install_all("./my-artifacts", registry=registry)
The adapter pattern means core loadout never needs to change when new agents appear. 🧩
🪝 Lifecycle callbacks
Hook into every stage of the installation process for logging, progress bars, analytics, or custom error handling:
from loadout import LoadoutCallbacks, Artifact, DetectedAgent, InstallResult, install_all
class RichCallbacks:
"""Example: pretty-print progress with Rich."""
def on_artifact_discovered(self, artifact: Artifact) -> None:
print(f" 🔎 Found {artifact.artifact_type.value}: {artifact.name}")
def on_agent_detected(self, agent: DetectedAgent) -> None:
print(f" 🤖 Detected agent: {agent.display_name}")
def on_install_started(self, artifact: Artifact, agent: DetectedAgent) -> None:
print(f" ⏳ Installing {artifact.name} → {agent.display_name}...")
def on_install_complete(self, result: InstallResult) -> None:
print(f" ✅ Installed to {result.target_path}")
def on_install_skipped(self, result: InstallResult) -> None:
print(f" ⏭️ Skipped: {result.error}")
def on_install_failed(self, result: InstallResult) -> None:
print(f" 💥 FAILED: {result.error}")
summary = install_all("./my-artifacts", callbacks=RichCallbacks())
Only override the hooks you care about — the LoadoutCallbacks protocol defines the full interface, and NoOpCallbacks provides a ready-made base with no-op defaults.
📖 API reference
⚙️ Top-level functions
| Function | Description |
|---|---|
install_all(source_dir, force, registry, callbacks) |
Discover artifacts, detect agents, install everything |
install_interactive(source_dir, force, registry, callbacks) |
Same as above with interactive agent selection |
install(artifacts, agents, force, registry, callbacks) |
Install specific artifacts to specific agents |
discover_artifacts(source_dir) |
Scan a directory and return a list of Artifact objects |
detect_agents(registry) |
Detect installed coding agents |
get_default_registry() |
Get the built-in adapter registry |
🧱 Models
| Model | Description |
|---|---|
Artifact |
A discovered artifact (name, type, source path, category, frontmatter) |
ArtifactType |
Enum: SKILL, RULE, AGENT, COMMAND |
DetectedAgent |
An agent found on the system (name, config dir, display name) |
InstallResult |
Result of a single artifact install (status, target path, error) |
InstallSummary |
Batch result with .installed, .skipped, .failed, .already_existed |
Manifest |
Parsed loadout.yaml manifest |
🚨 Exceptions
| Exception | Description |
|---|---|
LoadoutError |
Base exception for all loadout errors |
ArtifactNotFoundError |
Source artifact path does not exist |
ManifestError |
Invalid loadout.yaml |
InstallError |
Installation failed |
AdapterNotFoundError |
No adapter registered for the given agent |
AdapterAlreadyRegisteredError |
Adapter name collision |
TransformError |
Content transformation failed |
🛠️ Development
# Clone and install with all extras
git clone https://github.com/nickmaccarthy/loadout.git
cd loadout
# uv (recommended)
uv sync --all-extras
# pip
pip install -e ".[dev,interactive]"
# Set up pre-commit hooks
uv run pre-commit install
# Run all pre-commit checks (ruff, mypy, formatting, etc.)
uv run pre-commit run --all-files
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=loadout --cov-report=term-missing
💡 Pre-commit hooks run automatically on every
git commit, catching lint errors, type issues, and formatting problems before they hit CI.
📋 Requirements
- 🐍 Python 3.10+
- pydantic >= 2.0
- PyYAML >= 6.0
- questionary >= 2.0 (optional, for interactive mode)
📄 License
MIT — see LICENSE for details.
Metadata
Release files for loadout 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| loadout-0.5.0.tar.gz | 22.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| loadout-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.8 kB
Release files / loadout-0.5.0.tar.gz
| Download URL | loadout-0.5.0.tar.gz |
|---|---|
| Size | 22.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c5e419ec8ba63fc35015d0d375f814696ec27252fdd48df6c0a3487fad14d7f
|
|
BLAKE2b-256 checksum How to use checksums |
958d250bd679a4d0ea091906c408ae81b24a6b02d130995b013e6a81281e06c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 17, 2026.
Transparency logRelease files / loadout-0.5.0-py3-none-any.whl
| Download URL | loadout-0.5.0-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
22bb31a6b69046226d6b3acb23bc84602951aea23713841fb09935d785fc1bed
|
|
BLAKE2b-256 checksum How to use checksums |
c6ecea589ce95aae4e185b5b9454f05c24f6ad15e5150854437946e07f55bbac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 17, 2026.
Transparency logSecurity for loadout 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for loadout-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.