Skip to content

Commit c8826be

Browse files
feat: build one Agent Plugins package for several clients (#42)
* feat: build one Agent Plugins package for several clients The codex target builds an Agent Plugins package with one client profile and one marketplace, and the agent-plugins target builds packages with none. Codex, Copilot, VS Code and Cursor all read the format, so a plugin built once can serve them all, given each client's marketplace file and client-only files. Add `clients` to the agent-plugins target (ADR 0002): - `clients: { codex: {}, copilot: {} }` writes each client's marketplace: Codex .agents/plugins/marketplace.json, Copilot .github/plugin/marketplace.json. `clients.<client>.manifest` and `plugins.<name>.clients.<client>.entry` are deep-merged into them. The shared `entry` and target `manifest` are config errors on agent-plugins that name the client-scoped field instead. - Client-only kinds a listed client loads are selected by default and written to its extension directory: Copilot agents go to com.github.copilot/agents/, with .md, .markdown and .mdc renamed to NAME.agent.md. Any other file in agents/, or two files that land on the same path, fail the build. - A client-only kind that no listed client loads stays a config error, and the message names the client that would load it. A file of such a kind that arrives another way, such as additionalFiles, fails the build. Client names are checked strictly, a client set to undefined counts as not listed, and `clients` is rejected on other targets. - validate checks each client marketplace present: entries, including structured local Codex sources, must point at a package whose plugin.json has the same name. Copilot needs a marketplace owner, and its agents need NAME.agent.md names and valid frontmatter. - src/package-clients.ts holds each client's namespace, marketplace, file placement and validation, with citations to the client docs. - The engine gains two optional hooks, clientMarketplaces and placeFile. Other targets don't set them, and Glean's claude, cursor and codex output is byte-identical (191 files). Verified end to end with the Glean plugin: Copilot CLI 1.0.100 and Codex 0.160.1 both install it from their marketplace in the same output. * fix: route hooks through client placement and normalize additionalFiles paths With clients set, hooks/hooks.json from additionalFiles skipped the client-only guard and shipped at the package root. A target that defines placeFile now places hooks too. An additionalFiles destination starting with ./ could silently replace a placed file. Destinations are now normalized, and the collision check keys on the output path. Also: reword client-only errors to say pluginpack doesn't place the kind yet, test that metadata.owner wins over metadata.author for Copilot, and document the manifest and entry errors, Copilot agent checks, and new marketplace collisions in README, CLAUDE.md and the config skill. * test: cover path normalization in the engine and legacy sources
1 parent 9ca5066 commit c8826be

16 files changed

Lines changed: 1596 additions & 86 deletions

‎CLAUDE.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ diff, prune, and validate all derive from it.
8282

8383
`copilot`, `antigravity`, `cursor`, `claude`, `codex`, `agent-plugins`. `codex`
8484
and `agent-plugins` emit Agent Plugins packages through `src/agent-plugins.ts`
85-
(the package module; marketplaces stay on each target). Each target's MCP config
85+
(the package module). The `codex` target owns its marketplace. With `clients`,
86+
`agent-plugins` takes each client's marketplace, extension-directory placement
87+
and validation from `src/package-clients.ts`. Each target's MCP config
8688
is rendered by `src/mcp.ts` in the dialect its `mcpDialect` names. Adding a target means one
8789
new file implementing `PluginTargetDefinition` (`src/targets/<name>.ts`) plus one
8890
new entry in `src/targets/registry.ts` — `TargetName` (`types.ts`) is still a
@@ -105,8 +107,11 @@ schemas at runtime — vendor a pinned copy with recorded provenance.
105107
- **Recommended shape:** `shared/<plugin>/` canonical sources,
106108
`overrides/<target>/<plugin>/` target overrides, and
107109
`repositories/<target>/` generated-repository files.
108-
- **claude + copilot collide:** both write `.claude-plugin/marketplace.json`, so
109-
they need distinct `outDir`s. `build()` errors on overlapping output paths.
110+
- **Shared marketplace paths collide:** `claude` and `copilot` both write
111+
`.claude-plugin/marketplace.json`. `agent-plugins` with `clients.codex`
112+
writes the `codex` target's `.agents/plugins/marketplace.json`, and with
113+
`clients.copilot` it writes `.github/plugin/marketplace.json`. Such targets
114+
need distinct `outDir`s. `build()` errors on overlapping output paths.
110115
- **MCP:** a canonical source keeps `mcp/config.json`, its implementation, tests,
111116
and `mcp/pluginpack.json` shipping-file map together. Target adapters translate
112117
the config into the native output. Legacy sources still read `.mcp.json`.

‎CONFORMANCE.md‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ against a temp fixture via [`bintastic`](https://github.com/scalvert/bintastic).
4444
deliberately **not** used here: Copilot derives from the Claude marketplace but
4545
extends entries (e.g. `mcpServers` as a `.mcp.json` path), which Claude's stricter
4646
schema rejects. Because it shares the Claude marketplace path, the `claude` and
47-
`copilot` targets need separate output roots.
47+
`copilot` targets need separate output roots. An `agent-plugins` target with
48+
`clients.copilot` also writes `.github/plugin/marketplace.json`, so it needs an
49+
output root separate from the `copilot` target's.
4850
- **antigravity** — covered structurally by the cross-target build test in
4951
`tests/core.test.ts` (required `plugin.json` fields present; optional
5052
`mcp_config.json` written when MCP servers are present). Antigravity CLI does
@@ -55,7 +57,13 @@ against a temp fixture via [`bintastic`](https://github.com/scalvert/bintastic).
5557
validator (`src/agent-plugins.ts`) re-implements the closed-schema checks
5658
without ajv, plus the semantic rules a JSON Schema can't express: skill names
5759
per the Agent Skills specification and matching their directory, and MCP
58-
server rules from spec §7.2 and §9 (`src/mcp.ts`).
60+
server rules from spec §7.2 and §9 (`src/mcp.ts`). With `clients`, the same
61+
package oracle applies. The Codex marketplace uses the codex checks below,
62+
and the Copilot marketplace and `com.github.copilot/agents/` layout are
63+
asserted structurally against the
64+
[Copilot CLI plugin reference](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference)
65+
(retrieved 2026-10-20), which publishes no schema. Client facts and their
66+
citations live in `src/package-clients.ts`.
5967
- **codex** — the package half uses the Agent Plugins oracle above. The
6068
marketplace half is asserted structurally in `tests/conformance.test.ts` and
6169
`tests/core.test.ts` against the
@@ -74,8 +82,9 @@ against a temp fixture via [`bintastic`](https://github.com/scalvert/bintastic).
7482
added via `entry` is a structured object with an inner `source` discriminator
7583
(e.g. `{ source: "git-subdir", url, path, ref }`), validated by shape rather
7684
than requiring a local directory to exist. No published JSON Schema exists.
77-
Codex shares no marketplace path with the other targets, so it needs no
78-
separate output root.
85+
Among the native targets, Codex shares no marketplace path, but an
86+
`agent-plugins` target with `clients.codex` writes the same
87+
`.agents/plugins/marketplace.json`, so the two need separate output roots.
7988

8089
## Update-check hook facts
8190

‎CONTEXT.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ _Avoid_: portable plugin, AP plugin
3333
A client's additions to a package: its extension namespace, the manifest data under it, and the files in its extension directory.
3434
_Avoid_: overlay, flavor
3535

36+
**Package client**:
37+
A client listed in the `agent-plugins` target's `clients`. One package serves every package client; each also gets its own marketplace file.
38+
_Avoid_: sub-target, flavor
39+
3640
**Extension namespace**:
3741
The reverse-domain key a client owns inside a package, such as `com.openai`.
3842
_Avoid_: vendor key, client key

0 commit comments

Comments
 (0)