Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ New to APM? Follow this learning path for the fastest way to get productive:

### Command Line Interface
- **[CLI Reference](cli-reference.md)** - Complete command documentation with examples
- **[Plugin System](plugins.md)** - Installing and using plugins with plugin.json format
- **[Manifest Schema](manifest-schema.md)** - Complete `apm.yml` schema reference for integrators
- **[Integration Guide](integrations.md)** - VSCode, Spec-kit, AI runtimes, and tool compatibility

Expand Down
283 changes: 283 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# Plugins

APM supports plugins through the `plugin.json` format. Plugins are automatically detected and integrated into your project as standard APM dependencies.

## Overview

Plugins are packages that contain:

- **Skills** - Reusable agent personas and expertise
- **Agents** - AI agent definitions
- **Commands** - Executable prompts and workflows
- **Instructions** - Context and guidelines

APM automatically detects plugins with `plugin.json` manifests and synthesizes `apm.yml` from the metadata, treating them identically to other APM packages.

## Installation

Install plugins using the standard `apm install` command:

```bash
# Install a plugin from GitHub
apm install owner/repo/plugin-name

# Or add to apm.yml
dependencies:
apm:
- anthropics/claude-code-plugins/commit-commands#v1.2.0
```

## How APM Handles Plugins

When you run `apm install owner/repo/plugin-name`:

1. **Clone** - APM clones the repository to `apm_modules/`
2. **Detect** - It searches for `plugin.json` in priority order:
- `.github/plugin/plugin.json` (GitHub Copilot format)
- `.claude-plugin/plugin.json` (Claude format)
- `plugin.json` (root)
3. **Map Artifacts** - Plugin primitives from the repository root are mapped into `.apm/`:
- `agents/` → `.apm/agents/`
- `skills/` → `.apm/skills/`
- `commands/` → `.apm/prompts/`
- `*.md` command files are normalized to `*.prompt.md` for prompt/command integration
4. **Synthesize** - `apm.yml` is automatically generated from plugin metadata
5. **Integrate** - The plugin is now a standard dependency with:
- Version pinning via `apm.lock`
- Transitive dependency resolution
- Conflict detection
- Everything else APM packages support

This unified approach means **no special commands needed** — plugins work exactly like any other APM package.

## Plugin Format

A plugin repository contains a `plugin.json` manifest and primitives at the repository root.

### Supported Plugin Structures

APM supports multiple plugin manifest locations to accommodate different platforms:

#### GitHub Copilot Format
```
plugin-repo/
├── .github/
│ └── plugin/
│ └── plugin.json # GitHub Copilot location (highest priority)
├── agents/
│ └── agent-name.agent.md
├── skills/
│ └── skill-name/
│ └── SKILL.md
└── commands/
└── command-1.md
└── command-2.md
```

#### Claude Format
```
plugin-repo/
├── .claude-plugin/
│ └── plugin.json # Claude location (second priority)
├── agents/
│ └── agent-name.agent.md
├── skills/
│ └── skill-name/
│ └── SKILL.md
└── commands/
└── command-1.md
└── command-2.md
```

#### Legacy APM Format
```
plugin-repo/
├── plugins/
│ └── plugin.json # Legacy APM location (third priority)
├── agents/
│ └── agent-name.agent.md
├── skills/
│ └── skill-name/
│ └── SKILL.md
└── commands/
└── command-1.md
└── command-2.md
```

#### Root Format
```
plugin-repo/
├── plugin.json # Root location (lowest priority)
├── agents/
│ └── agent-name.agent.md
├── skills/
│ └── skill-name/
│ └── SKILL.md
└── commands/
└── command-1.md
└── command-2.md
```

**Priority Order**: APM checks for `plugin.json` in exactly three locations:
1. `plugin.json` (root)
2. `.github/plugin/plugin.json`
3. `.claude-plugin/plugin.json`

**Note**: Primitives (agents, skills, commands, instructions) are always located at the repository root, regardless of where `plugin.json` is located.

### plugin.json Manifest

Required fields:

```json
{
"name": "Plugin Display Name",
"version": "1.0.0",
"description": "What this plugin does"
}
```

Optional fields:

```json
{
"name": "My Plugin",
"version": "1.0.0",
"description": "A plugin for APM",
"author": "Author Name",
"license": "MIT",
"repository": "owner/repo",
"homepage": "https://example.com",
"tags": ["ai", "coding"],
"dependencies": [
"another-plugin-id"
]
}
```

## Examples

### Installing Plugins from GitHub

```bash
# Install a specific plugin
apm install anthropics/claude-code-plugins/commit-commands

# With version
apm install anthropics/claude-code-plugins/commit-commands#v1.2.0
```

### Adding Multiple Plugins to apm.yml

```yaml
dependencies:
apm:
- anthropics/claude-code-plugins/commit-commands#v1.2.0
- anthropics/claude-code-plugins/refactor-tools#v2.0
- mycompany/internal-standards#main
```

Then sync and install:

```bash
apm install
```

### Version Management

Plugins support all standard APM versioning:

```yaml
dependencies:
apm:
# Latest version
- owner/repo/plugin

# Latest from branch
- owner/repo/plugin#main

# Specific tag
- owner/repo/plugin#v1.2.0

# Specific commit
- owner/repo/plugin#abc123
```

Run `apm install` to download and lock versions in `apm.lock`.

## Supported Hosts

- **GitHub** - `owner/repo` or `owner/repo/plugin-path`
- **GitHub** - GitHub URLs or SSH references
- **Azure DevOps** - `dev.azure.com/org/project/repo`

## Lock File Integration

Plugin versions are automatically tracked in `apm.lock`:

```yaml
apm_modules:
anthropics/claude-code-plugins/commit-commands:
resolved: https://github.com/anthropics/claude-code-plugins/commit-commands#v1.2.0
commit: abc123def456789
```

This ensures reproducible installs across environments.

## Conflict Detection

APM automatically detects:

- Duplicate plugins from different sources
- Version conflicts between dependencies
- Missing transitive dependencies

Run with `--verbose` to see dependency resolution details:

```bash
apm install --verbose
```

## Compilation

Plugins are automatically compiled during `apm compile`:

```bash
apm compile
```

This:
- Generates `AGENTS.md` from plugin agents
- Integrates skills into the runtime
- Includes prompt primitives

## Finding Plugins

Plugins can be found through:
- GitHub repositories (search for repos with `plugin.json`)
- Organization-specific plugin repositories
- Community plugin collections

Once found, install them using the standard `apm install owner/repo/plugin-name` command.

## Troubleshooting

### Plugin Not Detected

If APM doesn't recognize your plugin:

1. Check `plugin.json` exists at the repository root or in a subdirectory:
- `plugin.json` (root )
- `.github/plugin/plugin.json` (GitHub Copilot format)
- `.claude-plugin/plugin.json` (Claude format)
2. Verify JSON is valid: `cat plugin.json | jq .`
3. Ensure required fields are present: `name`, `version`, `description`
4. Verify primitives are at the repository root (`agents/`, `skills/`, `commands/`)

### Version Resolution Issues

See the [concepts.md](./concepts.md) guide on dependency resolution.

### Custom Hosts / Private Repositories

See [integration-testing.md](./integration-testing.md) for enterprise setup.
4 changes: 4 additions & 0 deletions src/apm_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,10 @@ def download_callback(dep_ref, modules_dir):
_rich_info(
f" └─ Package type: Skill (SKILL.md detected)"
)
elif package_type == PackageType.MARKETPLACE_PLUGIN:
_rich_info(
f" └─ Package type: Marketplace Plugin (plugin.json detected)"
)
elif package_type == PackageType.HYBRID:
_rich_info(
f" └─ Package type: Hybrid (apm.yml + SKILL.md)"
Expand Down
31 changes: 30 additions & 1 deletion src/apm_cli/commands/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ def list_packages():
# Skip sub-skills inside .apm/ directories — they belong to the parent package
if '.apm' in rel_parts:
continue


# Skip skill sub-dirs nested inside another package (e.g. plugin
# skills/ directories that are deployment artifacts, not packages).
if has_skill_md and not has_apm_yml:
if _is_nested_under_package(candidate, apm_modules_path):
continue

try:
version = 'unknown'
if has_apm_yml:
Expand Down Expand Up @@ -357,6 +363,10 @@ def _add_children(parent_branch, parent_repo_url, depth=0):
rel_parts = candidate.relative_to(apm_modules_path).parts
if len(rel_parts) < 2:
continue
if '.apm' in rel_parts:
continue
if has_skill and not has_apm and _is_nested_under_package(candidate, apm_modules_path):
continue
display = "/".join(rel_parts)
info = _get_package_display_info(candidate)
branch = root_tree.add(f"[green]{info['display_name']}[/green]")
Expand Down Expand Up @@ -570,6 +580,25 @@ def info(package: str):

# Helper functions


def _is_nested_under_package(candidate: Path, apm_modules_path: Path) -> bool:
"""Check if *candidate* is a sub-directory of another installed package.

When a plugin ships ``skills/*/SKILL.md`` at its root (outside ``.apm/``),
the ``rglob`` scan would otherwise treat each skill sub-directory as an
independent package. This helper walks up from *candidate* towards
*apm_modules_path* and returns ``True`` if any intermediate parent already
contains ``apm.yml`` — meaning the candidate is a deployment artifact, not
a standalone package.
"""
parent = candidate.parent
while parent != apm_modules_path and parent != parent.parent:
if (parent / "apm.yml").exists():
return True
parent = parent.parent
return False


def _count_primitives(package_path: Path) -> Dict[str, int]:
"""Count primitives by type in a package.

Expand Down
Loading