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
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ The architectural decisions and basis for the project in that document are only
- The philosophy when architecting and implementing the project is to prime speed and simplicity over complexity. Do NOT over-engineer, but rather build a solid foundation that can be iterated on.
- APM is an active OSS project under the `microsoft` org with a growing community (250+ stars, external contributors). Breaking changes should be communicated clearly (CHANGELOG.md), but we still favor shipping fast over lengthy deprecation cycles.
- The goal is to deliver a solid and scalable architecture but simple starting implementation. Not building something complex from the start and then having to simplify it later. Remember we are delivering a new tool to the developer community and we will need to rapidly adapt to what's really useful, evolving standards, etc.
- **Cross-platform encoding rule**: All source code and CLI output must stay within printable ASCII (U+0020–U+007E). Do NOT use emojis, Unicode symbols, box-drawing characters, em dashes, or any character outside the ASCII range in source files or CLI output strings. Use bracket notation for status symbols: `[+]` success, `[!]` warning, `[x]` error, `[i]` info, `[*]` action, `[>]` running. This is required to prevent `charmap` codec errors on Windows cp1252 terminals.
- **Cross-platform encoding rule**: All source code and CLI output must stay within printable ASCII (U+0020–U+007E). Do NOT use emojis, Unicode symbols, box-drawing characters, em dashes, or any character outside the ASCII range in source files or CLI output strings. Use bracket notation for status symbols: `[+]` success, `[!]` warning, `[x]` error, `[i]` info, `[*]` action, `[>]` running. This is required to prevent `charmap` codec errors on Windows cp1252 terminals.
- **Path safety rule**: Any code that builds filesystem paths from user input or external data (marketplace names, plugin paths, lockfile entries, bundle contents) **must** use the centralized guards in `src/apm_cli/utils/path_security.py`. Use `validate_path_segments(value, context=)` at parse time to reject traversal sequences (`.`, `..`) with cross-platform backslash normalization, and `ensure_path_within(path, base_dir)` after resolution to assert containment (resolves symlinks). Never write ad-hoc `".." in x` checks.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ apm_modules/
build/tmp/
scout-pipeline-result.png
.copilot/
.playwright-mcp/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Marketplace integration for plugin discovery and governance: `apm marketplace add/list/browse/update/remove` commands, `apm search QUERY@MARKETPLACE` scoped search, `apm install NAME@MARKETPLACE` syntax for installing plugins from marketplace registries; lockfile provenance fields `discovered_via` and `marketplace_plugin_name` to track marketplace origin; support for Copilot CLI and Claude Code `marketplace.json` formats with 4 source types (github, url, git-subdir, relative path) (#503)
### Changed

- `apm deps update` now skips download and integration for packages whose resolved SHA matches the lockfile SHA, making the common "nothing changed" case near-instant (#495)
Expand Down
155 changes: 155 additions & 0 deletions docs/src/content/docs/guides/marketplaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: "Marketplaces"
sidebar:
order: 5
---

Marketplaces are curated indexes of plugins hosted as GitHub repositories. Each marketplace contains a `marketplace.json` file that maps plugin names to source locations. APM resolves these entries to Git URLs, so plugins installed from marketplaces get the same version locking, security scanning, and governance as any other APM dependency.

## How marketplaces work

A marketplace is a GitHub repository with a `marketplace.json` at its root. The file lists plugins with their source type and location:

```json
{
"name": "Acme Plugins",
"plugins": [
{
"name": "code-review",
"description": "Automated code review agent",
"source": { "type": "github", "repo": "acme/code-review-plugin" }
},
{
"name": "style-guide",
"source": { "type": "url", "url": "https://github.com/acme/style-guide.git" }
},
{
"name": "eslint-rules",
"source": { "type": "git-subdir", "repo": "acme/monorepo", "subdir": "plugins/eslint-rules" }
},
{
"name": "local-tools",
"source": "./tools/local-plugin"
}
]
}
```

Both Copilot CLI and Claude Code `marketplace.json` formats are supported. Copilot CLI uses `"repository"` and `"ref"` fields; Claude Code uses `"source"` (string or object). APM normalizes entries from either format into its canonical dependency representation.

### Supported source types

| Type | Description | Example |
|------|-------------|---------|
| `github` | GitHub `owner/repo` shorthand | `acme/code-review-plugin` |
| `url` | Full HTTPS or SSH Git URL | `https://github.com/acme/style-guide.git` |
| `git-subdir` | Subdirectory within a Git repository (`repo` + `subdir`) | `acme/monorepo` + `plugins/eslint-rules` |
| String `source` | Subdirectory within the marketplace repository itself | `./tools/local-plugin` |

npm sources are not supported. Copilot CLI format uses `"repository"` and optional `"ref"` fields instead of `"source"`.

## Register a marketplace

```bash
apm marketplace add acme/plugin-marketplace
```

This registers the marketplace and fetches its `marketplace.json`. By default APM tracks the `main` branch.

**Options:**
- `--name/-n` -- Custom display name for the marketplace
- `--branch/-b` -- Branch to track (default: `main`)

```bash
# Register with a custom name on a specific branch
apm marketplace add acme/plugin-marketplace --name acme-plugins --branch release
```

## List registered marketplaces

```bash
apm marketplace list
```

Shows all registered marketplaces with their source repository and branch.

## Browse plugins

View all plugins available in a specific marketplace:

```bash
apm marketplace browse acme-plugins
```

## Search a marketplace

Search plugins by name or description in a specific marketplace using `QUERY@MARKETPLACE`:

```bash
apm search "code review@skills"
```

**Options:**
- `--limit` -- Maximum results to return (default: 20)

```bash
apm search "linting@awesome-copilot" --limit 5
```

The `@MARKETPLACE` scope is required -- this avoids name collisions when different
marketplaces contain plugins with the same name. To see everything in a marketplace,
use `apm marketplace browse <name>` instead.

## Install from a marketplace

Use the `NAME@MARKETPLACE` syntax to install a plugin from a specific marketplace:

```bash
apm install code-review@acme-plugins
```

APM resolves the plugin name against the marketplace index, fetches the underlying Git repository, and installs it as a standard APM dependency. The resolved source appears in `apm.yml` and `apm.lock.yaml` just like any direct dependency.

For full `apm install` options, see [CLI Commands](../../reference/cli-commands/).

## Provenance tracking

Marketplace-resolved plugins are tracked in `apm.lock.yaml` with full provenance:

```yaml
apm_modules:
acme/code-review-plugin:
resolved: https://github.com/acme/code-review-plugin#main
commit: abc123def456789
discovered_via: acme-plugins
marketplace_plugin_name: code-review
```

The `discovered_via` field records which marketplace was used for discovery. `marketplace_plugin_name` stores the original plugin name from the index. The `resolved` URL and `commit` pin the exact version, so builds remain reproducible regardless of marketplace availability.

## Cache behavior

APM caches marketplace indexes locally with a 1-hour TTL. Within that window, commands like `search` and `browse` use the cached index. After expiry, APM fetches a fresh copy from the network. If the network request fails, APM falls back to the expired cache (stale-if-error) so commands still work offline.

Force a cache refresh:

```bash
# Refresh a specific marketplace
apm marketplace update acme-plugins

# Refresh all registered marketplaces
apm marketplace update
```

## Manage marketplaces

Remove a registered marketplace:

```bash
apm marketplace remove acme-plugins

# Skip confirmation prompt
apm marketplace remove acme-plugins --yes
```

Removing a marketplace does not uninstall plugins previously installed from it. Those plugins remain pinned in `apm.lock.yaml` to their resolved Git sources.
12 changes: 10 additions & 2 deletions docs/src/content/docs/guides/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,19 @@ Use the [hybrid authoring workflow](#hybrid-authoring-workflow) to develop plugi
## Finding Plugins

Plugins can be found through:
- **Marketplaces** -- curated `marketplace.json` indexes browsable with `apm marketplace browse` and searchable with `apm search QUERY@MARKETPLACE`. See the [Marketplaces guide](../marketplaces/) for setup.
- 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.
Install by name from a registered marketplace:

```bash
apm install code-review@acme-plugins
```

APM resolves marketplace entries to Git URLs, so marketplace-installed plugins get full version locking, security scanning, and governance. See [Marketplaces](../marketplaces/) for details.

For direct installs, use the standard `apm install owner/repo/plugin-name` command.

## Troubleshooting

Expand Down
147 changes: 146 additions & 1 deletion docs/src/content/docs/reference/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ apm install [PACKAGES...] [OPTIONS]
```

**Arguments:**
- `PACKAGES` - Optional APM packages to add and install. Accepts shorthand (`owner/repo`), HTTPS URLs, SSH URLs, FQDN shorthand (`host/owner/repo`), or local filesystem paths (`./path`, `../path`, `/absolute/path`, `~/path`). All forms are normalized to canonical format in `apm.yml`.
- `PACKAGES` - Optional APM packages to add and install. Accepts shorthand (`owner/repo`), HTTPS URLs, SSH URLs, FQDN shorthand (`host/owner/repo`), local filesystem paths (`./path`, `../path`, `/absolute/path`, `~/path`), or marketplace references (`NAME@MARKETPLACE`). All forms are normalized to canonical format in `apm.yml`.

**Options:**
- `--runtime TEXT` - Target specific runtime only (copilot, codex, vscode)
Expand Down Expand Up @@ -151,6 +151,9 @@ apm install --dev owner/test-helpers
# Install from a local path (copies to apm_modules/_local/)
apm install ./packages/my-shared-skills
apm install /home/user/repos/my-ai-package

# Install a plugin from a registered marketplace
apm install code-review@acme-plugins
```

**Auto-Bootstrap Behavior:**
Expand Down Expand Up @@ -832,6 +835,148 @@ apm mcp show a5e8a7f0-d4e4-4a1d-b12f-2896a23fd4f1
- Available installation packages
- Installation instructions

### `apm marketplace` - Plugin marketplace management

Register, browse, and manage plugin marketplaces. Marketplaces are GitHub repositories containing a `marketplace.json` index of plugins.

> See the [Marketplaces guide](../../guides/marketplaces/) for concepts and workflows.

```bash
apm marketplace COMMAND [OPTIONS]
```

#### `apm marketplace add` - Register a marketplace

Register a GitHub repository as a plugin marketplace.

```bash
apm marketplace add OWNER/REPO [OPTIONS]
```

**Arguments:**
- `OWNER/REPO` - GitHub repository containing `marketplace.json`

**Options:**
- `-n, --name TEXT` - Custom display name for the marketplace
- `-b, --branch TEXT` - Branch to track (default: main)
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
# Register a marketplace
apm marketplace add acme/plugin-marketplace

# Register with a custom name and branch
apm marketplace add acme/plugin-marketplace --name acme-plugins --branch release
```

#### `apm marketplace list` - List registered marketplaces

List all registered marketplaces with their source repository and branch.

```bash
apm marketplace list [OPTIONS]
```

**Options:**
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
apm marketplace list
```

#### `apm marketplace browse` - Browse marketplace plugins

List all plugins available in a registered marketplace.

```bash
apm marketplace browse NAME [OPTIONS]
```

**Arguments:**
- `NAME` - Name of the registered marketplace

**Options:**
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
# Browse all plugins in a marketplace
apm marketplace browse acme-plugins
```

#### `apm marketplace update` - Refresh marketplace cache

Refresh the cached `marketplace.json` for one or all registered marketplaces.

```bash
apm marketplace update [NAME] [OPTIONS]
```

**Arguments:**
- `NAME` - Optional marketplace name. Omit to refresh all.

**Options:**
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
# Refresh a specific marketplace
apm marketplace update acme-plugins

# Refresh all marketplaces
apm marketplace update
```

#### `apm marketplace remove` - Remove a registered marketplace

Unregister a marketplace. Plugins previously installed from it remain pinned in `apm.lock.yaml`.

```bash
apm marketplace remove NAME [OPTIONS]
```

**Arguments:**
- `NAME` - Name of the marketplace to remove

**Options:**
- `-y, --yes` - Skip confirmation prompt
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
# Remove with confirmation prompt
apm marketplace remove acme-plugins

# Remove without confirmation
apm marketplace remove acme-plugins --yes
```

### `apm search` - Search plugins in a marketplace

Search for plugins by name or description within a specific marketplace.

```bash
apm search QUERY@MARKETPLACE [OPTIONS]
```

**Arguments:**
- `QUERY@MARKETPLACE` - Search term scoped to a marketplace (e.g., `security@skills`)

**Options:**
- `--limit INTEGER` - Maximum results to return (default: 20)
- `-v, --verbose` - Show detailed output

**Examples:**
```bash
# Search for code review plugins in a marketplace
apm search "code review@skills"

# Limit results
apm search "linting@awesome-copilot" --limit 5
```

### `apm run` (Experimental) - Execute prompts

Execute a script defined in your apm.yml with parameters and real-time output streaming.
Expand Down
3 changes: 3 additions & 0 deletions src/apm_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from apm_cli.commands.init import init
from apm_cli.commands.install import install
from apm_cli.commands.list_cmd import list as list_cmd
from apm_cli.commands.marketplace import marketplace, search as marketplace_search
from apm_cli.commands.mcp import mcp
from apm_cli.commands.pack import pack_cmd, unpack_cmd
from apm_cli.commands.prune import prune
Expand Down Expand Up @@ -69,6 +70,8 @@ def cli(ctx):
cli.add_command(config)
cli.add_command(runtime)
cli.add_command(mcp)
cli.add_command(marketplace)
cli.add_command(marketplace_search, name="search")


def _configure_encoding() -> None:
Expand Down
Loading
Loading