From 088ac8e0eb93d4401ccf36352b9ef5a275286bbb Mon Sep 17 00:00:00 2001 From: Misha Milovidov <42074547+mishamilovidov@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:10:47 -0700 Subject: [PATCH 1/2] fix!: align generated marketplace names Use one generated marketplace identity across supported clients and cover Codex registration in project and global scopes. Document the native migration required by https://github.com/getsentry/dotagents/issues/175. The original Codex adapter introduced the divergent name in https://github.com/getsentry/dotagents/pull/113. --- CHANGELOG.md | 16 +++++++ README.md | 2 + docs/public/llms.txt | 2 + docs/src/content/docs/cli.mdx | 2 + docs/src/content/docs/guide.mdx | 6 +++ .../src/cli/commands/install.test.ts | 10 ++-- .../src/plugins/runtime/marketplace.ts | 14 +++--- .../src/plugins/runtime/writer.test.ts | 2 +- .../dotagents-qa/references/plugin-runtime.md | 8 ++-- .../references/release-plugin-matrix.md | 6 +-- skills/dotagents-qa/scripts/qa-example.mjs | 48 ++++++++++++++----- specs/SPEC.md | 2 + specs/plugins.md | 2 + 13 files changed, 90 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f71f2eda..adedceec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## Unreleased + +### Breaking Changes ⚠️ + +- Codex generated marketplaces now use `dotagents` instead of `dotagents-local`. + +Existing Codex installations keep the old marketplace and plugin identities. Complete this migration after the upgrade: + +1. Run `codex plugin remove @dotagents-local` for each installed dotagents plugin. +1. Run `codex plugin marketplace remove dotagents-local`. +1. Run dotagents `install` or `sync` for the applicable scope. +1. Run `codex plugin marketplace add `. +1. Run `codex plugin add @dotagents` for each plugin. + +Dotagents does not change native Codex marketplace registration or installed plugin state. + ## 3.0.1 ### Bug Fixes 🐛 diff --git a/README.md b/README.md index 877c42b6..f076e797 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,8 @@ The canonical portable format is an [Agent Plugins](https://agent-plugins.org/) Global plugins install canonical bundles under `~/.agents/plugins/`. Claude and Cursor marketplaces are generated under `~/.agents/`, the Codex marketplace is generated at `~/.agents/plugins/marketplace.json`, Grok plugins are copied into `~/.grok/plugins/`, OpenCode skills are linked into `~/.config/opencode/skills/`, portable MCP servers are merged into `~/.config/opencode/opencode.json`, and Pi skills are linked into `~/.agents/skills/`. `--user` remains a compatibility alias for `--global`. +All generated marketplaces use the name `dotagents`. Native plugin selectors use `@dotagents`. + Pi plugin targets are global skill projections rather than isolated plugin installs: a Pi-targeted plugin skill is added to `.agents/skills/` and is therefore visible to other clients that consume that shared directory. [Pi](https://github.com/badlogic/pi-mono) reads `.agents/skills/` natively. Normal skills need no Pi-specific configuration; plugin bundles can target `pi` when their `skills/` components should be exposed there. diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 25b13fe4..6f916cb2 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -309,6 +309,8 @@ Generated project-scope plugin outputs: - OpenCode: plugin `skills/` symlinked into `.opencode/skills/`; portable `mcp.json` servers merged into `.opencode/opencode.jsonc` under `plugin..` keys; generalized legacy plugin Markdown `agents/` symlinked into `.opencode/agents/`. Standard extension agents are preserved but not projected yet. - Pi: plugin `skills/` symlinked into `.agents/skills/` when `pi` is a configured plugin target +Every generated marketplace uses the name `dotagents`. Native plugin selectors use `@dotagents`. + Generated plugin JSON is deterministic: object keys and plugin entries are sorted, output is two-space indented, and files end with one trailing newline. Generated marketplaces and Claude/Cursor/Codex manifests use adjacent `.dotagents-managed` sidecars so client-owned JSON remains schema-native; legacy `metadata.managedBy` output remains recognizable during migration. Managed Grok copies and OpenCode/Pi component symlinks are pruned when their plugin or target is removed. Plugin sources that resolve to this project's `.agents/plugins//` install destination are rejected so dotagents never installs a same-repo plugin onto itself. Existing plugin install destinations are overwritten only when their on-disk `.dotagents-managed` marker proves ownership. Global plugins install under `~/.agents/plugins/`. Claude and Cursor marketplaces are generated below `~/.agents/`, Codex uses `~/.agents/plugins/marketplace.json` with paths rooted at the user's home, Grok plugins are copied into `~/.grok/plugins/`, OpenCode skills use `~/.config/opencode/skills/`, portable plugin MCP servers are merged into `~/.config/opencode/opencode.json`, and Pi skill projections use `~/.agents/skills/`. diff --git a/docs/src/content/docs/cli.mdx b/docs/src/content/docs/cli.mdx index 1b68ae56..79cda7a3 100644 --- a/docs/src/content/docs/cli.mdx +++ b/docs/src/content/docs/cli.mdx @@ -487,6 +487,8 @@ manifests, Grok plugin directories, OpenCode skill links and portable MCP entries, and Pi skill links. OpenCode MCP keys use `plugin..`; dotagents expands `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` and preserves unrelated entries in the shared config. +Every generated marketplace uses the name `dotagents`. Native plugin selectors +use `@dotagents`. Generalized legacy bundles can also project Markdown agents into OpenCode; standard extension agents are preserved but not projected yet. dotagents rejects plugin sources that resolve to the same project's diff --git a/docs/src/content/docs/guide.mdx b/docs/src/content/docs/guide.mdx index 82658fa6..57a86937 100644 --- a/docs/src/content/docs/guide.mdx +++ b/docs/src/content/docs/guide.mdx @@ -152,6 +152,12 @@ locations. Override the dotagents root with `DOTAGENTS_HOME`. `--global` selects global scope explicitly. `--user` remains a compatibility alias. +## Generated Marketplace Name + +Claude Code, Cursor, and Codex use `dotagents` for generated marketplaces. Native plugin selectors use `@dotagents`. + +Existing Codex installations keep the old marketplace identity after a dotagents upgrade. Remove each `@dotagents-local` plugin and the `dotagents-local` marketplace. Then run dotagents `install` or `sync`, register the project or global marketplace root again, and install each plugin as `@dotagents`. + ## Full Configuration Example diff --git a/packages/dotagents/src/cli/commands/install.test.ts b/packages/dotagents/src/cli/commands/install.test.ts index bb9bf2f8..c8c5242a 100644 --- a/packages/dotagents/src/cli/commands/install.test.ts +++ b/packages/dotagents/src/cli/commands/install.test.ts @@ -242,7 +242,7 @@ source = "path:plugin-source/review-tools" interface: { displayName: "Dotagents Plugins", }, - name: "dotagents-local", + name: "dotagents", owner: { name: "dotagents", }, @@ -437,7 +437,7 @@ source = "path:plugin-source/portable-tools" await expectHarnessFiles(projectRoot, [".agents/plugins/marketplace.json"], { ".agents/plugins/marketplace.json": { json: { interface: { displayName: "Dotagents Plugins" }, - name: "dotagents-local", + name: "dotagents", owner: { name: "dotagents" }, plugins: [{ category: "Productivity", @@ -1438,7 +1438,11 @@ source = "path:plugin-source/review-tools" expect(result.installedPlugins).toEqual(["review-tools"]); expect(existsSync(join(scope.pluginsDir, "review-tools", "plugin.json"))).toBe(true); expect(existsSync(join(scope.root, ".claude-plugin", "marketplace.json"))).toBe(true); - expect(existsSync(join(scope.root, ".agents", "plugins", "marketplace.json"))).toBe(true); + const codexMarketplace = JSON.parse(await readFile( + join(scope.root, ".agents", "plugins", "marketplace.json"), + "utf-8", + )); + expect(codexMarketplace["name"]).toBe("dotagents"); expect(await readlink(join(scope.skillsDir, "review"))).toBe("../plugins/review-tools/skills/review"); expect(await readlink(join(userHome, ".config", "opencode", "skills", "review"))).toContain( join("user-agents", "plugins", "review-tools", "skills", "review"), diff --git a/packages/dotagents/src/plugins/runtime/marketplace.ts b/packages/dotagents/src/plugins/runtime/marketplace.ts index fb6c1752..6c5a906b 100644 --- a/packages/dotagents/src/plugins/runtime/marketplace.ts +++ b/packages/dotagents/src/plugins/runtime/marketplace.ts @@ -7,6 +7,8 @@ import type { RuntimeOutput } from "./types.js"; import { normalizePluginRuntimeLayout, type PluginRuntimeRoot } from "./layout.js"; import type { SerializedObject } from "@sentry/dotagents-lib"; +const GENERATED_MARKETPLACE_NAME = "dotagents"; + /** Lists managed plugin marketplace files that may be generated or pruned. */ export function marketplaceOutputPaths(root: PluginRuntimeRoot): string[] { const layout = normalizePluginRuntimeLayout(root); @@ -36,7 +38,7 @@ export function marketplaceOutputs( outputs.push({ agent: "claude", filePath, - content: stableJson(pathMarketplace(layout.claudeMarketplaceRoot, "dotagents", claudePlugins)), + content: stableJson(pathMarketplace(layout.claudeMarketplaceRoot, claudePlugins)), }); } if (cursorPlugins.length > 0) { @@ -44,7 +46,7 @@ export function marketplaceOutputs( outputs.push({ agent: "cursor", filePath, - content: stableJson(pathMarketplace(layout.cursorMarketplaceRoot, "dotagents", cursorPlugins)), + content: stableJson(pathMarketplace(layout.cursorMarketplaceRoot, cursorPlugins)), }); } if (codexPlugins.length > 0) { @@ -52,7 +54,7 @@ export function marketplaceOutputs( outputs.push({ agent: "codex", filePath, - content: stableJson(codexMarketplace(layout.codexMarketplaceRoot, "dotagents-local", codexPlugins)), + content: stableJson(codexMarketplace(layout.codexMarketplaceRoot, codexPlugins)), }); } @@ -61,12 +63,11 @@ export function marketplaceOutputs( function pathMarketplace( projectRoot: string, - name: string, plugins: PluginDeclaration[], ): SerializedObject { return { description: "Generated by dotagents", - name, + name: GENERATED_MARKETPLACE_NAME, owner: { name: "dotagents", }, @@ -97,14 +98,13 @@ function pathMarketplaceEntry( function codexMarketplace( projectRoot: string, - name: string, plugins: PluginDeclaration[], ): SerializedObject { return { interface: { displayName: "Dotagents Plugins", }, - name, + name: GENERATED_MARKETPLACE_NAME, owner: { name: "dotagents", }, diff --git a/packages/dotagents/src/plugins/runtime/writer.test.ts b/packages/dotagents/src/plugins/runtime/writer.test.ts index 2a47e059..b3cd7030 100644 --- a/packages/dotagents/src/plugins/runtime/writer.test.ts +++ b/packages/dotagents/src/plugins/runtime/writer.test.ts @@ -124,7 +124,7 @@ describe("plugin writer", () => { interface: { displayName: "Dotagents Plugins", }, - name: "dotagents-local", + name: "dotagents", owner: { name: "dotagents", }, diff --git a/skills/dotagents-qa/references/plugin-runtime.md b/skills/dotagents-qa/references/plugin-runtime.md index 2d8e9005..99d54b25 100644 --- a/skills/dotagents-qa/references/plugin-runtime.md +++ b/skills/dotagents-qa/references/plugin-runtime.md @@ -55,15 +55,15 @@ export CODEX_HOME="$TMP/codex-home" mkdir -p "$CODEX_HOME" codex plugin marketplace add "$PROJECT" --json codex plugin list --available --json -codex plugin add qa-tools@dotagents-local --json +codex plugin add qa-tools@dotagents --json codex plugin list --json ``` Expected evidence: -- Marketplace add returns `dotagents-local` -- Available list includes `qa-tools@dotagents-local` -- Install returns `qa-tools@dotagents-local` +- Marketplace add returns `dotagents` +- Available list includes `qa-tools@dotagents` +- Install returns `qa-tools@dotagents` - Installed list shows the plugin enabled Manual final check with model auth: diff --git a/skills/dotagents-qa/references/release-plugin-matrix.md b/skills/dotagents-qa/references/release-plugin-matrix.md index d156614c..56c16c32 100644 --- a/skills/dotagents-qa/references/release-plugin-matrix.md +++ b/skills/dotagents-qa/references/release-plugin-matrix.md @@ -53,8 +53,8 @@ Validation and component inventory are no-auth proof, not model invocation. export CODEX_HOME=/sandbox/codex-home mkdir -p "$CODEX_HOME" codex plugin marketplace add ./ --json -codex plugin list --marketplace dotagents-local --available --json -codex plugin add @dotagents-local --json +codex plugin list --marketplace dotagents --available --json +codex plugin add @dotagents --json codex plugin list --json ``` @@ -117,7 +117,7 @@ claude plugin install @dotagents --scope user cd "$HOME" codex plugin marketplace add ./ --json -codex plugin add @dotagents-local --json +codex plugin add @dotagents --json cd /sandbox/neutral-project opencode debug skill diff --git a/skills/dotagents-qa/scripts/qa-example.mjs b/skills/dotagents-qa/scripts/qa-example.mjs index d25e4d26..2f8ce046 100644 --- a/skills/dotagents-qa/scripts/qa-example.mjs +++ b/skills/dotagents-qa/scripts/qa-example.mjs @@ -116,7 +116,7 @@ Tasks: install-files Install the full example and assert generated files sync-repair Delete representative generated files and assert sync repairs them plugin-claude Validate generated Claude plugin and marketplace with Claude Code - plugin-codex Add/list/install generated Codex marketplace with Codex CLI + plugin-codex Add/list/install project and global Codex marketplaces plugin-grok Confirm Grok Build discovers the generated project plugin opencode-projections Assert generated OpenCode resource projections plugin-clients Run every installed no-auth plugin client proof @@ -239,23 +239,47 @@ async function runGrokPluginProof() { async function runCodexPluginProof() { prepareClientHarness("codex"); + runCodexPluginLifecycle(projectDir, "project"); + + rmSync(dotagentsHomeDir, { recursive: true, force: true }); + mkdirSync(join(dotagentsHomeDir, "plugin-source"), { recursive: true }); + cpSync( + join(projectDir, "local-plugins", "qa-tools"), + join(dotagentsHomeDir, "plugin-source", "qa-tools"), + { recursive: true }, + ); + writeFileSync( + join(dotagentsHomeDir, "agents.toml"), + `version = 1 +agents = ["codex"] + +[[plugins]] +name = "qa-tools" +source = "path:plugin-source/qa-tools" +`, + ); + runCli(["install"]); + runCodexPluginLifecycle(dotagentsHomeDir, "global"); +} + +function runCodexPluginLifecycle(marketplaceRoot, scope) { rmSync(codexHomeDir, { recursive: true, force: true }); mkdirSync(codexHomeDir, { recursive: true }); const env = { ...fixtureEnv, CODEX_HOME: codexHomeDir }; - const add = execJson("codex", ["plugin", "marketplace", "add", projectDir, "--json"], env); - if (add.marketplaceName !== "dotagents-local") { - throw new Error("Codex marketplace add did not return dotagents-local"); + const add = execJson("codex", ["plugin", "marketplace", "add", marketplaceRoot, "--json"], env); + if (add.marketplaceName !== "dotagents") { + throw new Error(`Codex ${scope} marketplace add did not return dotagents`); } const available = execJson("codex", ["plugin", "list", "--available", "--json"], env); - if (!available.available?.some((plugin) => plugin.pluginId === "qa-tools@dotagents-local")) { - throw new Error("Codex available plugin list did not include qa-tools@dotagents-local"); + if (!available.available?.some((plugin) => plugin.pluginId === "qa-tools@dotagents")) { + throw new Error(`Codex ${scope} available plugin list did not include qa-tools@dotagents`); } - const installed = execJson("codex", ["plugin", "add", "qa-tools@dotagents-local", "--json"], env); - if (installed.pluginId !== "qa-tools@dotagents-local") { - throw new Error("Codex plugin add did not install qa-tools@dotagents-local"); + const installed = execJson("codex", ["plugin", "add", "qa-tools@dotagents", "--json"], env); + if (installed.pluginId !== "qa-tools@dotagents") { + throw new Error(`Codex ${scope} plugin add did not install qa-tools@dotagents`); } const installedMcp = JSON.parse(readFileSync(join(installed.installedPath, "mcp.json"), "utf-8")); const installedMcpNames = Object.keys(installedMcp.mcpServers ?? {}).toSorted(); @@ -264,8 +288,8 @@ async function runCodexPluginProof() { } const list = execJson("codex", ["plugin", "list", "--json"], env); - if (!list.installed?.some((plugin) => plugin.pluginId === "qa-tools@dotagents-local" && plugin.enabled === true)) { - throw new Error("Codex installed plugin list did not include enabled qa-tools@dotagents-local"); + if (!list.installed?.some((plugin) => plugin.pluginId === "qa-tools@dotagents" && plugin.enabled === true)) { + throw new Error(`Codex ${scope} installed plugin list did not include enabled qa-tools@dotagents`); } } @@ -495,7 +519,7 @@ function assertPluginOutputs() { assertFileIncludes("agents.lock", "qa-tools"); assertFile(".agents/plugins/marketplace.json"); assertFile(".agents/plugins/marketplace.json.dotagents-managed"); - assertFileIncludes(".agents/plugins/marketplace.json", '"name": "dotagents-local"'); + assertFileIncludes(".agents/plugins/marketplace.json", '"name": "dotagents"'); assertFileExcludes(".agents/plugins/marketplace.json", '"managedBy"'); assertFileIncludes(".agents/plugins/marketplace.json", '"path": "./.agents/plugins/qa-tools"'); assertFileIncludes(".agents/plugins/marketplace.json", '"installation": "AVAILABLE"'); diff --git a/specs/SPEC.md b/specs/SPEC.md index 96a65354..9b331bd9 100644 --- a/specs/SPEC.md +++ b/specs/SPEC.md @@ -267,6 +267,8 @@ compatibility implementation (see the remaining gaps in `specs/plugins.md`): | OpenCode | Plugin `skills/` symlinked into `.opencode/skills/`; portable `mcp.json` servers merged into `.opencode/opencode.jsonc` under `plugin..` keys; generalized legacy plugin Markdown `agents/` symlinked into `.opencode/agents/`. Standard extension agents are preserved but not projected yet. | | Pi | Plugin `skills/` symlinked into `.agents/skills/` when `pi` is a configured plugin target | +Every generated marketplace MUST use the name `dotagents`. Native plugin selectors use `@dotagents`. + Generated plugin JSON is stable: keys are sorted, plugin entries are sorted by name, and files end with one trailing newline. Generated marketplaces and Claude/Cursor/Codex manifests use adjacent `.dotagents-managed` sidecars; OpenCode/Pi component symlinks use marker files in reserved sibling `.dotagents-managed/` directories. This keeps ownership explicit without changing client-owned JSON or consuming a valid component name. Legacy `metadata.managedBy` output remains recognizable during migration. Managed Grok copies and component symlinks are pruned when their plugin or target is removed. Plugin sources that resolve to this project's `.agents/plugins//` install destination are rejected so dotagents never installs a same-repo plugin onto itself. Existing plugin install destinations are overwritten only when their on-disk `.dotagents-managed` marker proves ownership. Global scope installs canonical plugins into `~/.agents/plugins//`. It generates Claude and Cursor marketplaces below `~/.agents/`, a Codex marketplace at `~/.agents/plugins/marketplace.json` whose local paths are rooted at the user's home, OpenCode skill and legacy-agent projections below `~/.config/opencode/`, portable plugin MCP entries in `~/.config/opencode/opencode.json`, and Pi skill projections below `~/.agents/skills/`. diff --git a/specs/plugins.md b/specs/plugins.md index fc322a3b..60f8d39d 100644 --- a/specs/plugins.md +++ b/specs/plugins.md @@ -433,6 +433,8 @@ Agent Plugin bundle | OpenCode | Project plugin skills and merge normalized MCP servers into OpenCode config when needed | Read only namespaces registered to the OpenCode adapter | Symlink skills into `.opencode/skills/`; generalized legacy bundles may project Markdown agents, while standard extension resources are preserved but not projected yet; do not generate JavaScript or TypeScript plugin modules. | | Pi | Project supported skills | Read only namespaces registered to the Pi adapter | Symlink skills into `.agents/skills/`; ignore unsupported MCP or extension components with warnings. | +Every generated marketplace uses the name `dotagents`. Native plugin selectors use `@dotagents`. + For Claude, Cursor, and Codex, a retained matching native fallback replaces the generated-manifest step for that target only. A reproducible authored manifest does not. For Grok, hybrid compatibility copies exclude `.claude-plugin`, From 11890fd22cde1b0e649192683eb4e7d6d485074e Mon Sep 17 00:00:00 2001 From: Misha Milovidov <42074547+mishamilovidov@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:42:46 -0700 Subject: [PATCH 2/2] fix(copilot): correct pathMarketplace call arity The Copilot marketplace generation call site retained a stale 3-argument form from an earlier merge with main; pathMarketplace only takes (projectRoot, plugins) now that the marketplace name is sourced from the shared GENERATED_MARKETPLACE_NAME constant. Drop the redundant "dotagents" argument so this matches the Claude and Cursor call sites. --- packages/dotagents/src/plugins/runtime/marketplace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dotagents/src/plugins/runtime/marketplace.ts b/packages/dotagents/src/plugins/runtime/marketplace.ts index 22d22ca7..978b48a9 100644 --- a/packages/dotagents/src/plugins/runtime/marketplace.ts +++ b/packages/dotagents/src/plugins/runtime/marketplace.ts @@ -78,7 +78,7 @@ export function marketplaceOutputs( outputs.push({ agent: "copilot", filePath, - content: stableJson(pathMarketplace(layout.copilotMarketplaceRoot, "dotagents", copilotPlugins)), + content: stableJson(pathMarketplace(layout.copilotMarketplaceRoot, copilotPlugins)), }); } if (cursorPlugins.length > 0) {