diff --git a/CHANGELOG.md b/CHANGELOG.md index 8816f33..4aafbcf 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.4.0 ### New Features ✨ diff --git a/README.md b/README.md index 1a9e534..30ba538 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,8 @@ Local marketplace entries may use `"."` or `"./"` for the source root. An entry Global plugins install canonical bundles under `~/.agents/plugins/`. Claude and Cursor marketplaces are generated under `~/.agents/`. Copilot uses `~/.agents/.github/plugin/marketplace.json`, and Codex uses `~/.agents/plugins/marketplace.json`. Grok plugins are copied into `~/.grok/plugins/`. OpenCode skills are linked into `~/.config/opencode/skills/`, and portable MCP servers are merged into `~/.config/opencode/opencode.json`. 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://pi.dev) 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 c9fe82b..7b064c2 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -320,6 +320,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, and 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 and 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/`. Copilot uses `~/.agents/.github/plugin/marketplace.json`. 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 use `~/.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 4abf136..1b11de4 100644 --- a/docs/src/content/docs/cli.mdx +++ b/docs/src/content/docs/cli.mdx @@ -512,7 +512,18 @@ reinterpreting generated adapters as source input. | `path` | string | No | Optional explicit plugin path inside the source. | | `targets` | string[] | No | Optional subset of configured agents. | -Generated plugin outputs include Claude, Cursor, Codex, and Copilot marketplaces, native Claude, Cursor, and Codex 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. 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 `.agents/plugins//` install destination. Global plugins use `~/.agents/plugins//` and global harness projections. +Generated plugin outputs include Claude, Cursor, Codex, and Copilot +marketplaces, native Claude, Cursor, and Codex manifests, Grok plugin +directories, OpenCode skill links and portable MCP entries, and Pi skill +links. Every generated marketplace uses the name `dotagents`. Native plugin +selectors use `@dotagents`. OpenCode MCP keys use +`plugin..`; dotagents expands `${PLUGIN_ROOT}` and +`${PLUGIN_DATA}` and preserves unrelated entries in the shared config. +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 +`.agents/plugins//` install destination. Global plugins use +`~/.agents/plugins//` and global harness projections. ## Scopes diff --git a/docs/src/content/docs/guide.mdx b/docs/src/content/docs/guide.mdx index 8542357..16f1b09 100644 --- a/docs/src/content/docs/guide.mdx +++ b/docs/src/content/docs/guide.mdx @@ -152,6 +152,12 @@ OpenCode, and Pi 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 74f6cb6..19a9c96 100644 --- a/packages/dotagents/src/cli/commands/install.test.ts +++ b/packages/dotagents/src/cli/commands/install.test.ts @@ -248,7 +248,7 @@ source = "path:plugin-source/review-tools" interface: { displayName: "Dotagents Plugins", }, - name: "dotagents-local", + name: "dotagents", owner: { name: "dotagents", }, @@ -447,7 +447,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", @@ -1549,7 +1549,11 @@ source = "path:plugin-source/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, ".github", "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 lstat(join(userHome, ".claude", "skills"))).isSymbolicLink()).toBe(true); expect(await readlink(join(userHome, ".claude", "skills"))).toBe( diff --git a/packages/dotagents/src/plugins/runtime/marketplace.ts b/packages/dotagents/src/plugins/runtime/marketplace.ts index 61679d8..6791217 100644 --- a/packages/dotagents/src/plugins/runtime/marketplace.ts +++ b/packages/dotagents/src/plugins/runtime/marketplace.ts @@ -7,6 +7,8 @@ import { legacyManifestString, manifestString, type SerializedObject } from "@se import type { RuntimeOutput } from "./types.js"; import { normalizePluginRuntimeLayout, type PluginRuntimeRoot } from "./layout.js"; +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); @@ -67,7 +69,7 @@ export function marketplaceOutputs( outputs.push({ agent: "claude", filePath, - content: stableJson(pathMarketplace(layout.claudeMarketplaceRoot, "dotagents", claudePlugins)), + content: stableJson(pathMarketplace(layout.claudeMarketplaceRoot, claudePlugins)), }); } if (copilotPlugins.length > 0) { @@ -75,7 +77,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) { @@ -83,7 +85,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) { @@ -91,7 +93,7 @@ export function marketplaceOutputs( outputs.push({ agent: "codex", filePath, - content: stableJson(codexMarketplace(layout.codexMarketplaceRoot, "dotagents-local", codexPlugins)), + content: stableJson(codexMarketplace(layout.codexMarketplaceRoot, codexPlugins)), }); } @@ -100,12 +102,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", }, @@ -136,14 +137,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 a439a7c..f7a9be2 100644 --- a/packages/dotagents/src/plugins/runtime/writer.test.ts +++ b/packages/dotagents/src/plugins/runtime/writer.test.ts @@ -126,7 +126,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 85d2475..88ae0bd 100644 --- a/skills/dotagents-qa/references/plugin-runtime.md +++ b/skills/dotagents-qa/references/plugin-runtime.md @@ -56,15 +56,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: @@ -84,7 +84,7 @@ Automated proof: node skills/dotagents-qa/scripts/qa-example.mjs plugin-copilot ``` -This command adds the generated marketplace to an isolated `COPILOT_HOME`. It installs and lists the plugin. Copilot 1.0.83 loads a local plugin from its marketplace directory without a copied cache. The proof uses native commands to inspect the live plugin, its skill, and both MCP servers. +This command adds the generated marketplace to an isolated `COPILOT_HOME`. It installs and lists the plugin. Copilot 1.0.83 and 1.0.88 load a local plugin from its marketplace directory without a copied cache. Version 1.0.88 returns an array from `plugins list --json` and reports the plugin source as `live`. The proof accepts both output formats and checks the marketplace, skill, and both MCP servers. ## OpenCode projection (not native plugin E2E) diff --git a/skills/dotagents-qa/references/release-plugin-matrix.md b/skills/dotagents-qa/references/release-plugin-matrix.md index 8ac9661..9d000a6 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 ``` @@ -135,7 +135,7 @@ copilot plugin install @dotagents 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 1fd8239..5531607 100644 --- a/skills/dotagents-qa/scripts/qa-example.mjs +++ b/skills/dotagents-qa/scripts/qa-example.mjs @@ -121,7 +121,7 @@ Tasks: sync-repair Delete representative generated files and assert sync repairs them plugin-claude Validate generated Claude plugin and marketplace with Claude Code plugin-copilot Add, install, and list the generated marketplace with Copilot CLI - 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 @@ -269,10 +269,12 @@ async function runCopilotPluginProof() { const realProjectDir = realpathSync(projectDir); const pluginRoot = realpathSync(join(projectDir, ".agents", "plugins", "qa-tools")); const pluginState = execJson("copilot", ["plugins", "list", "--json"], env); - const livePlugin = pluginState.plugins?.find((plugin) => plugin.name === "qa-tools"); + const plugins = Array.isArray(pluginState) ? pluginState : pluginState.plugins; + const livePlugin = plugins?.find((plugin) => plugin.name === "qa-tools"); if ( livePlugin?.enabled !== true - || livePlugin.source !== "live-marketplace:dotagents" + || !["live-marketplace:dotagents", "live"].includes(livePlugin.source) + || (livePlugin.source === "live" && livePlugin.marketplace !== "dotagents") || !isStringValue(livePlugin.installedFrom) || realpathSync(livePlugin.installedFrom) !== realProjectDir ) { @@ -345,23 +347,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(); @@ -370,8 +396,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`); } } @@ -610,7 +636,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 3ae7818..cfda989 100644 --- a/specs/SPEC.md +++ b/specs/SPEC.md @@ -273,6 +273,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, and Codex manifests use adjacent `.dotagents-managed` sidecars; OpenCode and 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 Copilot marketplace at `~/.agents/.github/plugin/marketplace.json`, and a Codex marketplace at `~/.agents/plugins/marketplace.json`. Codex local paths start at the user's home. OpenCode skill and legacy-agent projections use `~/.config/opencode/`. Portable plugin MCP entries use `~/.config/opencode/opencode.json`. Pi skill projections use `~/.agents/skills/`. diff --git a/specs/plugins.md b/specs/plugins.md index 583ce08..7a08507 100644 --- a/specs/plugins.md +++ b/specs/plugins.md @@ -446,6 +446,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`. + Copilot follows its native precedence for marketplaces (`marketplace.json`, `.plugin/marketplace.json`, then `.github/plugin/marketplace.json`) and plugin manifests (`.plugin`, root, `.github/plugin`, then `.claude-plugin`). Dotagents