Skip to content

Commit 94f318d

Browse files
committed
chore(plugin-dev): apply AI code review suggestions
- declare skills path in manifest so Codex exposes them (Greptile P1) - rename skill names to lowercase-hyphen matching directories (cubic P1) - correct $schema and Codex-marketplace-scope wording in reference (cubic P3) - extract helpers so generateMultiFormat stays under the function-length guideline (Gemini)
1 parent 95f1dd7 commit 94f318d

7 files changed

Lines changed: 69 additions & 51 deletions

File tree

plugins/plugin-dev/.claude-plugin/plugin.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@
1818
"validation",
1919
"guidelines",
2020
"scaffold"
21+
],
22+
"skills": [
23+
"./skills/"
2124
]
2225
}

plugins/plugin-dev/.codex-plugin/plugin.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@
3030
"validation",
3131
"guidelines",
3232
"scaffold"
33-
]
33+
],
34+
"skills": "./skills/"
3435
}

plugins/plugin-dev/scripts/run.ts

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -30,88 +30,101 @@ import {
3030
const ROOT = resolve(import.meta.dirname!, "..", "..", "..")
3131
const PLUGINS_DIR = join(ROOT, "plugins")
3232

33-
/**
34-
* Generate Codex, Antigravity, and Cursor manifests for every local plugin so
35-
* the same plugin directory loads across runtimes.
36-
*/
37-
export async function generateMultiFormat() {
33+
interface PluginRunResult {
34+
pluginsProcessed: number
35+
filesWritten: number
36+
benignSkipped: string[]
37+
failed: { name: string; error: string }[]
38+
}
39+
40+
/** Read the Claude marketplace, or exit(1) if it is missing. */
41+
function loadMarketplace(): ClaudeMarketplace {
3842
const marketplaceJsonPath = join(ROOT, ".claude-plugin", "marketplace.json")
3943
if (!existsSync(marketplaceJsonPath)) {
4044
console.error(`! marketplace not found: ${marketplaceJsonPath}`)
4145
process.exit(1)
4246
}
47+
return JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace
48+
}
4349

44-
const marketplaceJson = JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace
50+
/** Index local (`./plugins/...`) marketplace entries by their plugin directory name. */
51+
function localEntriesByDir(marketplaceJson: ClaudeMarketplace): Map<string, MarketplaceEntry> {
4552
const entriesByPluginDir = new Map<string, MarketplaceEntry>()
4653
for (const entry of marketplaceJson.plugins) {
4754
if (typeof entry.source === "string" && entry.source.startsWith("./plugins/")) {
48-
const dirName = entry.source.replace(/^\.\/plugins\//, "")
49-
entriesByPluginDir.set(dirName, entry)
55+
entriesByPluginDir.set(entry.source.replace(/^\.\/plugins\//, ""), entry)
5056
}
5157
}
58+
return entriesByPluginDir
59+
}
5260

53-
console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n")
54-
let pluginsProcessed = 0
55-
let filesWritten = 0
56-
const benignSkipped: string[] = []
57-
const failed: { name: string; error: string }[] = []
61+
/** Generate per-runtime manifests for every plugin directory, logging per-plugin status. */
62+
function processPlugins(entriesByPluginDir: Map<string, MarketplaceEntry>): PluginRunResult {
63+
const out: PluginRunResult = { pluginsProcessed: 0, filesWritten: 0, benignSkipped: [], failed: [] }
5864
const pluginDirs = readdirSync(PLUGINS_DIR, { withFileTypes: true })
5965
.filter(d => d.isDirectory())
6066
.map(d => d.name)
6167
.sort()
6268

6369
for (const dirName of pluginDirs) {
64-
const pluginDir = join(PLUGINS_DIR, dirName)
65-
const entry = entriesByPluginDir.get(dirName)
6670
try {
67-
const result = generateForPlugin(pluginDir, entry)
71+
const result = generateForPlugin(join(PLUGINS_DIR, dirName), entriesByPluginDir.get(dirName))
6872
if (result.reason) {
6973
console.log(` ${dirName}: skipped (${result.reason})`)
70-
benignSkipped.push(dirName)
74+
out.benignSkipped.push(dirName)
7175
continue
7276
}
73-
pluginsProcessed++
77+
out.pluginsProcessed++
7478
if (result.written.length === 0) {
7579
console.log(` ${dirName}: up to date`)
7680
} else {
7781
console.log(` ${dirName}: wrote ${result.written.length} file(s)`)
78-
filesWritten += result.written.length
82+
out.filesWritten += result.written.length
7983
}
8084
} catch (err) {
8185
const msg = err instanceof Error ? err.message : String(err)
8286
console.error(` ${dirName}: FAILED — ${msg}`)
83-
failed.push({ name: dirName, error: msg })
87+
out.failed.push({ name: dirName, error: msg })
8488
}
8589
}
90+
return out
91+
}
8692

87-
// Generate Codex marketplace.json from the Claude one.
88-
const codexMarketplace = toCodexMarketplace(marketplaceJson)
89-
const codexMarketplacePath = join(ROOT, ".agents", "plugins", "marketplace.json")
90-
const codexWritten = writeIfChanged(codexMarketplacePath, JSON.stringify(codexMarketplace, null, 2) + "\n")
93+
/** Emit the Codex + Cursor marketplace files from the Claude marketplace; returns files written. */
94+
function writeMarketplaces(marketplaceJson: ClaudeMarketplace): number {
95+
const targets: { label: string; path: string; doc: unknown }[] = [
96+
{ label: "Codex", path: join(ROOT, ".agents", "plugins", "marketplace.json"), doc: toCodexMarketplace(marketplaceJson) },
97+
{ label: "Cursor", path: join(ROOT, ".cursor-plugin", "marketplace.json"), doc: toCursorMarketplace(marketplaceJson) },
98+
]
9199
console.log()
92-
if (codexWritten) {
93-
console.log(`Codex marketplace written: ${codexMarketplacePath}`)
94-
filesWritten++
95-
} else {
96-
console.log(`Codex marketplace up to date: ${codexMarketplacePath}`)
100+
let written = 0
101+
for (const { label, path, doc } of targets) {
102+
if (writeIfChanged(path, JSON.stringify(doc, null, 2) + "\n")) {
103+
console.log(`${label} marketplace written: ${path}`)
104+
written++
105+
} else {
106+
console.log(`${label} marketplace up to date: ${path}`)
107+
}
97108
}
109+
return written
110+
}
98111

99-
// Generate Cursor marketplace.json from the Claude one (local plugins only).
100-
const cursorMarketplace = toCursorMarketplace(marketplaceJson)
101-
const cursorMarketplacePath = join(ROOT, ".cursor-plugin", "marketplace.json")
102-
const cursorWritten = writeIfChanged(cursorMarketplacePath, JSON.stringify(cursorMarketplace, null, 2) + "\n")
103-
if (cursorWritten) {
104-
console.log(`Cursor marketplace written: ${cursorMarketplacePath}`)
105-
filesWritten++
106-
} else {
107-
console.log(`Cursor marketplace up to date: ${cursorMarketplacePath}`)
108-
}
112+
/**
113+
* Generate Codex, Antigravity, and Cursor manifests for every local plugin so
114+
* the same plugin directory loads across runtimes.
115+
*/
116+
export async function generateMultiFormat() {
117+
const marketplaceJson = loadMarketplace()
118+
119+
console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n")
120+
const run = processPlugins(localEntriesByDir(marketplaceJson))
121+
const filesWritten = run.filesWritten + writeMarketplaces(marketplaceJson)
109122

110-
console.log(`\nProcessed ${pluginsProcessed} plugins, wrote ${filesWritten} file(s).`)
111-
if (benignSkipped.length > 0) console.log(`Skipped (no manifest): ${benignSkipped.join(", ")}`)
112-
if (failed.length > 0) {
113-
console.error(`\n${failed.length} plugin(s) failed:`)
114-
for (const f of failed) console.error(` - ${f.name}: ${f.error}`)
123+
console.log(`\nProcessed ${run.pluginsProcessed} plugins, wrote ${filesWritten} file(s).`)
124+
if (run.benignSkipped.length > 0) console.log(`Skipped (no manifest): ${run.benignSkipped.join(", ")}`)
125+
if (run.failed.length > 0) {
126+
console.error(`\n${run.failed.length} plugin(s) failed:`)
127+
for (const f of run.failed) console.error(` - ${f.name}: ${f.error}`)
115128
process.exit(1)
116129
}
117130
console.log("Done.")

plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: Migrating Gemini Extensions
2+
name: migrating-gemini-extensions
33
description: Convert a Gemini CLI extension into a Claude Code plugin, preserving functionality and backwards compatibility. Use when the user wants to migrate/port a Gemini extension to Claude Code, convert gemini-extension.json to plugin.json, adapt GEMINI.md context or Gemini tools/hooks, or mentions "gemini-extension.json", "GEMINI.md", or "migrate Gemini".
44
---
55

plugins/plugin-dev/skills/plugin-authoring/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: Authoring Plugins (Multi-Runtime)
2+
name: plugin-authoring
33
description: Author, scaffold, or edit a plugin once in Claude Code format, then generate the Codex, Antigravity, and Cursor manifests so one directory loads in all four runtimes. Use when creating/scaffolding a new plugin, editing a plugin or its manifest, wiring a marketplace entry, running the multi-format generator, or when the user mentions "plugin.json", ".claude-plugin", ".codex-plugin", ".cursor-plugin", "multi-format", "marketplace.json", "scaffold a plugin", or "Codex/Cursor/Antigravity plugin".
44
---
55

plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ is the source of truth; the others are generated.
1111
| Cursor | `.cursor-plugin/plugin.json` | none — components auto-discovered | generated |
1212

1313
Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live **once** at the plugin root and are
14-
referenced by every manifest. Only manifest-level fields differ per runtime. Each generated manifest
15-
carries its own `$schema`:
14+
referenced by every manifest. Only manifest-level fields differ per runtime. The Claude source
15+
manifest and the generated Antigravity manifest each carry a `$schema` (the generated Codex and
16+
Cursor manifests do not):
1617

1718
- Claude source — `https://json.schemastore.org/claude-code-plugin-manifest.json`
1819
- Antigravity root `plugin.json``https://antigravity.google/schemas/v1/plugin.json`
@@ -36,7 +37,7 @@ For every local plugin (`source: "./plugins/..."`) it emits:
3637
- `plugins/<name>/.codex-plugin/plugin.json` (+ `.mcp.json` when the plugin defines `mcpServers`)
3738
- `plugins/<name>/plugin.json` + `mcp_config.json` + root `hooks.json` (Antigravity)
3839
- `plugins/<name>/.cursor-plugin/plugin.json`
39-
- `.agents/plugins/marketplace.json` (Codex marketplace, local plugins only)
40+
- `.agents/plugins/marketplace.json` (Codex marketplace; local plugins plus supported external sources)
4041
- `.cursor-plugin/marketplace.json` (Cursor marketplace, local plugins only)
4142

4243
It writes only files whose content changed, and prints per-plugin status

plugins/plugin-dev/skills/validating-plugins/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: Validating Plugins
2+
name: validating-plugins
33
description: Thoroughly validate a Claude Code plugin's manifest, directory structure, commands, agents, skills, hooks, and MCP config, then report issues by severity. Use when the user asks to validate, check, audit, or lint a plugin, verify a plugin.json or plugin structure, debug why a plugin won't load, or mentions "plugin validate", "invalid manifest", or "plugin not loading".
44
---
55

0 commit comments

Comments
 (0)