Skip to content

Producer experience: close the gap from "I built a plugin" to "every assistant can install it" #1348

@danielmeppiel

Description

@danielmeppiel

Producer experience: close the gap from "I built a plugin" to "every assistant can install it"

TL;DR

After #1281 (multi-profile marketplace outputs) and #1317 (filter-driven CLI + map-based manifest + --json), HEAD has the runtime primitives to publish any APM project to multiple AI assistants. What's missing is the producer journey that turns those primitives into a press-the-button release: discoverable repo shapes, a vendor-agnostic post-pack hint, scaffold templates that match real-world layouts, per-package versioning visibility, drift detection, and a microsoft/apm-action umbrella that absorbs the CI orchestration so producers don't hand-roll it.

This issue closes two community discussions (#1322 — jKlaus, monorepo plugin marketplace structure; #1332 — R. Collet, GitHub Releases for monorepo with per-package versions) by describing the full producer workflow APM should support, what works in HEAD today, and what we'll add to close the gap.

Why now

Two real producers tried to ship multi-plugin marketplaces against apm 0.13 + the v0.14 surface and both stalled at the same step: they couldn't tell which repo shape APM expects, what apm pack should produce in a monorepo, whether to tag once or per-plugin, or how to wire a release workflow that publishes Claude and Codex artifacts in one go. None of these are missing engineering — they are missing surface, scaffolds, and CI primitives.

The win-condition this issue chases:

A producer with a working plugin (or a folder of plugins) runs apm init --shape=…, gets a complete project skeleton including a release workflow that uses microsoft/apm-action, and is shipping multi-format marketplace artifacts on the next git tag — without writing a single shell script and without naming any one assistant's CLI in the output.

Vendor-agnostic constraints (read first)

This issue is bounded by three non-negotiables drawn from APM's positioning:

  1. APM is vendor-agnostic. Producer-facing output must not name a single consumer CLI (copilot plugin install …, claude plugin marketplace add …, codex plugins install …). Consumers may be on Copilot CLI, Claude Code, Codex, Cursor, OpenCode, Windsurf, or anything that ships next month. The post-pack experience must point producers at a docs page that lists all consumer paths, not a single one.
  2. apm pack is a release-time tool. The dominant invocation site is CI/CD on a tag push — not local dev. Friction here multiplies: a missing --json parse or a forgotten --marketplace flag becomes a broken release.
  3. microsoft/apm-action is ours to evolve. (repo — v1.6.0 floats v1.) Anything that's pure CI orchestration (matrix, drift PR, release notes, sha256 sidecars, GH Step Summary) belongs in the action, not as new CLI flags. Anything that's a portable validator (lockstep version check, dirty-tree check) belongs in the CLI as a thin additive flag so it works in GitLab/ADO/local pre-commit too.

Every recommendation below was checked against these three constraints before being proposed.

What ships in HEAD today (verified against apm 0.13.0 + Copilot CLI 1.0.48-1)

Capability Status Source
marketplace.outputs: accepts map form {claude: {}, codex: {path: …}} ✅ Shipped (#1317) src/apm_cli/marketplace/yml_schema.py:502-559
List form outputs: [claude, codex] parses with deprecation warning ✅ Shipped (#1317) yml_schema.py:586-595
apm pack --marketplace=<list|all|none> filter ✅ Shipped (#1317) src/apm_cli/commands/pack.py:129-138
apm pack --marketplace-path FORMAT=PATH (repeatable) ✅ Shipped (#1317) pack.py:140-148
apm pack --json machine-readable to stdout ✅ Shipped (#1317) pack.py:150-152
apm pack --offline / --include-prerelease ✅ Shipped pack.py
--marketplace-output deprecated, auto-translates to --marketplace-path claude=PATH ✅ Shipped (#1317), removal tracked in #1318 pack.py:195-207
Per-package tag_pattern: "{name}-v{version}" for independent versions ✅ Shipped, undocumented yml_schema.py:91-109, tag_pattern.py:52-103
apm init --plugin and apm marketplace init exist as separate commands ✅ Shipped commands/init.py:46-65, commands/marketplace/init.py:18-32
Output formats supported by APM Two only: claude, codex marketplace/output_profiles.py:67-91
apm pack post-pack hint mentions Copilot/Claude/Codex CLIs ❌ Vendor-neutral today (good) pack.py:373-406
apm marketplace doctor checks format coverage / version alignment ❌ Neither commands/marketplace/doctor.py
apm-action passes --marketplace, --marketplace-path, --json, --offline to apm pack ❌ Hardcoded only -o, --format, --target, --archive apm-action/src/bundler.ts:182-192

The CLI primitives are already there. The producer journey on top of them is not.

The four repo shapes APM must serve

Today producers improvise. We should name the shapes, scaffold each one, and the rest of the issue then references them by name.

Shape When to use apm.yml signature What apm pack produces
single-plugin One plugin, one repo (most plugins on GitHub today) plugin: {…} + optional marketplace: build/<plugin>/ (bundle) + optional marketplace.json with one entry
marketplace-aggregator Curate third-party plugins; no source in this repo marketplace: only, all source: are remote owner/repo[:path] build/.claude-plugin/marketplace.json + per-format siblings
marketplace-monorepo Many first-party plugins shipped together (Zava, jKlaus, Collet) marketplace: + every source: ./plugins/<name> One marketplace.json per format, listing local entries
hybrid First-party plugins plus vendored third-party marketplace: mixing local ./ and remote owner/repo Same as monorepo, with mixed entries

Each shape gets:

  • A scaffold (apm init --shape=<name>).
  • A canonical layout (in docs).
  • A canonical release workflow (emitted by the scaffold, using microsoft/apm-action).
  • A clear answer to "what do I tag and when."

What's broken or missing today (G-codes)

Numbered for cross-reference. Each row says how it was verified.

Bucket 1 — Wrong defaults / misleading output

  • G1. Scaffold default is a placeholder, not the user's reality. apm marketplace init emits a single acme/example-package entry regardless of whether plugins/ exists with real subdirs. Verified by running it on an empty repo and on a plugins/foo/-shaped repo: identical output. Fix: detect plugins/ and seed the packages: list from it.
  • G2. Marketplace-only and monorepo shapes both produce a misleading "No plugin.json found" warning + an empty bundle directory. Verified by running apm pack on a marketplace-only apm.yml; warning at src/apm_cli/bundle/plugin_exporter.py:360. The bundle path itself is not invalid — the message frames it as a missing file. Promote to highest priority — every marketplace-only and monorepo author hits this on their first run.
  • G3. Post-pack hint already correct (vendor-neutral) but minimal. pack.py:373-406 only prints "Built marketplace.json [claude] (N package(s)) -> path". No pointer to docs explaining how consumers in any ecosystem install the result. Fix: keep the line, append one docs link. Never name a vendor CLI.

Bucket 2 — Discoverability of shipped features

  • G4. outputs: map form (Marketplace output UX: filter-driven CLI + map-based manifest + JSON output #1317) is the new default but apm marketplace init still writes the deprecated list form. Verified by inspecting marketplace/init_template.py:89-153. Fix: scaffold writes outputs: {claude: {}} map form so the next-step ("add codex: {}") is observable in the file.
  • G5. Per-package tag_pattern is supported in HEAD but appears nowhere in scaffold, docs, or doctor output. Verified at yml_schema.py:91-109. Producers literally cannot find it without reading source. Fix: scaffold a commented tag_pattern: "{name}-v{version}" line under each package; document under "Per-plugin versioning"; have doctor warn when a monorepo lacks any tag_pattern (lockstep is a choice, not the only choice).
  • G6. Only two output formats (claude, codex) are recognized; producers in the Cursor / OpenCode / Windsurf / Gemini ecosystems get an Unknown marketplace format error. Verified at output_profiles.py:67-91. Fix is a roadmap statement (which formats next, in what order), plus a public extension contract for community contributions.
  • G7. apm marketplace doctor is silent on the producer's two biggest concerns: (a) which formats are configured vs. which APM supports (format coverage), and (b) whether per-package versions match git tags / lockfile (version alignment in monorepo). Add both as checks.

Bucket 3 — Release-time orchestration

  • G8. microsoft/apm-action drops every interesting apm pack flag. Verified at apm-action/src/bundler.ts:182-192: hardcodes -o, --format, --target, --archive only. Never passes --marketplace, --marketplace-path, --json, --offline, --include-prerelease. Highest-leverage fix in this issue — ~10 LOC unlocks multi-format CI publishing for every consumer of the action.
  • G9. No portable validators in CLI for release-time invariants. Producers in CI need (a) "all packages share the same version" (lockstep mode) and (b) "tree is clean / committed marketplace.json matches what apm pack would generate now" (drift). Today they shell-script it. Add apm pack --check-versions and apm pack --check-clean as additive flags so the same checks work in GitLab/ADO/local pre-commit.
  • G10. No release-mode in microsoft/apm-action. Today producers wire 6-8 release steps by hand: install, validate versions, pack each format, sha256, drift PR, release notes, upload, GH Step Summary. Verified by reading Zava's release.yml (8 steps) vs the action's surface (covers ~3). Add mode: release umbrella that orchestrates them, consuming apm pack --json.
  • G11. Codex output requires a category: per package but the error fires at pack time, not at apm marketplace doctor. Verified by adding outputs: {codex: {}} to a project without categories: hard fail at pack. Fix: doctor warns; or init --shape=… writes a sensible Productivity default with a comment.

Proposed surface

A. Scaffold: one shape flag, four templates

Refactor the existing apm init surface (do not add a new top-level command):

apm init                                  # interactive: asks shape
apm init --shape=single-plugin            # current --plugin behavior
apm init --shape=marketplace-aggregator   # current --marketplace behavior, remote sources only
apm init --shape=marketplace-monorepo     # NEW: scaffolds plugins/ dir + per-package tag_pattern
apm init --shape=hybrid                   # marketplace + local + remote sources

Backwards compat: --plugin and --marketplace keep working, mapped to --shape=single-plugin / --shape=marketplace-aggregator, with a one-cycle deprecation note.

Every shape's scaffold writes:

  • apm.yml with outputs: {claude: {}} map form (so + codex: {} is one line away).
  • A commented tag_pattern: "{name}-v{version}" under each package.
  • .github/workflows/release.yml that calls microsoft/apm-action with mode: release (see C below) — never raw apm shell.

B. Vendor-neutral post-pack output

Replace the current minimal hint with an artifact catalog and a single docs pointer:

[+] Marketplace artifacts written:
    claude  -> .claude-plugin/marketplace.json   (3 packages)
    codex   -> .agents/plugins/marketplace.json  (3 packages)

[i] Consumer install patterns vary by AI assistant.
    See: https://apm.dev/docs/producer/publish-to-a-marketplace#consumers

This stays vendor-agnostic, scales to N formats with no string changes, and gives the docs site one URL it owns.

C. microsoft/apm-actionmode: release umbrella

Two tracks land in apm-action and replace per-repo shell:

C1. Pass-through fix (G8) — additive, no version bump. Action accepts the flags apm pack already understands:

- uses: microsoft/apm-action@v1
  with:
    pack: true
    marketplace: claude,codex          # → apm pack --marketplace=claude,codex
    marketplace-path: |                # → repeated --marketplace-path FORMAT=PATH
      claude=dist/claude.json
      codex=dist/codex.json
    offline: true                      # → apm pack --offline
    json-output: pack.json             # captures apm pack --json

C2. Release umbrella — mode: release. New input that orchestrates the canonical release flow:

- uses: microsoft/apm-action@v1
  with:
    mode: release
    pack-each: plugins/*                 # matrix-pack each subdir; fan-in marketplace.json
    validate-versions: lockstep|per-plugin
    drift-check: .claude-plugin/marketplace.json
    sha256-sidecars: true
    release-notes: from-changelog
    upload-to: github-release

Outputs:

packages: JSON array [{name, path, version, bundle, sha256}]
marketplace-drift: clean | dirty
artifacts-uploaded: N
step-summary: rendered to $GITHUB_STEP_SUMMARY automatically

Internally mode: release shells out to the same apm pack --json it would on its own, so any maintainer can drop down to the CLI at any step.

Open call: the mode: reorg is additive in 1.x; we don't need a v2 cut. (Maintainers, please confirm or veto.)

D. CLI additions — narrow, portable, useful outside CI

These are the only new CLI flags this issue proposes. Everything richer goes in apm-action.

  • apm pack --check-versions — fail when packages disagree (lockstep) or when any package's version doesn't match its tag_pattern against current HEAD tags.
  • apm pack --check-clean — fail when the working tree has uncommitted changes that would alter the generated marketplace.json. Works in pre-commit hooks and any CI.
  • apm marketplace doctor gains two rows:
    • Format coverage: "Configured: claude. Supported by APM: claude, codex. Consider adding codex: {} for broader reach."
    • Version alignment: in monorepo + per-package shapes, lists each package's version and the tag the tag_pattern would resolve to.

E. Versioning answer (closes #1332)

The mechanism is already in HEAD; we just have to surface and document it.

For a marketplace-monorepo shape, producers get two valid models:

  1. Lockstep (Zava's choice): every package shares one version; one vX.Y.Z tag per release. Default scaffold writes this.
  2. Per-package ("Expressing" a release for an internal mono-repo marketplace #1332's ask): each package has its own version; tagged independently using its tag_pattern: "{name}-v{version}". Scaffold can opt into this with apm init --shape=marketplace-monorepo --versioning=per-package.

For Copilot CLI / Claude consumers (which today consume HEAD-of-default-branch with no version pin), this is invisible — they see the latest committed state. For APM consumers (dependencies: in their own apm.yml), the per-package tag is the pin.

This needs a docs page (producer/versioning-strategies.md) and three lines in --check-versions output, not new code.

F. Bumping one plugin in a monorepo (closes part of #1322)

Verified empirically: bumping one plugin's apm.yml produces no change in the parent marketplace.json for local-source packages — the entry is {name, description, source: ./…} and stays a pointer. So:

  • For Copilot CLI / Claude consumers: commit the bumped plugin.json, push to default branch. Done. They see the new version on next marketplace update. No tag needed.
  • For APM consumers: tag <plugin>-vX.Y.Z with the per-package tag_pattern. No need to retag the whole monorepo.

apm marketplace doctor and --check-versions will both surface this so producers don't have to read source to find out.

Acceptance criteria

A v0.14/v0.15 release that closes this issue ships:

  • apm init --shape=<single-plugin|marketplace-aggregator|marketplace-monorepo|hybrid> (G4, A) with --plugin/--marketplace as deprecated aliases.
  • Scaffold writes outputs: map form with commented codex: {} and a commented tag_pattern: (G4, G5).
  • G2 (misleading "No plugin.json" warning + empty bundle dir on marketplace-only and monorepo shapes) fixed.
  • G3: post-pack hint enumerates artifacts + one docs URL, names no vendor CLI (B).
  • apm marketplace doctor adds format-coverage row and version-alignment row (G7, G11).
  • apm pack --check-versions and apm pack --check-clean (D, G9).
  • microsoft/apm-action passes through --marketplace, --marketplace-path, --json, --offline, --include-prerelease (G8, C1). This alone unblocks every CI producer today.
  • microsoft/apm-action ships mode: release orchestrating matrix-pack, drift, sha256, release notes, GH Step Summary (G10, C2).
  • Scaffolded release.yml uses microsoft/apm-action@v1 with mode: release, never raw apm shell.
  • Roadmap statement on next output formats (G6) with a documented contributor extension point.
  • Docs: new pages producer/repo-shapes.md, producer/versioning-strategies.md; updated publish-to-a-marketplace.md with the consumer-paths table (B).

Out of scope for this issue

  • Adding new output formats (cursor, opencode, windsurf, gemini) — separate roadmap item per format. This issue documents the gap and the extension point.
  • Marketplace-level federation / nested marketplaces.
  • Signing / provenance — tracked separately.
  • Anything that would name a single consumer CLI in producer-facing output.

Open escalations (maintainer call)

These are panel disagreements I'd like ratified before implementation begins:

  1. apm-action@v2 cut? All proposed changes are additive in 1.x. The DevX panel and python-architect both lean "stay in v1" given the recent v1.6 default-pin precedent. Veto if you want a clean cut.
  2. Drift-PR ownership. Three options for mode: release when marketplace.json drift is detected: (a) action embeds PR-creation logic, (b) action shells out to peter-evans/create-pull-request, (c) action emits the diff and exits non-zero, caller composes. Recommendation: (c) — least magic, easiest to audit.
  3. Authoritative Claude Code marketplace.json schema URL. yml_schema.py:725 references json.schemastore.org/claude-code-plugin-manifest.json; we have not fetched it. Worth confirming before we cite field names externally.
  4. Codex schema stability. APM hardcodes policy.installation=AVAILABLE and policy.authentication=ON_INSTALL (marketplace/output_mappers.py:236-241) without a public Codex spec to cite. Want sign-off from the Codex side before we lean on this in scaffolds.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cliCLI command surface, flags, help text (cross-cutting).area/docs-sitedocs/src/content (Starlight), README, doc generation.area/marketplacemarketplace.json schema, federation, authoring suite, source parity.priority/highShips in current or next milestonestatus/acceptedDirection approved, safe to start work.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).theme/portabilityOne manifest, every target. Multi-target deploy, marketplace, packaging, install.type/featureNew capability, new flag, new primitive.

    Type

    No type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions