From d441d64246b8c7a851d0d8d9ce042a64e198c4a7 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Fri, 30 Oct 2026 15:54:46 +0900 Subject: [PATCH 1/2] fix(plugin-dev): declare skills.sh skills in generated Codex manifests The multi-format generator omitted `skills` from Codex manifests whenever the Claude manifest pointed at `./.agents/skills/`, so the skills of every skills.sh plugin never loaded under Codex. The Codex runtime accepts any `./`-relative path inside the plugin root and always loads root `skills/` as well; only the sample plugin-creator validator insists on `./skills/`. - Pass any valid in-root skills path through; from an array prefer the first non-root path, since Codex already auto-loads root `skills/`. - Declare `./skills/` explicitly for plugins that ship a root `skills/` dir without a manifest field. - Emit the `Skill` capability only when the Codex manifest declares `skills`, and drop the `Skill` fallback. - Update the generated Codex manifests (skills and capabilities only; the unrelated drift a full regeneration produces is left out). Closes #296 --- .../agent-browser/.codex-plugin/plugin.json | 3 +- plugins/ai-sdk/.codex-plugin/plugin.json | 3 +- plugins/antfu/.codex-plugin/plugin.json | 3 +- plugins/ast-grep/.codex-plugin/plugin.json | 3 +- plugins/axi/.codex-plugin/plugin.json | 3 +- plugins/better-auth/.codex-plugin/plugin.json | 2 +- plugins/chat-sdk/.codex-plugin/plugin.json | 3 +- .../.codex-plugin/plugin.json | 3 +- plugins/codegraph/.codex-plugin/plugin.json | 2 + plugins/cubic/.codex-plugin/plugin.json | 3 +- plugins/deno/.codex-plugin/plugin.json | 3 +- plugins/dev3000/.codex-plugin/plugin.json | 3 +- plugins/docus/.codex-plugin/plugin.json | 1 + plugins/edgeparse/.codex-plugin/plugin.json | 3 +- plugins/emulate/.codex-plugin/plugin.json | 3 +- plugins/eve/.codex-plugin/plugin.json | 3 +- plugins/evlog/.codex-plugin/plugin.json | 1 + plugins/fetch/.codex-plugin/plugin.json | 2 + plugins/firebase/.codex-plugin/plugin.json | 1 + plugins/gatekeeper/.codex-plugin/plugin.json | 4 +- plugins/genkit/.codex-plugin/plugin.json | 3 +- plugins/git-ai/.codex-plugin/plugin.json | 3 +- .../.codex-plugin/plugin.json | 3 +- plugins/graphify/.codex-plugin/plugin.json | 3 +- plugins/graphite/.codex-plugin/plugin.json | 3 +- plugins/greptile/.codex-plugin/plugin.json | 3 +- plugins/lavish/.codex-plugin/plugin.json | 3 +- plugins/markitdown/.codex-plugin/plugin.json | 2 + plugins/mastra/.codex-plugin/plugin.json | 2 +- plugins/mcp-dev/.codex-plugin/plugin.json | 3 +- plugins/next/.codex-plugin/plugin.json | 3 +- plugins/nostics/.codex-plugin/plugin.json | 3 +- plugins/nuxt-i18n/.codex-plugin/plugin.json | 3 +- plugins/nuxt-seo/.codex-plugin/plugin.json | 3 +- plugins/nuxt-ui/.codex-plugin/plugin.json | 1 + plugins/nuxt/.codex-plugin/plugin.json | 1 + plugins/orpc/.codex-plugin/plugin.json | 3 +- plugins/pinia/.codex-plugin/plugin.json | 3 +- .../playwright-cli/.codex-plugin/plugin.json | 3 +- .../please-plugins/.codex-plugin/plugin.json | 3 +- .../plugin-dev/scripts/multi-format.test.ts | 30 ++++++++- plugins/plugin-dev/scripts/multi-format.ts | 64 ++++++++++++------- plugins/pnpm/.codex-plugin/plugin.json | 3 +- plugins/port/.codex-plugin/plugin.json | 2 + plugins/portless/.codex-plugin/plugin.json | 3 +- plugins/portone/.codex-plugin/plugin.json | 1 + plugins/prisma/.codex-plugin/plugin.json | 1 + .../react-native/.codex-plugin/plugin.json | 3 +- plugins/react/.codex-plugin/plugin.json | 3 +- plugins/rtk/.codex-plugin/plugin.json | 3 +- plugins/shadcn-ui/.codex-plugin/plugin.json | 3 +- .../skill-optimizer/.codex-plugin/plugin.json | 3 +- .../skillopt-sleep/.codex-plugin/plugin.json | 3 +- plugins/slack-agent/.codex-plugin/plugin.json | 3 +- plugins/slidev/.codex-plugin/plugin.json | 3 +- plugins/supabase/.codex-plugin/plugin.json | 1 + plugins/tiptap/.codex-plugin/plugin.json | 3 +- .../tosspayments/.codex-plugin/plugin.json | 1 + plugins/tsdown/.codex-plugin/plugin.json | 3 +- plugins/turborepo/.codex-plugin/plugin.json | 3 +- plugins/unocss/.codex-plugin/plugin.json | 3 +- .../vercel-sandbox/.codex-plugin/plugin.json | 3 +- plugins/vinext/.codex-plugin/plugin.json | 3 +- plugins/vite/.codex-plugin/plugin.json | 3 +- plugins/vitepress/.codex-plugin/plugin.json | 3 +- plugins/vitest/.codex-plugin/plugin.json | 3 +- plugins/vue/.codex-plugin/plugin.json | 3 +- plugins/vueuse/.codex-plugin/plugin.json | 3 +- plugins/web-design/.codex-plugin/plugin.json | 3 +- plugins/wordpress/.codex-plugin/plugin.json | 3 +- .../workflow-sdk/.codex-plugin/plugin.json | 3 +- 71 files changed, 196 insertions(+), 82 deletions(-) diff --git a/plugins/agent-browser/.codex-plugin/plugin.json b/plugins/agent-browser/.codex-plugin/plugin.json index e19d8367..e823f74f 100644 --- a/plugins/agent-browser/.codex-plugin/plugin.json +++ b/plugins/agent-browser/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "browser", "automation", "agents" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/ai-sdk/.codex-plugin/plugin.json b/plugins/ai-sdk/.codex-plugin/plugin.json index 86b934d5..125c079d 100644 --- a/plugins/ai-sdk/.codex-plugin/plugin.json +++ b/plugins/ai-sdk/.codex-plugin/plugin.json @@ -27,5 +27,6 @@ "ai", "typescript", "llm" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/antfu/.codex-plugin/plugin.json b/plugins/antfu/.codex-plugin/plugin.json index acc81523..c7351534 100644 --- a/plugins/antfu/.codex-plugin/plugin.json +++ b/plugins/antfu/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "conventions", "eslint", "typescript" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/ast-grep/.codex-plugin/plugin.json b/plugins/ast-grep/.codex-plugin/plugin.json index 1324deab..f21e95c2 100644 --- a/plugins/ast-grep/.codex-plugin/plugin.json +++ b/plugins/ast-grep/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "structural-search", "pattern-matching", "refactoring" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/axi/.codex-plugin/plugin.json b/plugins/axi/.codex-plugin/plugin.json index 6b140e61..7faa6125 100644 --- a/plugins/axi/.codex-plugin/plugin.json +++ b/plugins/axi/.codex-plugin/plugin.json @@ -21,5 +21,6 @@ "license": "MIT", "keywords": [ "axi" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/better-auth/.codex-plugin/plugin.json b/plugins/better-auth/.codex-plugin/plugin.json index 6d3c2cd3..b21f110b 100644 --- a/plugins/better-auth/.codex-plugin/plugin.json +++ b/plugins/better-auth/.codex-plugin/plugin.json @@ -27,5 +27,5 @@ "auth", "typescript" ], - "skills": "./skills/" + "skills": "./.agents/skills/" } diff --git a/plugins/chat-sdk/.codex-plugin/plugin.json b/plugins/chat-sdk/.codex-plugin/plugin.json index 56be5f6e..b5409455 100644 --- a/plugins/chat-sdk/.codex-plugin/plugin.json +++ b/plugins/chat-sdk/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "slack", "teams", "discord" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/claude-md-management/.codex-plugin/plugin.json b/plugins/claude-md-management/.codex-plugin/plugin.json index 952f9d4b..08e32c41 100644 --- a/plugins/claude-md-management/.codex-plugin/plugin.json +++ b/plugins/claude-md-management/.codex-plugin/plugin.json @@ -17,5 +17,6 @@ "defaultPrompt": [ "Help me use Claude Md Management for my current task." ] - } + }, + "skills": "./skills/" } diff --git a/plugins/codegraph/.codex-plugin/plugin.json b/plugins/codegraph/.codex-plugin/plugin.json index ec995462..b2b3d5d6 100644 --- a/plugins/codegraph/.codex-plugin/plugin.json +++ b/plugins/codegraph/.codex-plugin/plugin.json @@ -13,6 +13,7 @@ "developerName": "Colby McHenry", "category": "Development", "capabilities": [ + "Skill", "Tool" ], "defaultPrompt": [ @@ -30,5 +31,6 @@ "semantic-search", "local-first" ], + "skills": "./skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/cubic/.codex-plugin/plugin.json b/plugins/cubic/.codex-plugin/plugin.json index 0a2a119d..88a2c903 100644 --- a/plugins/cubic/.codex-plugin/plugin.json +++ b/plugins/cubic/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "linting", "security", "bugs" - ] + ], + "skills": "./skills/" } diff --git a/plugins/deno/.codex-plugin/plugin.json b/plugins/deno/.codex-plugin/plugin.json index 74650b22..5404595d 100644 --- a/plugins/deno/.codex-plugin/plugin.json +++ b/plugins/deno/.codex-plugin/plugin.json @@ -33,5 +33,6 @@ "test-runner", "fresh", "deno-deploy" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/dev3000/.codex-plugin/plugin.json b/plugins/dev3000/.codex-plugin/plugin.json index 085d3418..751c6f8c 100644 --- a/plugins/dev3000/.codex-plugin/plugin.json +++ b/plugins/dev3000/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "web", "logs", "screenshots" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/docus/.codex-plugin/plugin.json b/plugins/docus/.codex-plugin/plugin.json index c7823c55..10637b06 100644 --- a/plugins/docus/.codex-plugin/plugin.json +++ b/plugins/docus/.codex-plugin/plugin.json @@ -31,5 +31,6 @@ "markdown", "nuxt-content" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/edgeparse/.codex-plugin/plugin.json b/plugins/edgeparse/.codex-plugin/plugin.json index 25fa50f1..2a7585f7 100644 --- a/plugins/edgeparse/.codex-plugin/plugin.json +++ b/plugins/edgeparse/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "parsing", "markdown", "rag" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/emulate/.codex-plugin/plugin.json b/plugins/emulate/.codex-plugin/plugin.json index ef3a9a9d..7e8a0a51 100644 --- a/plugins/emulate/.codex-plugin/plugin.json +++ b/plugins/emulate/.codex-plugin/plugin.json @@ -24,5 +24,6 @@ "api-emulation", "local-development", "testing" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/eve/.codex-plugin/plugin.json b/plugins/eve/.codex-plugin/plugin.json index e370b830..c93ff4eb 100644 --- a/plugins/eve/.codex-plugin/plugin.json +++ b/plugins/eve/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "backend", "vercel", "framework" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/evlog/.codex-plugin/plugin.json b/plugins/evlog/.codex-plugin/plugin.json index ac8cce99..de0dd340 100644 --- a/plugins/evlog/.codex-plugin/plugin.json +++ b/plugins/evlog/.codex-plugin/plugin.json @@ -33,5 +33,6 @@ "structured-errors", "typescript" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/fetch/.codex-plugin/plugin.json b/plugins/fetch/.codex-plugin/plugin.json index 3c4007c8..c5079957 100644 --- a/plugins/fetch/.codex-plugin/plugin.json +++ b/plugins/fetch/.codex-plugin/plugin.json @@ -13,6 +13,7 @@ "developerName": "fetch-mcp project", "category": "Tooling", "capabilities": [ + "Skill", "Tool" ], "defaultPrompt": [ @@ -30,5 +31,6 @@ "youtube", "mcp" ], + "skills": "./skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/firebase/.codex-plugin/plugin.json b/plugins/firebase/.codex-plugin/plugin.json index 42f10118..720ccf4f 100644 --- a/plugins/firebase/.codex-plugin/plugin.json +++ b/plugins/firebase/.codex-plugin/plugin.json @@ -33,5 +33,6 @@ "hosting", "functions" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/gatekeeper/.codex-plugin/plugin.json b/plugins/gatekeeper/.codex-plugin/plugin.json index 29e2c600..cfdc053b 100644 --- a/plugins/gatekeeper/.codex-plugin/plugin.json +++ b/plugins/gatekeeper/.codex-plugin/plugin.json @@ -12,9 +12,7 @@ "longDescription": "Two-layer security for Claude Code: auto-approve safe commands, AI-assisted review for the rest", "developerName": "Minsu Lee", "category": "Tooling", - "capabilities": [ - "Skill" - ], + "capabilities": [], "defaultPrompt": [ "Help me use Gatekeeper for my current task." ] diff --git a/plugins/genkit/.codex-plugin/plugin.json b/plugins/genkit/.codex-plugin/plugin.json index 0a5e92e0..ac742d40 100644 --- a/plugins/genkit/.codex-plugin/plugin.json +++ b/plugins/genkit/.codex-plugin/plugin.json @@ -36,5 +36,6 @@ "dart", "flutter", "python" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/git-ai/.codex-plugin/plugin.json b/plugins/git-ai/.codex-plugin/plugin.json index 970a5201..adc5adf7 100644 --- a/plugins/git-ai/.codex-plugin/plugin.json +++ b/plugins/git-ai/.codex-plugin/plugin.json @@ -24,5 +24,6 @@ "git", "ai", "code-tracking" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/google-workspace/.codex-plugin/plugin.json b/plugins/google-workspace/.codex-plugin/plugin.json index a747b31f..4d300835 100644 --- a/plugins/google-workspace/.codex-plugin/plugin.json +++ b/plugins/google-workspace/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "calendar", "docs", "sheets" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/graphify/.codex-plugin/plugin.json b/plugins/graphify/.codex-plugin/plugin.json index 02453b6f..1ad419bc 100644 --- a/plugins/graphify/.codex-plugin/plugin.json +++ b/plugins/graphify/.codex-plugin/plugin.json @@ -33,5 +33,6 @@ "community-detection", "obsidian", "neo4j" - ] + ], + "skills": "./skills/" } diff --git a/plugins/graphite/.codex-plugin/plugin.json b/plugins/graphite/.codex-plugin/plugin.json index a9bb7c6a..b22821a6 100644 --- a/plugins/graphite/.codex-plugin/plugin.json +++ b/plugins/graphite/.codex-plugin/plugin.json @@ -32,5 +32,6 @@ "github", "pull-request", "code-review" - ] + ], + "skills": "./skills/" } diff --git a/plugins/greptile/.codex-plugin/plugin.json b/plugins/greptile/.codex-plugin/plugin.json index 1d2a7302..6b655d39 100644 --- a/plugins/greptile/.codex-plugin/plugin.json +++ b/plugins/greptile/.codex-plugin/plugin.json @@ -23,5 +23,6 @@ "greptile", "code-review", "pull-request" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/lavish/.codex-plugin/plugin.json b/plugins/lavish/.codex-plugin/plugin.json index 0f7c13f8..d7b070fd 100644 --- a/plugins/lavish/.codex-plugin/plugin.json +++ b/plugins/lavish/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "artifacts", "review", "visualization" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/markitdown/.codex-plugin/plugin.json b/plugins/markitdown/.codex-plugin/plugin.json index 37031609..313cacf4 100644 --- a/plugins/markitdown/.codex-plugin/plugin.json +++ b/plugins/markitdown/.codex-plugin/plugin.json @@ -13,6 +13,7 @@ "developerName": "Microsoft", "category": "Tooling", "capabilities": [ + "Skill", "Tool" ], "defaultPrompt": [ @@ -32,5 +33,6 @@ "markdown", "conversion" ], + "skills": "./skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/mastra/.codex-plugin/plugin.json b/plugins/mastra/.codex-plugin/plugin.json index 3e45ede6..f4154b6e 100644 --- a/plugins/mastra/.codex-plugin/plugin.json +++ b/plugins/mastra/.codex-plugin/plugin.json @@ -28,6 +28,6 @@ "framework", "agent" ], - "skills": "./skills/", + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/mcp-dev/.codex-plugin/plugin.json b/plugins/mcp-dev/.codex-plugin/plugin.json index 85f6f66c..c1b7906f 100644 --- a/plugins/mcp-dev/.codex-plugin/plugin.json +++ b/plugins/mcp-dev/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "typescript", "python", "fastmcp" - ] + ], + "skills": "./skills/" } diff --git a/plugins/next/.codex-plugin/plugin.json b/plugins/next/.codex-plugin/plugin.json index f4c287b8..7574732c 100644 --- a/plugins/next/.codex-plugin/plugin.json +++ b/plugins/next/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "react", "ssr", "rsc" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/nostics/.codex-plugin/plugin.json b/plugins/nostics/.codex-plugin/plugin.json index 810f85ce..404ff976 100644 --- a/plugins/nostics/.codex-plugin/plugin.json +++ b/plugins/nostics/.codex-plugin/plugin.json @@ -27,5 +27,6 @@ "error-handling", "typescript", "javascript" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/nuxt-i18n/.codex-plugin/plugin.json b/plugins/nuxt-i18n/.codex-plugin/plugin.json index 96e6636a..d775b279 100644 --- a/plugins/nuxt-i18n/.codex-plugin/plugin.json +++ b/plugins/nuxt-i18n/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "internationalization", "vue-i18n", "localization" - ] + ], + "skills": "./skills/" } diff --git a/plugins/nuxt-seo/.codex-plugin/plugin.json b/plugins/nuxt-seo/.codex-plugin/plugin.json index c6571e5d..69a1932f 100644 --- a/plugins/nuxt-seo/.codex-plugin/plugin.json +++ b/plugins/nuxt-seo/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "sitemap", "og-image", "schema-org" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/nuxt-ui/.codex-plugin/plugin.json b/plugins/nuxt-ui/.codex-plugin/plugin.json index ddfb7462..66999563 100644 --- a/plugins/nuxt-ui/.codex-plugin/plugin.json +++ b/plugins/nuxt-ui/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "tailwind", "reka-ui" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/nuxt/.codex-plugin/plugin.json b/plugins/nuxt/.codex-plugin/plugin.json index 2201a9b3..5d89f705 100644 --- a/plugins/nuxt/.codex-plugin/plugin.json +++ b/plugins/nuxt/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "ssr", "antfu" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/orpc/.codex-plugin/plugin.json b/plugins/orpc/.codex-plugin/plugin.json index 36da8f4d..18b790be 100644 --- a/plugins/orpc/.codex-plugin/plugin.json +++ b/plugins/orpc/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "rpc", "openapi", "typescript" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/pinia/.codex-plugin/plugin.json b/plugins/pinia/.codex-plugin/plugin.json index 91b8962a..6982f0a7 100644 --- a/plugins/pinia/.codex-plugin/plugin.json +++ b/plugins/pinia/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "vue", "state-management", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/playwright-cli/.codex-plugin/plugin.json b/plugins/playwright-cli/.codex-plugin/plugin.json index 77020951..44ecec3c 100644 --- a/plugins/playwright-cli/.codex-plugin/plugin.json +++ b/plugins/playwright-cli/.codex-plugin/plugin.json @@ -31,5 +31,6 @@ "e2e", "screenshot", "web" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/please-plugins/.codex-plugin/plugin.json b/plugins/please-plugins/.codex-plugin/plugin.json index ed1f4469..c437423f 100644 --- a/plugins/please-plugins/.codex-plugin/plugin.json +++ b/plugins/please-plugins/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "marketplace", "auto-detect", "setup" - ] + ], + "skills": "./skills/" } diff --git a/plugins/plugin-dev/scripts/multi-format.test.ts b/plugins/plugin-dev/scripts/multi-format.test.ts index ca9276e5..7c36a3b2 100644 --- a/plugins/plugin-dev/scripts/multi-format.test.ts +++ b/plugins/plugin-dev/scripts/multi-format.test.ts @@ -41,7 +41,7 @@ describe("toCodexManifest", () => { expect(result.interface.websiteURL).toBe("https://example.com/demo") expect(result.interface.shortDescription).toBeTypeOf("string") expect(result.interface.longDescription).toBe(baseClaude.description) - expect(result.interface.capabilities).toContain("Skill") + expect(result.interface.capabilities).toEqual([]) expect(Array.isArray(result.interface.defaultPrompt)).toBe(true) expect(result.interface.defaultPrompt.length).toBeGreaterThan(0) }) @@ -140,10 +140,36 @@ describe("toCodexManifest", () => { expect(result.skills).toBe("./skills/") }) - test("omits skills when path is incompatible with Codex (e.g. .agents/skills/)", () => { + test("keeps ./.agents/skills/ (runtime accepts any in-root ./ path)", () => { const claude: ClaudePluginManifest = { ...baseClaude, skills: "./.agents/skills/" } const result = toCodexManifest(claude, undefined) + expect(result.skills).toBe("./.agents/skills/") + }) + + test("prefers the first non-root path from a skills array (root skills/ auto-loads)", () => { + const claude: ClaudePluginManifest = { ...baseClaude, skills: ["./skills/", "./.agents/skills/"] } + const result = toCodexManifest(claude, undefined) + expect(result.skills).toBe("./.agents/skills/") + }) + + test.each(["../x", "/abs/skills", "./", "./a/../b"])("omits invalid skills path %s", (bad) => { + const claude: ClaudePluginManifest = { ...baseClaude, skills: bad } + const result = toCodexManifest(claude, undefined) expect(result.skills).toBeUndefined() + expect(result.interface.capabilities).not.toContain("Skill") + }) + + test("declares ./skills/ when root skills dir exists but Claude manifest has no skills", () => { + const result = toCodexManifest(baseClaude, undefined, { hasRootSkillsDir: true }) + expect(result.skills).toBe("./skills/") + expect(result.interface.capabilities).toContain("Skill") + }) + + test("advertises Skill only when skills are declared", () => { + const withSkills = toCodexManifest({ ...baseClaude, skills: "./.agents/skills/" }, undefined) + expect(withSkills.interface.capabilities).toEqual(["Skill"]) + const hooksOnly = toCodexManifest({ ...baseClaude, hooks: "./hooks/hooks.json" }, undefined) + expect(hooksOnly.interface.capabilities).toEqual(["Interactive"]) }) }) diff --git a/plugins/plugin-dev/scripts/multi-format.ts b/plugins/plugin-dev/scripts/multi-format.ts index 45956de7..29b1cfcf 100644 --- a/plugins/plugin-dev/scripts/multi-format.ts +++ b/plugins/plugin-dev/scripts/multi-format.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs" import { dirname, join } from "node:path" /** @@ -148,18 +148,37 @@ function pickCategory(entry: MarketplaceEntry | undefined): string { return raw[0]!.toUpperCase() + raw.slice(1) } +const ROOT_SKILLS = "./skills/" + /** - * Codex expects `skills` as a single string path that normalises to "skills" - * (the validator literally requires `./skills/` at plugin root). Plugins that - * source skills from `./.agents/skills/` (Antfu convention) cannot satisfy - * Codex via the manifest field — return null so the caller omits it. In that - * case the skills won't auto-load under Codex, but the plugin still installs. + * Normalise one Claude skills path into Codex manifest form (`.//`), or + * null when Codex would reject it. The runtime accepts any `./`-relative path + * that stays inside the plugin root (no absolute, no `..`, not `./` alone). */ -function normalizeSkillsPath(value: string | string[]): string | null { - const first = typeof value === "string" ? value : (value[0] ?? null) - if (!first) return null - const trimmed = first.replace(/^\.\//, "").replace(/\/$/, "") - return trimmed === "skills" ? "./skills/" : null +function toCodexSkillsPath(raw: string): string | null { + const trimmed = raw.trim().replace(/^\.\//, "").replace(/\/+$/, "") + if (!trimmed || trimmed.startsWith("/")) return null + const segments = trimmed.split("/") + if (segments.some(seg => seg === "" || seg === "." || seg === "..")) return null + return `./${segments.join("/")}/` +} + +/** + * Pick the Codex `skills` value. Codex takes a single string and always + * auto-loads root `skills/` in addition to it, so from an array prefer the + * first valid non-root path (e.g. `./.agents/skills/`) and fall back to the + * root dir only when nothing else is listed. A plugin with no declared + * skills but a root `skills/` dir declares it explicitly so capabilities + * stay truthful. + */ +function normalizeSkillsPath(value: string | string[] | undefined, hasRootSkillsDir: boolean): string | null { + if (value === undefined || value === "" || (Array.isArray(value) && value.length === 0)) { + return hasRootSkillsDir ? ROOT_SKILLS : null + } + const valid = (Array.isArray(value) ? value : [value]) + .map(toCodexSkillsPath) + .filter((p): p is string => p !== null) + return valid.find(p => p !== ROOT_SKILLS) ?? valid[0] ?? null } function shortDescription(text: string): string { @@ -202,17 +221,17 @@ function pickAuthor(claude: ClaudePluginManifest): CodexAuthor { /** * Derive Codex `capabilities` from what the plugin actually provides: - * - `Skill` when the manifest references a skills directory + * - `Skill` when the generated Codex manifest declares `skills` * - `Tool` when the manifest references MCP servers * - `Interactive` when the plugin has hooks (sessions/commands respond to events) - * Always returns a non-empty array so the validator is satisfied. + * May be empty: the runtime field is a plain string list, so a plugin with + * none of these advertises nothing rather than a capability it lacks. */ -function deriveCapabilities(claude: ClaudePluginManifest): string[] { +function deriveCapabilities(claude: ClaudePluginManifest, hasSkills: boolean): string[] { const caps: string[] = [] - if (claude.skills) caps.push("Skill") + if (hasSkills) caps.push("Skill") if (extractMcpServersFile(claude) !== null || typeof claude.mcpServers === "string") caps.push("Tool") if (claude.hooks || claude.commands) caps.push("Interactive") - if (caps.length === 0) caps.push("Skill") return caps } @@ -239,6 +258,7 @@ function deriveDefaultPrompts(claude: ClaudePluginManifest, displayName: string) export function toCodexManifest( claude: ClaudePluginManifest, entry: MarketplaceEntry | undefined, + options: { hasRootSkillsDir?: boolean } = {}, ): CodexManifest { // Empty mcpServers ({}) is treated as "none" so we don't emit a manifest // field that points at a non-existent .mcp.json file (Codex validator @@ -253,6 +273,7 @@ export function toCodexManifest( const description = claude.description ?? entry?.description ?? claude.name const displayName = pickDisplayName(claude, entry) const author = pickAuthor(claude) + const skillsPath = normalizeSkillsPath(claude.skills, options.hasRootSkillsDir ?? false) const manifest: CodexManifest = { name: claude.name, @@ -265,7 +286,7 @@ export function toCodexManifest( longDescription: description, developerName: author.name, category: pickCategory(entry), - capabilities: deriveCapabilities(claude), + capabilities: deriveCapabilities(claude, skillsPath !== null), defaultPrompt: deriveDefaultPrompts(claude, displayName), }, } @@ -274,10 +295,7 @@ export function toCodexManifest( if (claude.repository) manifest.repository = claude.repository if (claude.license) manifest.license = claude.license if (claude.keywords && claude.keywords.length > 0) manifest.keywords = claude.keywords - if (claude.skills) { - const skillsPath = normalizeSkillsPath(claude.skills) - if (skillsPath) manifest.skills = skillsPath - } + if (skillsPath) manifest.skills = skillsPath if (hasInlineMcp || hasMcpString) manifest.mcpServers = hasInlineMcp ? "./.mcp.json" : (claude.mcpServers as string) if (isHttpsUrl(claude.homepage)) manifest.interface.websiteURL = claude.homepage @@ -553,7 +571,9 @@ export function generateForPlugin( // resolves `${CLAUDE_PLUGIN_ROOT}` via a documented back-compat alias, so the // shared Claude hook file works as-is. (A stray `.codex-plugin/hooks.json` from // an earlier generator is cleaned up if present.) - const codex = toCodexManifest(claude, marketplaceEntry) + const codex = toCodexManifest(claude, marketplaceEntry, { + hasRootSkillsDir: existsSync(join(pluginDir, "skills")) && statSync(join(pluginDir, "skills")).isDirectory(), + }) const codexPath = join(pluginDir, ".codex-plugin", "plugin.json") if (writeIfChanged(codexPath, stringify(codex))) result.written.push(codexPath) else result.skipped.push(codexPath) diff --git a/plugins/pnpm/.codex-plugin/plugin.json b/plugins/pnpm/.codex-plugin/plugin.json index 12185d6d..28ac0964 100644 --- a/plugins/pnpm/.codex-plugin/plugin.json +++ b/plugins/pnpm/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "package-manager", "monorepo", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/port/.codex-plugin/plugin.json b/plugins/port/.codex-plugin/plugin.json index 263a6d4d..6402747a 100644 --- a/plugins/port/.codex-plugin/plugin.json +++ b/plugins/port/.codex-plugin/plugin.json @@ -13,6 +13,7 @@ "developerName": "Port", "category": "Tooling", "capabilities": [ + "Skill", "Tool" ], "defaultPrompt": [ @@ -34,5 +35,6 @@ "scorecards", "mcp" ], + "skills": "./skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/portless/.codex-plugin/plugin.json b/plugins/portless/.codex-plugin/plugin.json index 669c504e..4b4aee93 100644 --- a/plugins/portless/.codex-plugin/plugin.json +++ b/plugins/portless/.codex-plugin/plugin.json @@ -24,5 +24,6 @@ "localhost", "dev-server", "proxy" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/portone/.codex-plugin/plugin.json b/plugins/portone/.codex-plugin/plugin.json index 6200108b..5fb942cd 100644 --- a/plugins/portone/.codex-plugin/plugin.json +++ b/plugins/portone/.codex-plugin/plugin.json @@ -24,6 +24,7 @@ "developerName": "PortOne", "category": "Productivity", "capabilities": [ + "Skill", "Interactive", "Write" ], diff --git a/plugins/prisma/.codex-plugin/plugin.json b/plugins/prisma/.codex-plugin/plugin.json index 04b5c241..43376914 100644 --- a/plugins/prisma/.codex-plugin/plugin.json +++ b/plugins/prisma/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "typescript", "migrations" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/react-native/.codex-plugin/plugin.json b/plugins/react-native/.codex-plugin/plugin.json index 122e6b33..795995da 100644 --- a/plugins/react-native/.codex-plugin/plugin.json +++ b/plugins/react-native/.codex-plugin/plugin.json @@ -24,5 +24,6 @@ "expo", "mobile", "performance" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/react/.codex-plugin/plugin.json b/plugins/react/.codex-plugin/plugin.json index caa46ded..1388d22c 100644 --- a/plugins/react/.codex-plugin/plugin.json +++ b/plugins/react/.codex-plugin/plugin.json @@ -25,5 +25,6 @@ "vercel", "performance", "view-transitions" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/rtk/.codex-plugin/plugin.json b/plugins/rtk/.codex-plugin/plugin.json index 63f5d696..aaba35f9 100644 --- a/plugins/rtk/.codex-plugin/plugin.json +++ b/plugins/rtk/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "performance", "cli-proxy", "compression" - ] + ], + "skills": "./skills/" } diff --git a/plugins/shadcn-ui/.codex-plugin/plugin.json b/plugins/shadcn-ui/.codex-plugin/plugin.json index 117cd94b..716c2d8d 100644 --- a/plugins/shadcn-ui/.codex-plugin/plugin.json +++ b/plugins/shadcn-ui/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "radix", "base-ui", "ui" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/skill-optimizer/.codex-plugin/plugin.json b/plugins/skill-optimizer/.codex-plugin/plugin.json index 61e1decc..665474fb 100644 --- a/plugins/skill-optimizer/.codex-plugin/plugin.json +++ b/plugins/skill-optimizer/.codex-plugin/plugin.json @@ -27,5 +27,6 @@ "evals", "optimization", "benchmark" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/skillopt-sleep/.codex-plugin/plugin.json b/plugins/skillopt-sleep/.codex-plugin/plugin.json index b2e133d2..5f4d1590 100644 --- a/plugins/skillopt-sleep/.codex-plugin/plugin.json +++ b/plugins/skillopt-sleep/.codex-plugin/plugin.json @@ -32,5 +32,6 @@ "skills", "continual-learning", "offline-optimization" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/slack-agent/.codex-plugin/plugin.json b/plugins/slack-agent/.codex-plugin/plugin.json index 16a4de39..09893150 100644 --- a/plugins/slack-agent/.codex-plugin/plugin.json +++ b/plugins/slack-agent/.codex-plugin/plugin.json @@ -23,5 +23,6 @@ "license": "Apache-2.0", "keywords": [ "slack-agent" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/slidev/.codex-plugin/plugin.json b/plugins/slidev/.codex-plugin/plugin.json index 67113b25..17ae62e3 100644 --- a/plugins/slidev/.codex-plugin/plugin.json +++ b/plugins/slidev/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "slides", "presentation", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/supabase/.codex-plugin/plugin.json b/plugins/supabase/.codex-plugin/plugin.json index 0c200ac4..92d03f2f 100644 --- a/plugins/supabase/.codex-plugin/plugin.json +++ b/plugins/supabase/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "database", "backend" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/tiptap/.codex-plugin/plugin.json b/plugins/tiptap/.codex-plugin/plugin.json index 3f309f4c..245b612f 100644 --- a/plugins/tiptap/.codex-plugin/plugin.json +++ b/plugins/tiptap/.codex-plugin/plugin.json @@ -23,5 +23,6 @@ "tiptap", "rich-text-editor", "prosemirror" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/tosspayments/.codex-plugin/plugin.json b/plugins/tosspayments/.codex-plugin/plugin.json index 44894b67..f16fae18 100644 --- a/plugins/tosspayments/.codex-plugin/plugin.json +++ b/plugins/tosspayments/.codex-plugin/plugin.json @@ -31,5 +31,6 @@ "korea", "mcp" ], + "skills": "./.agents/skills/", "mcpServers": "./.mcp.json" } diff --git a/plugins/tsdown/.codex-plugin/plugin.json b/plugins/tsdown/.codex-plugin/plugin.json index b8c64789..60675b23 100644 --- a/plugins/tsdown/.codex-plugin/plugin.json +++ b/plugins/tsdown/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "bundler", "typescript", "rolldown" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/turborepo/.codex-plugin/plugin.json b/plugins/turborepo/.codex-plugin/plugin.json index 88a80c84..21f988c3 100644 --- a/plugins/turborepo/.codex-plugin/plugin.json +++ b/plugins/turborepo/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "monorepo", "build-system", "vercel" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/unocss/.codex-plugin/plugin.json b/plugins/unocss/.codex-plugin/plugin.json index f9dc97ce..4c381585 100644 --- a/plugins/unocss/.codex-plugin/plugin.json +++ b/plugins/unocss/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "css", "atomic-css", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vercel-sandbox/.codex-plugin/plugin.json b/plugins/vercel-sandbox/.codex-plugin/plugin.json index b540cba6..a868bbbc 100644 --- a/plugins/vercel-sandbox/.codex-plugin/plugin.json +++ b/plugins/vercel-sandbox/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "microvm", "vercel", "isolated-execution" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vinext/.codex-plugin/plugin.json b/plugins/vinext/.codex-plugin/plugin.json index e434b552..42f45e2a 100644 --- a/plugins/vinext/.codex-plugin/plugin.json +++ b/plugins/vinext/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "vite", "migration", "cloudflare" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vite/.codex-plugin/plugin.json b/plugins/vite/.codex-plugin/plugin.json index 2eda1e3e..bb73dc7c 100644 --- a/plugins/vite/.codex-plugin/plugin.json +++ b/plugins/vite/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "build-tool", "bundler", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vitepress/.codex-plugin/plugin.json b/plugins/vitepress/.codex-plugin/plugin.json index dcfebb21..89c74853 100644 --- a/plugins/vitepress/.codex-plugin/plugin.json +++ b/plugins/vitepress/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "documentation", "static-site", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vitest/.codex-plugin/plugin.json b/plugins/vitest/.codex-plugin/plugin.json index 1825896c..9ade8f2f 100644 --- a/plugins/vitest/.codex-plugin/plugin.json +++ b/plugins/vitest/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "testing", "unit-test", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vue/.codex-plugin/plugin.json b/plugins/vue/.codex-plugin/plugin.json index 34430ee0..4606685a 100644 --- a/plugins/vue/.codex-plugin/plugin.json +++ b/plugins/vue/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "vue3", "composition-api", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/vueuse/.codex-plugin/plugin.json b/plugins/vueuse/.codex-plugin/plugin.json index 3033c037..62f548f3 100644 --- a/plugins/vueuse/.codex-plugin/plugin.json +++ b/plugins/vueuse/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "vue", "composables", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/web-design/.codex-plugin/plugin.json b/plugins/web-design/.codex-plugin/plugin.json index 10b09db9..0ce9d11d 100644 --- a/plugins/web-design/.codex-plugin/plugin.json +++ b/plugins/web-design/.codex-plugin/plugin.json @@ -28,5 +28,6 @@ "ui", "accessibility", "antfu" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/wordpress/.codex-plugin/plugin.json b/plugins/wordpress/.codex-plugin/plugin.json index 0856aee6..18de2956 100644 --- a/plugins/wordpress/.codex-plugin/plugin.json +++ b/plugins/wordpress/.codex-plugin/plugin.json @@ -26,5 +26,6 @@ "plugins", "gutenberg", "php" - ] + ], + "skills": "./.agents/skills/" } diff --git a/plugins/workflow-sdk/.codex-plugin/plugin.json b/plugins/workflow-sdk/.codex-plugin/plugin.json index 6d1b22f9..ae94cf55 100644 --- a/plugins/workflow-sdk/.codex-plugin/plugin.json +++ b/plugins/workflow-sdk/.codex-plugin/plugin.json @@ -29,5 +29,6 @@ "vercel", "typescript", "ai-agents" - ] + ], + "skills": "./.agents/skills/" } From afc7803fc0e3db3a38a76a13728dfb7db7c6e9c5 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Fri, 30 Oct 2026 17:07:50 +0900 Subject: [PATCH 2/2] fix(plugin-dev): reject backslash separators in Codex skills paths --- plugins/plugin-dev/scripts/multi-format.test.ts | 2 +- plugins/plugin-dev/scripts/multi-format.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/plugin-dev/scripts/multi-format.test.ts b/plugins/plugin-dev/scripts/multi-format.test.ts index 7c36a3b2..8951d656 100644 --- a/plugins/plugin-dev/scripts/multi-format.test.ts +++ b/plugins/plugin-dev/scripts/multi-format.test.ts @@ -152,7 +152,7 @@ describe("toCodexManifest", () => { expect(result.skills).toBe("./.agents/skills/") }) - test.each(["../x", "/abs/skills", "./", "./a/../b"])("omits invalid skills path %s", (bad) => { + test.each(["../x", "/abs/skills", "./", "./a/../b", "./foo\\..\\bar"])("omits invalid skills path %s", (bad) => { const claude: ClaudePluginManifest = { ...baseClaude, skills: bad } const result = toCodexManifest(claude, undefined) expect(result.skills).toBeUndefined() diff --git a/plugins/plugin-dev/scripts/multi-format.ts b/plugins/plugin-dev/scripts/multi-format.ts index 29b1cfcf..fab2fad2 100644 --- a/plugins/plugin-dev/scripts/multi-format.ts +++ b/plugins/plugin-dev/scripts/multi-format.ts @@ -153,9 +153,10 @@ const ROOT_SKILLS = "./skills/" /** * Normalise one Claude skills path into Codex manifest form (`.//`), or * null when Codex would reject it. The runtime accepts any `./`-relative path - * that stays inside the plugin root (no absolute, no `..`, not `./` alone). + * that stays inside the plugin root (no absolute, no `..`, no backslash separators, not `./` alone). */ function toCodexSkillsPath(raw: string): string | null { + if (raw.includes("\\")) return null const trimmed = raw.trim().replace(/^\.\//, "").replace(/\/+$/, "") if (!trimmed || trimmed.startsWith("/")) return null const segments = trimmed.split("/")