Skip to content

apm pack

Terminal window
apm pack [OPTIONS]

apm pack produces distributable artifacts from the current APM project. It reads apm.yml to decide what to emit:

  • dependencies: block present -> a bundle (directory by default, or archive with --archive; see --archive-format).
  • marketplace: block present -> selected marketplace artifacts.
  • target: (or targets:) field containing claude or copilot -> ecosystem-specific plugin.json files.
  • 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.

FlagDefaultDescription
--format plugin|apmpluginBundle format. plugin emits a Claude Code plugin directory with plugin.json and plugin-native subdirs (agents/, skills/, commands/, instructions/, hooks/). apm emits the legacy APM bundle layout, kept for tooling that still consumes it (e.g. microsoft/apm-action@v1 restore mode).
--archiveoffProduce 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.gzzipArchive 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./buildBundle output directory. Does not affect the marketplace.json path.
--forceoffAllow 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-runoffPrint what would be packed without writing anything.
--verbose, -voffShow per-file paths and detailed packer output.
--offlineoffMarketplace: resolve version ranges from cached refs only; skip git ls-remote.
--include-prereleaseoffMarketplace: allow pre-release tags to satisfy version ranges.
-m, --marketplace FORMATSall configuredComma-separated list of marketplace formats to build. Sentinels: all (every configured format), none (skip marketplace entirely).
--marketplace-path FORMAT=PATHmanifest defaultOverride the output path for a specific format. Repeatable. Example: --marketplace-path codex=./dist/codex.json.
--jsonoffEmit machine-readable JSON to stdout. All logs move to stderr. Shape: {ok, dry_run, warnings, errors, marketplace: {outputs: [...]}}.
--legacy-skill-pathsoffBundle skills under per-client paths (e.g. .cursor/skills/) instead of the converged .agents/skills/. Compatibility flag.
--check-versionsoffRelease 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-cleanoffRelease gate: regenerate every configured marketplace output to a temp path and diff against the on-disk file. Exits 4 if the working tree is dirty (out-of-date marketplace.json). The gate itself never writes to disk.
--target, -t VALUEauto-detectDeprecated. Recorded as informational pack.target metadata only; ignored by apm install. Will be removed in a future release.
Terminal window
apm pack # plugin format (default), ./build/
apm pack --archive # plugin bundle as .zip (default)
apm pack --archive --archive-format tar.gz # legacy CI: produce .tar.gz instead
apm pack --format apm -o ./dist # legacy APM bundle layout
Terminal window
apm pack
apm 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'
Terminal window
apm pack
apm pack --archive --offline
marketplace:
outputs:
claude: {}
codex:
path: ./build/codex-marketplace.json
Terminal window
apm pack --dry-run
apm pack --archive --dry-run -v

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 your .apm/ content and from installed dependencies: agents/, skills/, commands/, instructions/, hooks/, extensions/ (canvas extensions, when the canvas experimental flag is enabled).
  • A merged hooks.json when multiple sources contribute hooks.
  • apm.lock.yaml — enriched copy with pack: metadata and a bundle_files map of per-file SHA-256 digests, used by apm install for install-time integrity verification.
  • devDependencies are excluded.

The legacy APM layout under --output. Files are copied preserving their install-time directory structure. 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'
generated_at: ...
dependencies:
- repo_url: owner/repo

.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.

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.

EcosystemOutput path
claude.claude-plugin/plugin.json
copilot.github/plugin/plugin.json

Add one line to apm.yml and pack:

apm.yml
name: my-plugin
version: 1.0.0
target: claude
Terminal window
apm pack # writes .claude-plugin/plugin.json

Use 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 mcpServers sourced from .mcp.json when that file declares servers that survive credential stripping.
  • Copilot: omits 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/authorization blocks, plus any key whose name contains token, secret, password, credential, apikey, or key.
  • Secret-shaped values are redacted even when the key name is innocuous: user:pass@host URL userinfo, inline --token=... flags, space-separated --token value pairs, shell ENV=secret prefixes, Bearer/Basic auth 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 positional args.

A warning lists everything dropped or redacted, led by the consequence (secrets withheld from commit).

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.

  • Lockfile-driven. The bundle enumerates deployed_files from apm.lock.yaml. Run apm install first if the lockfile is stale or missing.
  • 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 files match (e.g. nothing was installed yet), apm pack emits a warning and exits 0 with an empty bundle. Verbose mode prints a hint to run apm install first.
  • Share line. On success, apm pack prints Share with: apm install <bundle-path> so the produced bundle is immediately copy-pasteable.
  • Marketplace fallback. With no marketplace: block in apm.yml, a legacy marketplace.yml file is read with a deprecation warning. Both files present is a hard error.
  • Marketplace outputs. Configure via marketplace.outputs map (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. --json makes apm pack machine-friendly: stdout is a single JSON object, all human-readable logs move to stderr. Combine with --marketplace= for selective CI matrix builds.
CodeMeaning
0Success. Requested artifacts written (or, with --dry-run, planned).
1Build or runtime error: network failure, ref not found, no tag matches a marketplace range, lockfile read error, or unhandled packer exception.
2apm.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).