From 6beb89cf42be54865cfd655f2d9a29ca0269bdbe Mon Sep 17 00:00:00 2001 From: Ian Pascoe Date: Sat, 16 May 2026 09:25:46 -0400 Subject: [PATCH 1/2] fix: package agent plugin under plugins root --- .agents/plugins/marketplace.json | 2 +- .changeset/fix-codex-plugin-install.md | 5 ++ .claude-plugin/marketplace.json | 13 ++- README.md | 10 ++- codex.mcp.json | 8 -- package.json | 2 +- packages/core/test/agent-plugins.test.ts | 77 ++++++++++-------- .../caplets/.claude-plugin}/plugin.json | 2 +- .../caplets/.codex-plugin}/plugin.json | 2 +- {assets => plugins/caplets/assets}/icon.png | Bin claude.mcp.json => plugins/caplets/mcp.json | 0 .../caplets/skills}/caplets/SKILL.md | 0 scripts/sync-plugin-versions.mjs | 5 +- 13 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 .changeset/fix-codex-plugin-install.md delete mode 100644 codex.mcp.json rename {.claude-plugin => plugins/caplets/.claude-plugin}/plugin.json (92%) rename {.codex-plugin => plugins/caplets/.codex-plugin}/plugin.json (96%) rename {assets => plugins/caplets/assets}/icon.png (100%) rename claude.mcp.json => plugins/caplets/mcp.json (100%) rename {skills => plugins/caplets/skills}/caplets/SKILL.md (100%) diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 3addd3ad..c0599d59 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -8,7 +8,7 @@ "name": "caplets", "source": { "source": "local", - "path": "./" + "path": "./plugins/caplets" }, "policy": { "installation": "AVAILABLE", diff --git a/.changeset/fix-codex-plugin-install.md b/.changeset/fix-codex-plugin-install.md new file mode 100644 index 00000000..a718ffca --- /dev/null +++ b/.changeset/fix-codex-plugin-install.md @@ -0,0 +1,5 @@ +--- +"caplets": patch +--- + +Fix Codex plugin marketplace installation by using the supported `plugins/caplets` local plugin source layout with bundled skills and shared MCP config. diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c0fc241a..536ede62 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,14 +1,21 @@ { "name": "caplets", "owner": { - "name": "Spirit-Led Software LLC" + "name": "Spirit-Led Software LLC", + "url": "https://github.com/spiritledsoftware" }, "plugins": [ { "name": "caplets", - "source": "./", + "source": "./plugins/caplets", "description": "Expose configured Caplets as progressive-disclosure tools in Claude Code.", - "category": "Developer Tools" + "category": "Developer Tools", + "author": { + "name": "Spirit Led Software LLC", + "url": "https://github.com/spiritledsoftware", + "email": "ianpascoe@spiritledsoftware.com" + }, + "homepage": "https://github.com/spiritledsoftware/caplets" } ] } diff --git a/README.md b/README.md index ca9fcf69..ef034378 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Caplets logo + Caplets logo

Caplets

@@ -81,9 +81,11 @@ your coding agent supports one. | OpenCode | Install [`@caplets/opencode`](packages/opencode/README.md) | Native `caplets_` tools and prompt guidance hooks | | Pi | Install [`@caplets/pi`](packages/pi/README.md) | Native `caplets_` tools with Pi prompt snippets/guidelines | -Codex and Claude Code plugins are plugin-native but MCP-backed. Their manifests live in -`.codex-plugin/` and `.claude-plugin/`; component files live at the plugin root so -marketplace installs can copy and resolve them correctly. +Codex and Claude Code plugins are plugin-native but MCP-backed. The installable plugin +lives under `plugins/caplets/`, with agent-specific manifests in `.codex-plugin/` and +`.claude-plugin/`, a shared `skills/` directory, and shared `mcp.json` config. The +repo-level `.agents/plugins/marketplace.json` and `.claude-plugin/marketplace.json` +files only advertise that installable plugin root. The Claude Code and Codex commands install from this GitHub repository through each agent's plugin marketplace flow; users do not need to clone the repository manually. Plugin MCP diff --git a/codex.mcp.json b/codex.mcp.json deleted file mode 100644 index e0473a17..00000000 --- a/codex.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "caplets": { - "command": "caplets", - "args": ["serve"], - "startup_timeout_sec": 20, - "tool_timeout_sec": 120 - } -} diff --git a/package.json b/package.json index 84666f19..5dd8dd90 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "typecheck": "pnpm -r --filter './packages/*' typecheck", "test": "pnpm -r --filter './packages/*' test", "verify": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm schema:check && pnpm test && pnpm benchmark:check && pnpm build", - "version-packages": "changeset version && node scripts/sync-plugin-versions.mjs && oxfmt .codex-plugin/plugin.json .claude-plugin/plugin.json" + "version-packages": "changeset version && node scripts/sync-plugin-versions.mjs && oxfmt plugins/caplets/.codex-plugin/plugin.json plugins/caplets/.claude-plugin/plugin.json" }, "devDependencies": { "@changesets/cli": "^2.31.0", diff --git a/packages/core/test/agent-plugins.test.ts b/packages/core/test/agent-plugins.test.ts index 59ac9b09..c0b3dabc 100644 --- a/packages/core/test/agent-plugins.test.ts +++ b/packages/core/test/agent-plugins.test.ts @@ -1,4 +1,4 @@ -import { existsSync } from "node:fs"; +import { existsSync, lstatSync } from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -13,21 +13,21 @@ async function readJson(filePath: string): Promise { describe("root agent plugin artifacts", () => { it("declares Codex plugin components using Codex-specific files", async () => { const manifest = await readJson>( - path.join(repoRoot, ".codex-plugin/plugin.json"), + path.join(repoRoot, "plugins/caplets/.codex-plugin/plugin.json"), ); expect(manifest.skills).toBe("./skills/"); - expect(manifest.mcpServers).toBe("./codex.mcp.json"); + expect(manifest.mcpServers).toBe("./mcp.json"); expect(manifest.hooks).toBeUndefined(); }); it("declares Claude Code plugin components using Claude-specific files", async () => { const manifest = await readJson>( - path.join(repoRoot, ".claude-plugin/plugin.json"), + path.join(repoRoot, "plugins/caplets/.claude-plugin/plugin.json"), ); expect(manifest.skills).toBe("./skills/"); - expect(manifest.mcpServers).toBe("./claude.mcp.json"); + expect(manifest.mcpServers).toBe("./mcp.json"); expect(manifest.hooks).toBeUndefined(); }); @@ -35,8 +35,12 @@ describe("root agent plugin artifacts", () => { const [rootPackage, cliPackage, codexManifest, claudeManifest] = await Promise.all([ readJson<{ scripts: Record }>(path.join(repoRoot, "package.json")), readJson<{ version: string }>(path.join(repoRoot, "packages/cli/package.json")), - readJson<{ version: string }>(path.join(repoRoot, ".codex-plugin/plugin.json")), - readJson<{ version: string }>(path.join(repoRoot, ".claude-plugin/plugin.json")), + readJson<{ version: string }>( + path.join(repoRoot, "plugins/caplets/.codex-plugin/plugin.json"), + ), + readJson<{ version: string }>( + path.join(repoRoot, "plugins/caplets/.claude-plugin/plugin.json"), + ), ]); expect(codexManifest.version).toBe(cliPackage.version); @@ -60,7 +64,7 @@ describe("root agent plugin artifacts", () => { expect(marketplace.plugins).toEqual([ expect.objectContaining({ name: "caplets", - source: "./", + source: "./plugins/caplets", }), ]); }); @@ -78,35 +82,28 @@ describe("root agent plugin artifacts", () => { expect(marketplace.plugins).toEqual([ expect.objectContaining({ name: "caplets", - source: { source: "local", path: "./" }, + source: { source: "local", path: "./plugins/caplets" }, }), ]); }); it("runs the globally installed Caplets CLI in both MCP configs", async () => { - const [codexMcp, claudeMcp] = await Promise.all([ - readJson<{ caplets: { command: string; args: string[] } }>( - path.join(repoRoot, "codex.mcp.json"), - ), - readJson<{ mcpServers: { caplets: { command: string; args: string[] } } }>( - path.join(repoRoot, "claude.mcp.json"), - ), - ]); + const mcp = await readJson<{ mcpServers: { caplets: { command: string; args: string[] } } }>( + path.join(repoRoot, "plugins/caplets/mcp.json"), + ); - expect(codexMcp.caplets).toMatchObject({ + expect(mcp.mcpServers.caplets).toEqual({ command: "caplets", args: ["serve"], }); - expect(claudeMcp.mcpServers.caplets).toEqual({ - command: "caplets", - args: ["serve"], - }); - expect(JSON.stringify(codexMcp)).not.toContain("caplets@"); - expect(JSON.stringify(claudeMcp)).not.toContain("caplets@"); + expect(JSON.stringify(mcp)).not.toContain("caplets@"); }); - it("uses a strong shared root skill for automatic selection", async () => { - const skill = await readFile(path.join(repoRoot, "skills/caplets/SKILL.md"), "utf8"); + it("uses a strong shared plugin skill for automatic selection", async () => { + const skill = await readFile( + path.join(repoRoot, "plugins/caplets/skills/caplets/SKILL.md"), + "utf8", + ); expect(skill).toContain("name: caplets"); expect(skill).toContain("when_to_use:"); @@ -119,35 +116,47 @@ describe("root agent plugin artifacts", () => { expect(skill).toContain("Skip this skill for normal local code edits"); }); + it("keeps the Codex marketplace source self-contained for plugin installs", () => { + const pluginRoot = path.join(repoRoot, "plugins/caplets"); + const skillDir = path.join(pluginRoot, "skills"); + + expect(lstatSync(pluginRoot).isDirectory()).toBe(true); + expect(lstatSync(skillDir).isDirectory()).toBe(true); + expect(lstatSync(skillDir).isSymbolicLink()).toBe(false); + expect(existsSync(path.join(pluginRoot, ".codex-plugin/plugin.json"))).toBe(true); + expect(existsSync(path.join(pluginRoot, "skills/caplets/SKILL.md"))).toBe(true); + expect(existsSync(path.join(pluginRoot, "mcp.json"))).toBe(true); + }); + it("keeps plugin metadata and components in documented locations", () => { for (const forbiddenPath of [ "packages/codex", "packages/claude-code", "packages/agent-plugin-shared", - "plugins", + ".codex-plugin", + ".codex-plugins", ".mcp.json", "hooks", - ".codex-plugin/.mcp.json", + ".claude-plugin/plugin.json", ".claude-plugin/.mcp.json", ".codex-plugin/hooks.json", ".claude-plugin/hooks.json", ".codex-plugin/hooks", ".claude-plugin/hooks", - ".codex-plugin/skills", ".claude-plugin/skills", ]) { expect(existsSync(path.join(repoRoot, forbiddenPath)), forbiddenPath).toBe(false); } for (const requiredPath of [ - ".codex-plugin/plugin.json", - ".claude-plugin/plugin.json", + "plugins/caplets/.codex-plugin/plugin.json", + "plugins/caplets/.claude-plugin/plugin.json", + "plugins/caplets/mcp.json", + "plugins/caplets/skills/caplets/SKILL.md", + "plugins/caplets/assets/icon.png", ".claude-plugin/marketplace.json", ".agents/plugins/marketplace.json", - "codex.mcp.json", - "claude.mcp.json", "scripts/sync-plugin-versions.mjs", - "skills/caplets/SKILL.md", ]) { expect(existsSync(path.join(repoRoot, requiredPath)), requiredPath).toBe(true); } diff --git a/.claude-plugin/plugin.json b/plugins/caplets/.claude-plugin/plugin.json similarity index 92% rename from .claude-plugin/plugin.json rename to plugins/caplets/.claude-plugin/plugin.json index d1e171c5..0aa82ffc 100644 --- a/.claude-plugin/plugin.json +++ b/plugins/caplets/.claude-plugin/plugin.json @@ -10,5 +10,5 @@ "license": "MIT", "keywords": ["caplets", "mcp", "claude-code", "tools"], "skills": "./skills/", - "mcpServers": "./claude.mcp.json" + "mcpServers": "./mcp.json" } diff --git a/.codex-plugin/plugin.json b/plugins/caplets/.codex-plugin/plugin.json similarity index 96% rename from .codex-plugin/plugin.json rename to plugins/caplets/.codex-plugin/plugin.json index 2f2bec33..6b78c951 100644 --- a/.codex-plugin/plugin.json +++ b/plugins/caplets/.codex-plugin/plugin.json @@ -10,7 +10,7 @@ "license": "MIT", "keywords": ["caplets", "mcp", "codex", "tools"], "skills": "./skills/", - "mcpServers": "./codex.mcp.json", + "mcpServers": "./mcp.json", "interface": { "displayName": "Caplets", "shortDescription": "Progressive disclosure for Caplets tools.", diff --git a/assets/icon.png b/plugins/caplets/assets/icon.png similarity index 100% rename from assets/icon.png rename to plugins/caplets/assets/icon.png diff --git a/claude.mcp.json b/plugins/caplets/mcp.json similarity index 100% rename from claude.mcp.json rename to plugins/caplets/mcp.json diff --git a/skills/caplets/SKILL.md b/plugins/caplets/skills/caplets/SKILL.md similarity index 100% rename from skills/caplets/SKILL.md rename to plugins/caplets/skills/caplets/SKILL.md diff --git a/scripts/sync-plugin-versions.mjs b/scripts/sync-plugin-versions.mjs index b9fde03e..d0ac157f 100644 --- a/scripts/sync-plugin-versions.mjs +++ b/scripts/sync-plugin-versions.mjs @@ -10,7 +10,10 @@ async function readJson(filePath) { const cliPackage = await readJson("packages/cli/package.json"); -for (const manifestPath of [".codex-plugin/plugin.json", ".claude-plugin/plugin.json"]) { +for (const manifestPath of [ + "plugins/caplets/.codex-plugin/plugin.json", + "plugins/caplets/.claude-plugin/plugin.json", +]) { const manifest = await readJson(manifestPath); if (typeof manifest.version !== "string") { From dfaed318009dd75968319847ba112f73a2c7ec82 Mon Sep 17 00:00:00 2001 From: Ian Pascoe Date: Sun, 17 May 2026 06:55:38 -0400 Subject: [PATCH 2/2] fix: address PR review feedback --- .claude-plugin/marketplace.json | 2 +- packages/benchmarks/lib/config.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 536ede62..b90d2ff4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ "description": "Expose configured Caplets as progressive-disclosure tools in Claude Code.", "category": "Developer Tools", "author": { - "name": "Spirit Led Software LLC", + "name": "Spirit-Led Software LLC", "url": "https://github.com/spiritledsoftware", "email": "ianpascoe@spiritledsoftware.com" }, diff --git a/packages/benchmarks/lib/config.mjs b/packages/benchmarks/lib/config.mjs index 606001dc..e50485fc 100644 --- a/packages/benchmarks/lib/config.mjs +++ b/packages/benchmarks/lib/config.mjs @@ -12,7 +12,7 @@ export function getBenchmarkPaths({ repoRoot = REPO_ROOT } = {}) { return { repoRoot: absoluteRepoRoot, fixtureServerPath: join(absoluteRepoRoot, "fixtures", "mcp-server.mjs"), - capletsCliPath: resolve(absoluteRepoRoot, "../cli/dist/index.js"), + capletsCliPath: CAPLETS_CLI_PATH, }; }