apm pack
Synopsis
Section titled “Synopsis”apm pack [OPTIONS]Description
Section titled “Description”apm pack produces distributable artifacts from the current APM project. It reads apm.yml to decide what to emit:
dependencies:mapping present -> a bundle (directory by default, or archive with--archive; see--archive-format). An explicit empty mapping (dependencies: {}) produces a bundle of the package’s local content; an omitted or nulldependencies:value does not.marketplace:block present -> selected marketplace artifacts.target:(ortargets:) field containingclaudeorcopilot-> ecosystem-specificplugin.jsonfiles.- Both blocks present -> bundle plus selected marketplace artifacts in a single run.
The bundle is built from apm.lock.yaml. An enriched copy of the lockfile (per-file SHA-256 in bundle_files, plus pack: metadata) is embedded inside the bundle so apm install <bundle> can verify integrity at install time.
Bundles are target-agnostic. The consumer’s project decides where files land at install time – the bundle carries no harness binding. Flags whose scope does not match the detected outputs are silent no-ops, not errors, so the same apm pack invocation works in CI across projects that produce only a bundle, only a marketplace, or both.
Options
Section titled “Options”| Flag | Default | Description |
|---|---|---|
--claude-plugin |
on (no-flag default) | Select the Claude Code plugin bundle: plugin.json plus plugin-native subdirs (agents/, skills/, commands/, instructions/, hooks/). This is what apm pack produces with zero flags. |
--format plugin|agent-plugin|claude|claude-plugin|apm |
claude-plugin |
Bundle format selector. agent-plugin is the sole opt-in for the portable Agent Plugins v1 bundle. plugin is a compatibility alias for the Claude Code plugin bundle, not for agent-plugin. claude and claude-plugin also select the Claude Code plugin bundle (the no-flag default). apm emits the legacy APM bundle layout, kept for tooling that still consumes it (e.g. microsoft/apm-action@v1 restore mode). Passing more than one selector (--claude-plugin, --format) is a usage error. |
--archive |
off | Produce a .zip archive instead of a directory (previous default: .tar.gz; use --archive-format tar.gz for legacy CI pipelines). Bundle only. |
--archive-format zip|tar.gz |
zip |
Archive format when --archive is set. zip is natively extractable on Windows and matches the format expected by Claude Code and plugin hosts. tar.gz is typically smaller for text-heavy bundles and preserves the previous default for pipelines that depend on it. |
-o, --output PATH |
./build |
Bundle output directory. Does not affect the marketplace.json path. |
--force |
off | Allow overwriting on collision. In plugin bundle format, last writer wins instead of first; for generated plugin.json manifests, overwrites an existing file instead of preserving it. |
--dry-run |
off | Print what would be packed without writing anything. |
--verbose, -v |
off | Show per-file paths and detailed packer output. |
--offline |
off | Marketplace: resolve version ranges from cached refs only; skip git ls-remote. |
--include-prerelease |
off | Marketplace: allow pre-release tags to satisfy version ranges. |
-m, --marketplace FORMATS |
all configured | Comma-separated list of marketplace formats to build. Sentinels: all (every configured format), none (skip marketplace entirely). |
--marketplace-path FORMAT=PATH |
manifest default | Override the output path for a specific format. Repeatable. Example: --marketplace-path codex=./dist/codex.json. |
--json |
off | Emit machine-readable JSON to stdout. All logs move to stderr. Includes metadata_enrichment.certifiable and per-package outcomes. |
--legacy-skill-paths |
off | Bundle skills under per-client paths (e.g. .cursor/skills/) instead of the converged .agents/skills/. Compatibility flag. |
--check-versions |
off | Release gate: verify per-package versions agree with the configured marketplace.versioning.strategy (lockstep, tag_pattern, or per_package). Exits 3 on misalignment. Composes with --check-clean and --dry-run. |
--check-clean |
off | Read-only release gate: regenerate every configured marketplace output in memory and diff against the same effective path used by apm pack, including --marketplace-path overrides. Exits 4 for drift or uncertifiable remote Claude metadata. It automatically suppresses normal pack writes. |
--strict-metadata |
off | Claude marketplace: fail before writing when remote package metadata cannot be fetched. Use it in publishing CI to require those fetches to succeed. Exits 5 before --check-clean runs when both flags are present. |
--target, -t VALUE |
auto-detect | Deprecated. Recorded as informational pack.target metadata only; ignored by apm install. Will be removed in a future release. |
Metadata outcome JSON
Section titled “Metadata outcome JSON”The metadata_enrichment object has a closed per-package status vocabulary:
| Status | Meaning | Certifiable |
|---|---|---|
fetched |
Remote apm.yml supplied metadata. |
yes |
empty |
Remote apm.yml was reachable but had no description or version. |
yes |
local |
Metadata came from a local package. | yes |
explicit |
Fixed description and version came from the marketplace entry. | yes |
failed |
The remote manifest could not be fetched. | no |
offline |
Fetching was intentionally skipped by --offline. |
no |
{ "metadata_enrichment": { "certifiable": false, "outcomes": [ { "package": "remote-tool", "status": "failed", "cause": "request timed out" } ] }, "errors": [ { "code": "marketplace_metadata_uncertifiable", "message": "remote metadata unavailable; regeneration is uncertifiable" } ]}Default packing reports an uncertifiable result as a warning. --check-clean
uses marketplace_metadata_uncertifiable and exits 4; --strict-metadata
uses metadata_incomplete and exits 5 before writing.
GitHub-hosted packages can inherit description and version from their remote
apm.yml. For GitLab, Azure DevOps, and other hosts, set fixed description
and version fields on the marketplace package entry so strict checks can
certify without a GitHub metadata request.
Examples
Section titled “Examples”Bundle only
Section titled “Bundle only”apm pack # Claude plugin bundle (default), ./build/apm pack --archive # Claude plugin bundle as .zip (default)apm pack --archive --archive-format tar.gz # legacy CI: produce .tar.gz insteadapm pack --format agent-plugin # explicit Agent Plugin v1 bundleapm pack --format apm -o ./dist # legacy APM bundle layoutMarketplace only
Section titled “Marketplace only”apm packapm pack --offline --dry-run
# Build only Claude format, output as JSON for CI:apm pack --marketplace=claude --json
# Override codex output path:apm pack --marketplace-path codex=./dist/codex-marketplace.json
# Build all formats, preview paths:apm pack --marketplace=all --json | jq -r '.marketplace.outputs[].path'Both artifacts in one run
Section titled “Both artifacts in one run”apm packapm pack --archive --offlineConfigure marketplace output paths
Section titled “Configure marketplace output paths”marketplace: outputs: claude: {} codex: path: ./build/codex-marketplace.jsonPreview without writing
Section titled “Preview without writing”apm pack --dry-runapm pack --archive --dry-run -vOutput format
Section titled “Output format”Claude plugin bundle (--claude-plugin, default)
Section titled “Claude plugin bundle (--claude-plugin, default)”A Claude Code plugin directory under --output. Contains:
plugin.json– schema-conformant manifest. Convention-dir keys are stripped because Claude Code auto-discovers them.- Plugin-native subdirs populated from local source and installed dependencies:
agents/,skills/,commands/,instructions/,hooks/,extensions/(canvas extensions, when thecanvasexperimental flag is enabled).- When
.apm/exists, local primitives and hooks are sourced from.apm/. Root convention sources are skipped with actionable warnings. - Without
.apm/, supported plugin-native root directories remain pack sources, including afterapm initwritesincludes: auto. - An explicit
includes:list is exhaustive. A missing or unpackable listed path stops packing instead of falling back to implicit discovery.
- When
- Installed dependencies are packed exclusively from lockfile-attested
deployed_files; theapm_modulescache is never packed (it has no provenance or integrity guarantee). Each attested file is verified against itsdeployed_file_hashesSHA-256 before inclusion.- If the dependency declares
skills:, only the named skills are included; the cache cannot add extras. - If a dependency has cached primitives but no
deployed_files,apm packfails and tells you to runapm install.
- If the dependency declares
- A merged
hooks.jsonfrom the producer’s own hooks. Dependency hook-configs and MCP-configs are not merged into the bundle; dependencies contribute only their attesteddeployed_files(hook scripts recorded there still map intohooks/). apm.lock.yaml– enriched copy withpack:metadata and abundle_filesmap of per-file SHA-256 digests, used byapm installfor install-time integrity verification.devDependenciesare excluded.
Agent Plugin bundle (--format agent-plugin)
Section titled “Agent Plugin bundle (--format agent-plugin)”An explicit opt-in. apm pack --format agent-plugin emits a strict, portable Agent Plugins v1 bundle instead of the Claude-compatible layout above. --format plugin does not select this bundle – it is a compatibility alias for the Claude plugin bundle below.
plugin.json– identifies the pinned Agent Plugins v1 schema ($schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"), synthesised fromapm.ymlthe same way as the Claude plugin manifest.skills/– standard skill bundles. This is the only primitive directory the Agent Plugin bundle carries.mcp.json– MCP server declarations fromapm.yml/apm.lock.yaml(present even when empty).apm.lock.yaml, andREADME.md/LICENSE/CHANGELOG.mdwhen present at the project root.
The portable schema deliberately scopes to skills/ and MCP config so a bundle can be consumed by any Agent-Plugin-aware host, not just APM. Agents, commands, instructions, extensions, hooks, and LSP configuration stay in the Claude-compatible layout instead. If your source project has any of them, apm pack --format agent-plugin fails before writing anything:
Cannot pack Agent Plugin: non-portable primitives would be discarded (agents, hooks).Agent Plugins v1 portable components are limited to root plugin.json, skills/, androot mcp.json. Use 'apm pack --format claude-plugin' to preserve agents, hooks in thelegacy Claude client format.LSP configuration gets its own guidance in the same error, since neither bundle format carries it: configure LSP servers directly in the target client instead.
APM bundle (--format apm)
Section titled “APM bundle (--format apm)”The legacy APM layout under --output. Files are copied preserving their install-time directory structure. Installed dependencies are packed exclusively from lockfile-attested deployed_files, and each file is verified against its deployed_file_hashes SHA-256 before it is copied (the same integrity gate the Claude plugin format applies) – a file whose bytes no longer match its recorded hash fails the pack with ... does not match the hash recorded in apm.lock.yaml. Files with no recorded hash (older lockfiles) pack without verification. The bundle’s apm.lock.yaml carries the same pack: metadata and bundle_files digests. The project’s own apm.lock.yaml is never modified.
Example enriched lockfile fragment:
pack: format: apm packed_at: '2026-03-09T12:00:00+00:00' bundle_files: .github/agents/architect.md: a1b2c3...lockfile_version: '1'dependencies: - repo_url: owner/repoMarketplace artifacts
Section titled “Marketplace artifacts”.claude-plugin/marketplace.json by default, plus any additional artifact selected by marketplace.outputs such as .agents/plugins/marketplace.json for Codex. Each remote plugin’s version range is resolved against git ls-remote; local-path entries pass through verbatim. Files are written atomically, and parent directories are created if absent.
Configure marketplace artifact paths in apm.yml with the marketplace.outputs map, keyed by format. Use --marketplace-path FORMAT=PATH to override per-format output paths at pack time.
Remote Claude entries can inherit description and version from their own
apm.yml. If APM cannot fetch that metadata, normal packing writes the artifact
with an actionable warning so authors can add those fields to the marketplace
entry or retry with network access. Use --strict-metadata in publishing CI to
fail before writing with uncertifiable remote metadata. --check-clean also fails with exit
4 rather than certifying a regeneration whose metadata could not be fetched.
Plugin manifests
Section titled “Plugin manifests”Ship one APM package; consumers get a native plugin for their tool of choice. When apm.yml declares a target: (or targets:) field containing claude or copilot, apm pack generates an ecosystem-specific plugin.json so the same source tree drops into a Claude Code plugin directory or a Copilot plugin path with no hand-editing.
| Ecosystem | Output path |
|---|---|
claude |
.claude-plugin/plugin.json |
copilot |
.github/plugin/plugin.json |
This runs for the default Claude plugin build (--claude-plugin, or --format plugin|claude|claude-plugin, since plugin is a compatibility alias for the Claude plugin bundle). Under an
explicit --format agent-plugin build, the Claude ecosystem
manifest is skipped – if target: names only claude and the project has no
other agent sources, apm pack --format agent-plugin fails and tells you to use
apm pack --claude-plugin instead.
Add one line to apm.yml and pack:
name: my-pluginversion: 1.0.0target: claudeapm pack # writes .claude-plugin/plugin.jsonUse targets: [claude, copilot] instead to emit both .claude-plugin/plugin.json and .github/plugin/plugin.json from the one source tree in a single apm pack.
target: and targets: are mutually exclusive: declaring both is a build error (exit 1). An empty targets: list or an unrecognised ecosystem token is likewise rejected before any artifact is written.
The manifest is synthesised from apm.yml identity fields (name, version, description, author, license). Per-ecosystem differences:
- Claude: includes
mcpServerssourced from.mcp.jsonwhen that file declares servers that survive credential stripping. - Copilot: omits
mcpServers.
Credential stripping (Claude mcpServers)
Section titled “Credential stripping (Claude mcpServers)”.mcp.json routinely embeds secrets that an MCP host injects at startup, so they are removed before the manifest is written – a committed plugin.json never leaks them. Stripping is recursive and applies at any nesting depth:
- Credential-bearing keys are dropped:
env/environment/headers/authorizationblocks, plus any key whose name containstoken,secret,password,credential,apikey, orkey. - Secret-shaped values are redacted even when the key name is innocuous:
user:pass@hostURL userinfo, inline--token=...flags, space-separated--token valuepairs, shellENV=secretprefixes,Bearer/Basicauth headers, and bare provider tokens (GitHub, OpenAI, Slack, AWS, Google, GitLab, npm, PyPI, HuggingFace, Stripe, SendGrid, Supabase, Databricks, and other recognised provider token prefixes) passed as positionalargs.
A warning lists everything dropped or redacted, led by the consequence (secrets withheld from commit).
Overwrite and dry-run
Section titled “Overwrite and dry-run”If a plugin.json already exists at the target path it is preserved: apm pack warns and skips the write. Re-run with --force to overwrite it (the same flag that governs bundle collisions). The --dry-run flag prevents any writes – the manifest content is computed but not persisted.
Plugin manifest generation runs after BUNDLE and MARKETPLACE phases so the generated file is never accidentally included in the bundle export.
Behavior
Section titled “Behavior”- Lockfile-attested dependencies. Dependency content is packed exclusively from lockfile
deployed_filesand verified againstdeployed_file_hashes; theapm_modulescache is never packed. If a dependency has cached primitives but nodeployed_files,apm packerrors and tells you to runapm install. - Hidden-character scan. Source files are scanned before bundling. Findings are reported as warnings only – packing is non-blocking. Consumers are protected at install time, where critical findings block.
- Empty bundle warning. If no package files match after dependency resolution,
apm packemits a warning and exits0with an empty bundle. Missing dependency content is an error, not an empty bundle. - Share line. On success,
apm packprintsShare with: apm install <bundle-path>so the produced bundle is immediately copy-pasteable. - Marketplace fallback. With no
marketplace:block inapm.yml, a legacymarketplace.ymlfile is read with a deprecation warning. Both files present is a hard error. - Marketplace outputs. Configure via
marketplace.outputsmap (keyed by format). Claude is included by default. The legacy list form (outputs: [claude]) still parses with a deprecation warning. Use--marketplace=to filter which formats are built in a given invocation. - JSON mode.
--jsonmakesapm packmachine-friendly: stdout is a single JSON object, all human-readable logs move to stderr. Combine with--marketplace=for selective CI matrix builds.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
Success. Requested artifacts written, planned with --dry-run, or validated without writes by --check-clean. |
1 |
Build or runtime error: network failure, ref not found, no tag matches a marketplace range, lockfile read error, or unhandled packer exception. |
2 |
apm.yml schema validation error. |
3 |
--check-versions failed: per-package versions disagree with the configured marketplace versioning strategy. |
4 |
--check-clean failed: marketplace working tree is dirty (regenerated output differs from on-disk file), or remote Claude metadata could not be fetched to certify the comparison. |
5 |
--strict-metadata failed: remote marketplace metadata was unavailable, so APM did not write the artifact. |
Related
Section titled “Related”apm unpack– inverse, deprecated; preferapm install <bundle>.apm install– consumer side; installs a packed bundle directory,.zip, or.tar.gz.- Pack a bundle (producer guide) – task-oriented walkthrough.
- Publish to a marketplace – end-to-end marketplace flow.
- Lockfile spec –
pack:metadata andbundle_filesschema.