From 95f1dd73151b2a8a9a7f88ae1893531027478051 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Jul 2026 10:01:29 +0900 Subject: [PATCH 1/2] refactor(plugin-dev): make plugin self-contained and migrate commands to skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the multi-format generator into plugin-dev so the plugin no longer depends on repo-root scripts/, add $schema fields to the Claude and generated Antigravity manifests, and consolidate the 5 slash commands into 3 skills with a shared references/ folder. - Move scripts/multi-format.ts and scripts/multi-format.test.ts into plugins/plugin-dev/scripts/. Add scripts/run.ts as the CLI entry point holding the generateMultiFormat() orchestration previously in scripts/cli.ts, which now re-exports it so `bun scripts/cli.ts multi-format` keeps working. Extend vitest.config.ts to discover the relocated test file. - Add "$schema" to plugin-dev's .claude-plugin/plugin.json (Claude source) and to the generator's Antigravity output (root plugin.json), with a new generator test covering the emitted schema URL. - Fix a pre-existing bug: plugin-dev previously shipped only a root-level plugin.json with no .claude-plugin/plugin.json, which fails `claude plugin validate`. Migrate the manifest to .claude-plugin/plugin.json as the Claude source of truth, with the generator producing the root plugin.json as the Antigravity manifest — matching the pattern already used by the bun plugin. - Replace commands/{scaffold,multi-format,validate,best-practices, migrate-gemini}.md with three skills modeled on the Codex plugin-creator and Cursor create-plugin reference skills: skills/plugin-authoring (scaffold + multi-format + best practices, with references/{plugin-json-spec,multi-runtime-manifests, best-practices}.md), skills/validating-plugins, and skills/migrating-gemini-extensions. Update README.md's Commands section to Skills and fix the stale claude-at-root comment in the generator. All 132 tests pass; `claude plugin validate plugins/plugin-dev` passes. --- plugins/plugin-dev/.claude-plugin/plugin.json | 22 ++ plugins/plugin-dev/README.md | 124 ++------- plugins/plugin-dev/commands/best-practices.md | 101 ------- plugins/plugin-dev/commands/migrate-gemini.md | 235 ---------------- plugins/plugin-dev/commands/multi-format.md | 82 ------ plugins/plugin-dev/commands/scaffold.md | 258 ------------------ plugins/plugin-dev/commands/validate.md | 125 --------- plugins/plugin-dev/plugin.json | 4 +- .../plugin-dev/scripts}/multi-format.test.ts | 10 +- .../plugin-dev/scripts}/multi-format.ts | 17 +- plugins/plugin-dev/scripts/run.ts | 122 +++++++++ .../migrating-gemini-extensions/SKILL.md | 131 +++++++++ .../skills/plugin-authoring/SKILL.md | 83 +++--- .../references/best-practices.md | 68 +++++ .../references/multi-runtime-manifests.md | 82 ++++++ .../references/plugin-json-spec.md | 93 +++++++ .../skills/validating-plugins/SKILL.md | 91 ++++++ scripts/cli.ts | 98 +------ vitest.config.ts | 2 +- 19 files changed, 711 insertions(+), 1037 deletions(-) create mode 100644 plugins/plugin-dev/.claude-plugin/plugin.json delete mode 100644 plugins/plugin-dev/commands/best-practices.md delete mode 100644 plugins/plugin-dev/commands/migrate-gemini.md delete mode 100644 plugins/plugin-dev/commands/multi-format.md delete mode 100644 plugins/plugin-dev/commands/scaffold.md delete mode 100644 plugins/plugin-dev/commands/validate.md rename {scripts => plugins/plugin-dev/scripts}/multi-format.test.ts (98%) rename {scripts => plugins/plugin-dev/scripts}/multi-format.ts (97%) create mode 100644 plugins/plugin-dev/scripts/run.ts create mode 100644 plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md create mode 100644 plugins/plugin-dev/skills/plugin-authoring/references/best-practices.md create mode 100644 plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md create mode 100644 plugins/plugin-dev/skills/plugin-authoring/references/plugin-json-spec.md create mode 100644 plugins/plugin-dev/skills/validating-plugins/SKILL.md diff --git a/plugins/plugin-dev/.claude-plugin/plugin.json b/plugins/plugin-dev/.claude-plugin/plugin.json new file mode 100644 index 00000000..0aba57a9 --- /dev/null +++ b/plugins/plugin-dev/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "plugin-dev", + "version": "1.1.0", + "description": "Best practices, guidelines, and validation tools for Claude Code plugin development", + "author": { + "name": "Passion Factory", + "email": "support@passionfactory.ai", + "url": "https://github.com/pleaseai" + }, + "homepage": "https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/plugin-dev", + "repository": "https://github.com/pleaseai/claude-code-plugins", + "license": "MIT", + "keywords": [ + "plugin", + "development", + "best-practices", + "validation", + "guidelines", + "scaffold" + ] +} \ No newline at end of file diff --git a/plugins/plugin-dev/README.md b/plugins/plugin-dev/README.md index baed7017..a7d3fcbd 100644 --- a/plugins/plugin-dev/README.md +++ b/plugins/plugin-dev/README.md @@ -4,7 +4,7 @@ Best practices, guidelines, and validation tools for Claude Code plugin developm ## Overview -`plugin-dev` is a comprehensive toolkit for Claude Code plugin developers. It provides commands, validation hooks, and expert guidance to help you create high-quality plugins that follow best practices. +`plugin-dev` is a comprehensive toolkit for Claude Code plugin developers. It provides skills, validation hooks, and expert guidance to help you create high-quality plugins that follow best practices. The skills activate automatically when you describe a plugin-development task — no slash commands to remember. ## Features @@ -28,101 +28,37 @@ claude /plugin install plugin-dev@pleaseai ``` -## Commands +## Skills -### `/plugin-dev:best-practices` +These skills activate automatically when your request matches — just describe what you want to do. +Three focused skills, with deep material factored into `references/` (loaded on demand). -Get comprehensive guidance on Claude Code plugin development best practices. +### `plugin-authoring` -**Use cases:** -- Learning plugin development standards -- Reviewing plugin architecture decisions -- Understanding component best practices -- Getting answers to specific development questions +The umbrella workflow: author, scaffold, or edit a plugin **once** in Claude Code format, then +generate the Codex, Antigravity, and Cursor manifests from it (`bun scripts/cli.ts multi-format`, +bundled in this plugin at `scripts/run.ts` + `scripts/multi-format.ts`). Covers scaffolding, +multi-format generation, and best practices inline, and points to its references: -**Example:** -``` -/plugin-dev:best-practices - -How should I structure my plugin's hooks for optimal performance? -``` - -### `/plugin-dev:validate` - -Perform comprehensive validation of your plugin structure and configuration. - -**Validates:** -- ✅ Plugin manifest (plugin.json) correctness -- ✅ Directory structure compliance -- ✅ Command file formats -- ✅ Hook configurations -- ✅ MCP server definitions -- ✅ Documentation completeness - -**Example:** -``` -/plugin-dev:validate +- `references/plugin-json-spec.md` — manifest + marketplace field guide with canonical samples. +- `references/multi-runtime-manifests.md` — per-runtime mapping, the generator, diff scoping. +- `references/best-practices.md` — quality checklist, pitfalls, testing. -Please validate my plugin at ./plugins/my-plugin -``` +> "I need a new plugin called *api-tools* for API testing." · "Regenerate *plugins/api-tools*' Codex and Cursor manifests." -### `/plugin-dev:scaffold` +### `validating-plugins` -Generate a new plugin with proper structure and best practices baked in. +Audit a plugin's manifest, directory structure, commands, skills, hooks, and MCP config, with +findings grouped by severity. -**Creates:** -- Complete directory structure -- plugin.json manifest -- Example commands -- Hook templates -- README and documentation -- License and changelog +> "Validate my plugin at ./plugins/my-plugin." -**Example:** -``` -/plugin-dev:scaffold +### `migrating-gemini-extensions` -I need a new plugin called "api-tools" that provides commands for API testing -``` +Convert a Gemini CLI extension to a Claude Code plugin, preserving functionality and backwards +compatibility. -### `/plugin-dev:multi-format` - -Generate the Codex, Antigravity, and Cursor manifests for the marketplace's local plugins from the -Claude Code source of truth. The Claude manifest is the only file you author by hand — this command -keeps every runtime in sync. - -**Generates (per local plugin):** -- `.codex-plugin/plugin.json` (+ `.mcp.json` when MCP is present) -- root `plugin.json` + `mcp_config.json` + `hooks.json` (Antigravity) -- `.cursor-plugin/plugin.json` -- `.agents/plugins/marketplace.json` and `.cursor-plugin/marketplace.json` - -**Example:** -``` -/plugin-dev:multi-format - -I just edited plugins/api-tools — regenerate its Codex and Cursor manifests -``` - -> The generator rewrites all local plugins; scope your commit to the plugin(s) you changed. - -### `/plugin-dev:migrate-gemini` - -Migrate existing Gemini CLI extensions to Claude Code plugins. - -**Handles:** -- Converting gemini-extension.json → plugin.json -- Migrating context files to SessionStart hooks -- Updating MCP server configurations -- Maintaining backwards compatibility -- Updating documentation - -**Example:** -``` -/plugin-dev:migrate-gemini - -Help me migrate my Gemini extension at ./extensions/my-extension -``` +> "Help me migrate my Gemini extension at ./extensions/my-extension." ## Automatic Validation @@ -194,24 +130,22 @@ plugin-name/ ## Development Workflow -1. **Create plugin structure** - ```bash - /plugin-dev:scaffold - ``` +1. **Create plugin structure** — describe the plugin; the `plugin-authoring` skill activates. 2. **Develop components** - Add commands in `commands/` - Create agents in `agents/` + - Add skills in `skills/` - Configure hooks in `hooks/` -3. **Generate multi-runtime manifests** +3. **Generate multi-runtime manifests** (the `plugin-authoring` skill) ```bash - /plugin-dev:multi-format + bun scripts/cli.ts multi-format ``` -4. **Validate continuously** +4. **Validate continuously** — ask to validate the plugin (the `validating-plugins` skill), and run ```bash - /plugin-dev:validate + claude plugin validate plugins/ ``` 5. **Test locally** @@ -251,7 +185,7 @@ chmod +x hooks/script.sh ### Validation errors **Problem**: Invalid JSON or missing required fields -**Solution**: Run `/plugin-dev:validate` to see specific issues +**Solution**: Ask to validate the plugin (the `validating-plugins` skill) to see specific issues ## Resources @@ -274,7 +208,7 @@ See these plugins for real-world examples: Contributions welcome! Please: 1. Follow the best practices outlined in this plugin -2. Validate your changes with `/plugin-dev:validate` +2. Validate your changes (the `validating-plugins` skill) 3. Update documentation 4. Add examples for new features diff --git a/plugins/plugin-dev/commands/best-practices.md b/plugins/plugin-dev/commands/best-practices.md deleted file mode 100644 index a4fe0927..00000000 --- a/plugins/plugin-dev/commands/best-practices.md +++ /dev/null @@ -1,101 +0,0 @@ -# Plugin Development Best Practices - -You are an expert in Claude Code plugin development. Provide comprehensive best practices guidance based on the official documentation and lessons learned from the plugin ecosystem. - -## Your Task - -Review the user's plugin or answer questions about plugin development best practices, covering: - -### 1. Plugin Manifest (plugin.json) -- **Required fields**: Ensure `name` is present (kebab-case, no spaces) -- **Metadata quality**: Check version (semver), description, author, repository -- **Keywords**: Verify relevant keywords for discoverability -- **Component paths**: Validate custom paths start with `./` and are relative -- **Environment variables**: Use `${CLAUDE_PLUGIN_ROOT}` for all plugin paths - -### 2. Directory Structure -- **Manifest location**: `.claude-plugin/plugin.json` must be at this exact path -- **Component locations**: `commands/`, `agents/`, `hooks/` must be at plugin root -- **Never nest**: Don't put commands/agents/hooks inside `.claude-plugin/` -- **Scripts**: Place helper scripts in `scripts/` or `hooks/` directory -- **Documentation**: Include `README.md`, `LICENSE`, `CHANGELOG.md` - -### 3. Commands Best Practices -- **File format**: Use Markdown with clear frontmatter -- **Naming**: Use kebab-case for file names -- **Namespacing**: Commands automatically namespaced as `/plugin-name:command` -- **Documentation**: Include usage examples and parameter descriptions -- **Prompts**: Write clear, focused prompts that guide Claude effectively - -### 4. Hooks Best Practices -- **Configuration**: Define in `hooks/hooks.json` or inline in `plugin.json` -- **Script execution**: Make scripts executable (`chmod +x`) -- **Path references**: Always use `${CLAUDE_PLUGIN_ROOT}` -- **Output format**: Return proper JSON with `hookSpecificOutput` -- **Error handling**: Handle failures gracefully -- **Performance**: Keep hooks fast (<1s when possible) - -### 5. MCP Server Integration -- **NPX pattern**: Use `npx -y` for npm-published servers (recommended) -- **Environment variables**: Use `${VAR:-}` pattern for optional vars -- **Command paths**: Use `${CLAUDE_PLUGIN_ROOT}` for local servers -- **Testing**: Verify server starts and tools are accessible - -### 6. Agents Best Practices -- **Description quality**: Write clear, specific descriptions -- **Capabilities**: List specific tasks the agent excels at -- **When to use**: Provide clear guidance on invocation scenarios -- **Context**: Include examples and use cases - -### 7. Documentation Standards -- **README structure**: Include installation, usage, features, examples -- **Installation instructions**: Provide clear marketplace commands -- **API documentation**: Document all commands, agents, and hooks -- **Examples**: Include real-world usage examples -- **Changelog**: Maintain version history - -### 8. Common Pitfalls to Avoid -- ❌ Absolute paths in configurations -- ❌ Commands/agents/hooks inside `.claude-plugin/` -- ❌ Missing executable permissions on scripts -- ❌ Hardcoded paths without `${CLAUDE_PLUGIN_ROOT}` -- ❌ Non-executable hook scripts -- ❌ Invalid JSON in manifests -- ❌ Missing required fields in plugin.json - -### 9. Testing & Validation -- **Local testing**: Use `claude --debug` to see plugin loading -- **Manifest validation**: Validate JSON syntax -- **Command testing**: Test all slash commands work -- **Hook testing**: Verify hooks fire correctly -- **MCP testing**: Ensure servers start and tools are accessible - -### 10. Distribution & Versioning -- **Semantic versioning**: Follow semver (MAJOR.MINOR.PATCH) -- **Git tags**: Tag releases in git -- **Changelog**: Document changes for each version -- **Marketplace**: Submit to plugin marketplaces - -## Response Format - -When reviewing a plugin: -1. Analyze the current structure -2. List specific issues found (with file:line references) -3. Provide actionable recommendations -4. Include code examples for fixes -5. Prioritize issues by severity (Critical, Warning, Suggestion) - -When answering questions: -1. Reference official documentation -2. Provide concrete examples -3. Explain the "why" behind recommendations -4. Link to relevant resources - -## Key Resources - -- [Claude Code Plugins Documentation](https://docs.claude.com/en/docs/claude-code/plugins) -- [Plugin Reference](https://docs.claude.com/en/docs/claude-code/plugins-reference) -- [Plugin Marketplaces](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces) -- [MCP Documentation](https://modelcontextprotocol.io/) - -Now, help the user with their plugin development best practices question or review. diff --git a/plugins/plugin-dev/commands/migrate-gemini.md b/plugins/plugin-dev/commands/migrate-gemini.md deleted file mode 100644 index 931b1050..00000000 --- a/plugins/plugin-dev/commands/migrate-gemini.md +++ /dev/null @@ -1,235 +0,0 @@ -# Migrate Gemini Extension to Claude Code Plugin - -You are an expert in migrating Gemini CLI extensions to Claude Code plugins. Help users convert their existing extensions seamlessly. - -## Your Task - -Guide the user through migrating a Gemini CLI extension to a Claude Code plugin, preserving functionality while adopting Claude Code best practices. - -## Migration Strategy - -### Phase 1: Analysis - -1. **Read existing structure** - - Locate `gemini-extension.json` - - Check for context files (e.g., `GEMINI.md`) - - Identify commands, MCP servers, hooks - - Note any custom scripts - -2. **Map components** - - Gemini context → Claude commands/agents - - Gemini tools → MCP servers - - Gemini scripts → Hook scripts - -### Phase 2: Structure Creation - -Create Claude Code structure: -``` -plugin-name/ -├── .claude-plugin/ -│ └── plugin.json # NEW: Claude manifest -├── gemini-extension.json # KEEP: For backwards compatibility -├── commands/ # NEW: Convert context to commands -├── hooks/ # MIGRATE: Adapt existing hooks -│ ├── hooks.json # NEW: Claude hook format -│ └── scripts/ # ADAPT: Update paths -└── README.md # UPDATE: Add Claude instructions -``` - -### Phase 3: Convert Manifest - -Transform `gemini-extension.json` → `plugin.json`: - -**From (Gemini):** -```json -{ - "name": "extension-name", - "version": "1.0.0", - "description": "Description", - "tools": [...], - "context": "CONTEXT.md" -} -``` - -**To (Claude):** -```json -{ - "name": "plugin-name", - "version": "1.0.0", - "description": "Description", - "author": { - "name": "...", - "email": "..." - }, - "homepage": "...", - "repository": "...", - "license": "MIT", - "keywords": [...] -} -``` - -### Phase 4: Migrate Context Files - -**Gemini context file** → **Claude SessionStart hook** - -Create `hooks/hooks.json`: -```json -{ - "description": "Load usage instructions at session start", - "hooks": { - "SessionStart": [ - { - "matcher": "startup", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/context.sh", - "timeout": 10 - } - ] - } - ] - } -} -``` - -Create `hooks/context.sh`: -```bash -#!/usr/bin/env bash -set -euo pipefail - -CONTEXT_FILE="${CLAUDE_PLUGIN_ROOT}/hooks/CONTEXT.md" - -if [ -f "$CONTEXT_FILE" ]; then - CONTEXT_CONTENT=$(cat "$CONTEXT_FILE") - jq -n --arg context "$CONTEXT_CONTENT" '{ - "hookSpecificOutput": { - "hookEventName": "SessionStart", - "additionalContext": $context - } - }' -fi -``` - -**Don't forget**: `chmod +x hooks/context.sh` - -### Phase 5: Migrate MCP Servers - -**Update MCP configuration:** - -From Gemini tools → Claude `.mcp.json` or inline in `plugin.json`: - -```json -{ - "mcpServers": { - "server-name": { - "command": "npx", - "args": ["-y", "@package/name"], - "env": { - "API_KEY": "${PLUGIN_API_KEY:-}" - } - } - } -} -``` - -**Key changes:** -- Use `${CLAUDE_PLUGIN_ROOT}` for local paths -- Use `${VAR:-}` pattern for optional env vars -- Prefer `npx -y` for npm packages - -### Phase 6: Update Documentation - -Add Claude Code installation to README: - -```markdown -## Installation - -### Claude Code Plugin - -\`\`\`bash -claude -/plugin marketplace add owner/marketplace-repo -/plugin install plugin-name@owner -\`\`\` - -This automatically loads usage instructions on session start. - -### Gemini CLI Extension (Legacy) - -\`\`\`bash -gemini ext add owner/repo -\`\`\` -``` - -### Phase 7: Testing - -1. **Test Claude Code plugin** - ```bash - claude --debug - /plugin list - /plugin-name:command - ``` - -2. **Verify backwards compatibility** - ```bash - gemini ext list - # Should still work with Gemini CLI - ``` - -3. **Check functionality** - - Commands work as expected - - Hooks fire correctly - - MCP servers start properly - -## Migration Checklist - -- [ ] Create `.claude-plugin/plugin.json` -- [ ] Keep `gemini-extension.json` for backwards compatibility -- [ ] Convert context files to SessionStart hooks -- [ ] Make hook scripts executable -- [ ] Update paths to use `${CLAUDE_PLUGIN_ROOT}` -- [ ] Migrate MCP server configurations -- [ ] Update README with both installation methods -- [ ] Test with Claude Code -- [ ] Verify Gemini CLI still works -- [ ] Update CHANGELOG - -## Common Migration Issues - -### Issue: Context not loading -**Solution**: Ensure hook script is executable and returns proper JSON - -### Issue: MCP server fails to start -**Solution**: Check paths use `${CLAUDE_PLUGIN_ROOT}` and command exists - -### Issue: Commands not appearing -**Solution**: Verify `commands/` is at root, not in `.claude-plugin/` - -### Issue: Hook script errors -**Solution**: Test script independently: `bash hooks/context.sh` - -## Best Practices - -1. **Keep both formats** for backwards compatibility -2. **Update README** to show both installation methods -3. **Test thoroughly** with both Claude Code and Gemini CLI -4. **Use SessionStart hooks** for automatic context loading -5. **Submit upstream PR** if migrating third-party extension - -## Example Migration - -See `external-plugins/context7/` for a complete migration example: -- Kept `gemini-extension.json` -- Added `.claude-plugin/plugin.json` -- Created SessionStart hook for context -- Updated README with Claude instructions -- Submitted upstream PR - -## Resources - -- [Context7 Migration Lessons](../../docs/lessons-learned/context7.md) -- [Claude Code Plugin Documentation](https://docs.claude.com/en/docs/claude-code/plugins) -- [MCP Documentation](https://modelcontextprotocol.io/) - -Now, help the user migrate their Gemini extension to Claude Code! \ No newline at end of file diff --git a/plugins/plugin-dev/commands/multi-format.md b/plugins/plugin-dev/commands/multi-format.md deleted file mode 100644 index d308a9f6..00000000 --- a/plugins/plugin-dev/commands/multi-format.md +++ /dev/null @@ -1,82 +0,0 @@ -# Generate Multi-Runtime Manifests - -You are a multi-runtime plugin packaging expert. Generate the Codex, Antigravity, and Cursor -manifests for the marketplace's local plugins from the Claude Code source of truth, keeping every -runtime in sync. - -## Background - -This repository ships the **same plugin set across four runtimes**. The Claude Code manifest is the -**single source of truth**; the others are **generated** and must never be hand-edited. - -| Runtime | Manifest path | Companion files | -|--------------|----------------------------------------|---------------------------------------| -| Claude Code | `.claude-plugin/plugin.json` (or root `plugin.json`) | `hooks/hooks.json`, inline `mcpServers` | -| Codex | `.codex-plugin/plugin.json` | `.mcp.json` (when MCP present) | -| Antigravity | `plugin.json` (root) | `mcp_config.json`, root `hooks.json` | -| Cursor | `.cursor-plugin/plugin.json` | none — components auto-discovered | - -Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live once at the plugin root and are -referenced by every manifest. Only manifest-level fields differ per runtime. - -## Your Task - -### 1. Run the generator - -> **New plugin? Wire its `.claude-plugin/marketplace.json` entry first.** The generator resolves -> each plugin's metadata (notably `category`) from the marketplace entry. A plugin dir not yet -> listed there is generated with no entry — its Codex manifest falls back to the default category -> and it is omitted from the emitted Codex/Cursor marketplace files. Add the entry, then run: - -```bash -bun scripts/cli.ts multi-format -``` - -For every local plugin (`source: "./plugins/..."` in `.claude-plugin/marketplace.json`) this emits: - -- `plugins//.codex-plugin/plugin.json` (+ `.mcp.json` when the plugin defines `mcpServers`) -- `plugins//plugin.json` + `mcp_config.json` + root `hooks.json` (Antigravity) -- `plugins//.cursor-plugin/plugin.json` -- `.agents/plugins/marketplace.json` (Codex marketplace, local plugins only) -- `.cursor-plugin/marketplace.json` (Cursor marketplace, local plugins only) - -The generator only writes files whose content actually changed, and prints per-plugin status -(`wrote N file(s)` / `up to date` / `skipped`). - -### 2. Scope the diff to your change - -⚠️ **`multi-format` rewrites all local plugins, not just the one you touched.** If the committed -artifacts had pre-existing drift, the command produces a large unrelated diff. Keep the change -atomic: - -```bash -git status --short -# Revert churn from plugins you did NOT intend to change: -git restore plugins//... -``` - -Commit only the files for the plugin(s) you actually changed. - -### 3. Claude-only fields do not propagate - -Fields that exist only in the Claude runtime — notably `relevance` (plugin suggestion signals) — are -intentionally **not** copied to the Codex/Cursor marketplaces. Author them only in -`.claude-plugin/marketplace.json`. - -### 4. Verify - -```bash -claude plugin validate .claude-plugin/marketplace.json -claude plugin validate plugins/ -``` - -## When to Run - -- After scaffolding a new plugin (`/plugin-dev:scaffold`) -- After editing any plugin's Claude manifest (name, version, description, `mcpServers`, hooks) -- After adding, removing, or reordering a plugin entry in `.claude-plugin/marketplace.json` - -Do **not** hand-edit `.codex-plugin/`, `.cursor-plugin/`, root Antigravity `plugin.json`, or the -generated marketplaces — re-run this command instead. - -Now, run the generator, scope the diff to the intended plugin(s), and report what changed. diff --git a/plugins/plugin-dev/commands/scaffold.md b/plugins/plugin-dev/commands/scaffold.md deleted file mode 100644 index 8341b518..00000000 --- a/plugins/plugin-dev/commands/scaffold.md +++ /dev/null @@ -1,258 +0,0 @@ -# Scaffold New Plugin - -You are a plugin scaffolding expert. Help users create a new plugin with proper structure and best practices. - -Author the plugin **once in Claude Code format** (the source of truth), then generate the -Codex / Antigravity / Cursor manifests from it in a single step. One directory, four runtimes — -you never hand-write the other manifests. - -## Your Task - -Create a complete plugin structure based on user requirements. Ask clarifying questions if needed, -generate all necessary files in Claude Code format, then run the multi-format generator so the same -directory loads in Codex, Antigravity, and Cursor too. - -## Scaffolding Process - -### 1. Gather Requirements - -Ask the user about: -- **Plugin name**: What should the plugin be called? (kebab-case) -- **Purpose**: What problem does this plugin solve? -- **Components needed**: Commands? Agents? Hooks? MCP servers? -- **Author information**: Name, email, GitHub URL? -- **License**: MIT, Apache-2.0, etc.? - -### 2. Create Directory Structure - -Generate the standard layout: -``` -plugins/{plugin-name}/ -├── .claude-plugin/ -│ └── plugin.json -├── commands/ # If commands needed -├── agents/ # If agents needed -├── hooks/ # If hooks needed -│ └── hooks.json -├── scripts/ # If hook scripts needed -├── README.md -├── LICENSE -└── CHANGELOG.md -``` - -### 3. Generate plugin.json - -Create manifest with: -```json -{ - "name": "plugin-name", - "version": "0.1.0", - "description": "Brief description", - "author": { - "name": "Author Name", - "email": "email@example.com", - "url": "https://github.com/username" - }, - "homepage": "https://github.com/username/repo", - "repository": "https://github.com/username/repo", - "license": "MIT", - "keywords": ["keyword1", "keyword2"] -} -``` - -### 4. Create Example Commands - -If commands requested, create template: -```markdown -# Command Name - -Brief description of what this command does. - -## Usage - -Explain how to use this command and what it accomplishes. - -## Examples - -Provide concrete examples of using this command. - -Now, [action the command should take]. -``` - -### 5. Create Example Agents - -If agents requested, create template: -```markdown ---- -description: Brief description of agent specialty -capabilities: ["task1", "task2", "task3"] ---- - -# Agent Name - -Detailed description of what this agent does and when Claude should invoke it. - -## Capabilities -- Specific capability 1 -- Specific capability 2 -- Specific capability 3 - -## When to Use -- Scenario 1 -- Scenario 2 -- Scenario 3 - -## Examples -Provide examples of when this agent is helpful. -``` - -### 6. Create Hook Configuration - -If hooks requested, create `hooks/hooks.json`: -```json -{ - "description": "Hook description", - "hooks": { - "SessionStart": [ - { - "matcher": "startup", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/init.sh", - "timeout": 10 - } - ] - } - ] - } -} -``` - -### 7. Generate README.md - -Create comprehensive README: -```markdown -# Plugin Name - -Brief description of the plugin. - -## Features - -- Feature 1 -- Feature 2 -- Feature 3 - -## Installation - -\`\`\`bash -# Add marketplace (if not already added) -claude -/plugin marketplace add owner/marketplace-repo - -# Install plugin -/plugin install plugin-name@owner -\`\`\` - -## Usage - -### Commands - -- `/plugin-name:command1` - Description -- `/plugin-name:command2` - Description - -### Agents - -- **agent-name** - When to use - -## Configuration - -Optional configuration instructions. - -## Examples - -Concrete usage examples. - -## License - -[License name] -``` - -### 8. Create LICENSE File - -Generate appropriate license file based on choice. - -### 9. Create CHANGELOG.md - -Initialize changelog: -```markdown -# Changelog - -All notable changes to this project will be documented in this file. - -## [0.1.0] - YYYY-MM-DD - -### Added -- Initial release -- Feature 1 -- Feature 2 -``` - -### 10. Wire the Marketplace Entry, Then Generate Multi-Runtime Manifests - -The plugin is authored in Claude Code format (`.claude-plugin/plugin.json`, or a root-level -`plugin.json` for plugins that also serve as the Antigravity manifest). This is the **source of -truth** — never hand-write the other runtimes' manifests; generate them instead. - -**Order matters — wire the marketplace entry _first_.** The generator resolves each plugin's -metadata (notably `category`) from `.claude-plugin/marketplace.json`. A plugin dir that is not yet -listed there is generated with no entry, so its Codex manifest falls back to the default category -and it is omitted from the emitted Codex/Cursor marketplace files. So for a brand-new plugin, wire -the companion files described in `.claude/rules/marketplace-sync.md` **before** running the -generator: - -1. Add the marketplace entry to `.claude-plugin/marketplace.json` (source of truth). -2. If the plugin is release-managed, add a `plugins/` entry to `release-please-config.json` - + `.release-please-manifest.json` covering every version-bearing manifest the plugin ships. - -Then generate the other runtimes' manifests in a single pass: - -```bash -bun scripts/cli.ts multi-format -``` - -This reads the Claude manifest + the marketplace entry and emits, for every local plugin -(`source: "./plugins/..."`): - -- `plugins//.codex-plugin/plugin.json` (+ `.mcp.json` when the plugin defines `mcpServers`) -- `plugins//plugin.json` + `mcp_config.json` + root `hooks.json` (Antigravity) -- `plugins//.cursor-plugin/plugin.json` -- `.agents/plugins/marketplace.json` (Codex marketplace) and `.cursor-plugin/marketplace.json` (Cursor marketplace) - -Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live **once** at the plugin root and -are referenced by every manifest — only manifest-level fields differ per runtime. - -> **This command rewrites all local plugins, not just the new one.** If unrelated plugins show up -> in the diff (pre-existing drift), `git restore` those files and commit only the new plugin's -> artifacts so the change stays atomic. See `/plugin-dev:multi-format` for the dedicated wrapper. - -## After Scaffolding - -1. **Validate structure** using `/plugin-dev:validate` -2. **Test locally** with `claude --debug` -3. **Iterate** on commands/agents based on needs -4. **Document** usage and examples -5. **Version control** with git -6. **Publish** to marketplace when ready - -## Best Practices Reminder - -- ✅ Use kebab-case for plugin name -- ✅ Include all metadata in plugin.json -- ✅ Write clear, actionable commands -- ✅ Make hook scripts executable -- ✅ Use `${CLAUDE_PLUGIN_ROOT}` in paths -- ✅ Document everything in README -- ✅ Follow semantic versioning - -Now, help the user scaffold their new plugin! \ No newline at end of file diff --git a/plugins/plugin-dev/commands/validate.md b/plugins/plugin-dev/commands/validate.md deleted file mode 100644 index 0d54565f..00000000 --- a/plugins/plugin-dev/commands/validate.md +++ /dev/null @@ -1,125 +0,0 @@ -# Validate Plugin Structure - -You are a plugin validation expert. Thoroughly validate the user's Claude Code plugin structure and configuration. - -## Your Task - -Perform a comprehensive validation of the plugin, checking: - -### 1. Plugin Manifest Validation - -Check `.claude-plugin/plugin.json`: -- ✅ File exists at correct location -- ✅ Valid JSON syntax -- ✅ Required field: `name` (string, kebab-case) -- ✅ Recommended fields: `version`, `description`, `author` -- ✅ Valid semver for `version` field -- ✅ Keywords are relevant and descriptive -- ✅ Paths use `./` prefix and are relative -- ✅ Environment variables use `${CLAUDE_PLUGIN_ROOT}` - -### 2. Directory Structure Validation - -Check layout: -- ✅ `.claude-plugin/` directory exists with `plugin.json` -- ✅ `commands/` at root (not in `.claude-plugin/`) -- ✅ `agents/` at root if present -- ✅ `hooks/` at root if present -- ✅ `README.md` exists -- ✅ `LICENSE` file present -- ❌ No commands/agents/hooks nested in `.claude-plugin/` - -### 3. Commands Validation - -For each `.md` file in `commands/`: -- ✅ Valid Markdown format -- ✅ Clear, actionable content -- ✅ Files use kebab-case naming -- ✅ Frontmatter if needed -- ⚠️ Check for useful descriptions - -### 4. Hooks Validation - -If hooks exist: -- ✅ `hooks/hooks.json` has valid JSON -- ✅ Hook scripts are executable (`chmod +x`) -- ✅ Scripts use `${CLAUDE_PLUGIN_ROOT}` -- ✅ Output format matches spec -- ⚠️ Check hook event names are valid - -### 5. MCP Server Validation - -If MCP servers defined: -- ✅ Valid configuration in `.mcp.json` or `plugin.json` -- ✅ Command paths exist -- ✅ Environment variables properly formatted -- ✅ Use `npx -y` pattern when applicable - -### 6. Agents Validation - -For each agent in `agents/`: -- ✅ Valid Markdown format -- ✅ Clear description of capabilities -- ✅ Usage examples provided -- ✅ When to use guidance included - -### 7. Documentation Validation - -Check documentation: -- ✅ `README.md` has installation instructions -- ✅ Usage examples provided -- ✅ Features documented -- ✅ License specified -- ⚠️ `CHANGELOG.md` recommended - -## Validation Process - -1. **Scan plugin directory structure** - - Use Glob/Read tools to analyze the plugin - - Check all required files exist - -2. **Parse and validate plugin.json** - - Verify JSON is valid - - Check required and recommended fields - - Validate paths and environment variables - -3. **Check component integrity** - - Validate commands, agents, hooks - - Check file permissions for scripts - - Verify paths are correct - -4. **Report findings** - - Group by severity: Critical, Warning, Suggestion - - Provide specific file:line references - - Include actionable fixes - -## Output Format - -``` -# Plugin Validation Report: {plugin-name} - -## Critical Issues ❌ -- [FILE:LINE] Issue description - Fix: Specific solution - -## Warnings ⚠️ -- [FILE:LINE] Issue description - Recommendation: Suggested improvement - -## Suggestions 💡 -- [FILE:LINE] Enhancement opportunity - Suggestion: How to improve - -## Summary -- Total issues: X -- Critical: X -- Warnings: X -- Suggestions: X - -## Next Steps -1. Fix critical issues first -2. Address warnings for better quality -3. Consider suggestions for enhancement -``` - -Now, validate the user's plugin and provide a detailed report. \ No newline at end of file diff --git a/plugins/plugin-dev/plugin.json b/plugins/plugin-dev/plugin.json index f88c7f99..341fa70f 100644 --- a/plugins/plugin-dev/plugin.json +++ b/plugins/plugin-dev/plugin.json @@ -1,10 +1,10 @@ { + "$schema": "https://antigravity.google/schemas/v1/plugin.json", "name": "plugin-dev", "version": "1.1.0", "description": "Best practices, guidelines, and validation tools for Claude Code plugin development", "author": { "name": "Passion Factory", - "email": "support@passionfactory.ai", "url": "https://github.com/pleaseai" }, "homepage": "https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/plugin-dev", @@ -18,4 +18,4 @@ "guidelines", "scaffold" ] -} \ No newline at end of file +} diff --git a/scripts/multi-format.test.ts b/plugins/plugin-dev/scripts/multi-format.test.ts similarity index 98% rename from scripts/multi-format.test.ts rename to plugins/plugin-dev/scripts/multi-format.test.ts index 13724b1e..ca9276e5 100644 --- a/scripts/multi-format.test.ts +++ b/plugins/plugin-dev/scripts/multi-format.test.ts @@ -168,7 +168,15 @@ describe("toAntigravityManifest", () => { test("produces flat manifest with only present fields", () => { const claude: ClaudePluginManifest = { name: "x" } const result = toAntigravityManifest(claude) as unknown as Record - expect(result).toEqual({ name: "x" }) + expect(result).toEqual({ + "$schema": "https://antigravity.google/schemas/v1/plugin.json", + name: "x", + }) + }) + + test("emits the Antigravity $schema URL for editor validation", () => { + const result = toAntigravityManifest({ name: "x" }) as unknown as Record + expect(result["$schema"]).toBe("https://antigravity.google/schemas/v1/plugin.json") }) test("preserves common metadata fields", () => { diff --git a/scripts/multi-format.ts b/plugins/plugin-dev/scripts/multi-format.ts similarity index 97% rename from scripts/multi-format.ts rename to plugins/plugin-dev/scripts/multi-format.ts index 5b002d25..45956de7 100644 --- a/scripts/multi-format.ts +++ b/plugins/plugin-dev/scripts/multi-format.ts @@ -37,10 +37,12 @@ import { dirname, join } from "node:path" * auto-discovers them from default folders at the plugin root * - `mcpServers` inline → kept inline in the manifest (Cursor accepts an object) * - * Antigravity collision: plugins that already use a root-level `plugin.json` - * (currently `bun`, `plugin-dev`) are treated as Antigravity-compatible - * already — generator only writes the file when content actually changes, - * and Claude Code itself can read root-level `plugin.json`. + * Antigravity collision safeguard: if a plugin's Claude manifest lives at the + * root `plugin.json` (no `.claude-plugin/plugin.json`), that file IS the source + * of truth and is left untouched rather than overwritten with a generated + * Antigravity manifest. No local plugin currently ships that way — every plugin + * uses `.claude-plugin/plugin.json` as source and a generated root `plugin.json` + * for Antigravity — but the guard remains for safety. */ export interface ClaudePluginManifest { @@ -102,6 +104,7 @@ export interface CodexManifest { } export interface AntigravityManifest { + "$schema"?: string name: string version?: string description?: string @@ -114,6 +117,10 @@ export interface AntigravityManifest { const DEFAULT_CATEGORY = "Productivity" +// JSON Schema URL for the Antigravity plugin manifest, used by editors for +// autocomplete and validation. See https://antigravity.google/docs/cli/plugins +const ANTIGRAVITY_SCHEMA = "https://antigravity.google/schemas/v1/plugin.json" + function isHttpsUrl(value: string | undefined): value is string { return !!value && /^https:\/\/[^\s]+$/.test(value) } @@ -286,7 +293,7 @@ export function toCodexManifest( * Claude Code manifest convention used by a few existing plugins. */ export function toAntigravityManifest(claude: ClaudePluginManifest): AntigravityManifest { - const manifest: AntigravityManifest = { name: claude.name } + const manifest: AntigravityManifest = { "$schema": ANTIGRAVITY_SCHEMA, name: claude.name } if (claude.version) manifest.version = claude.version if (claude.description) manifest.description = claude.description const author = sanitiseAuthor(claude.author) diff --git a/plugins/plugin-dev/scripts/run.ts b/plugins/plugin-dev/scripts/run.ts new file mode 100644 index 00000000..99fd51a4 --- /dev/null +++ b/plugins/plugin-dev/scripts/run.ts @@ -0,0 +1,122 @@ +#!/usr/bin/env bun +/** + * Multi-runtime manifest generator for this marketplace's local plugins. + * + * This is a repo-maintenance script that lives inside the plugin-dev plugin so + * the plugin is self-contained: `/plugin-dev:multi-format` invokes it directly + * (`bun ${CLAUDE_PLUGIN_ROOT}/scripts/run.ts`). The repo-root `scripts/cli.ts + * multi-format` command re-exports {@link generateMultiFormat} from here so the + * historical invocation keeps working. + * + * It reads the Claude Code marketplace (the single source of truth) at the repo + * root and emits Codex, Antigravity, and Cursor manifests for every local + * plugin. See ./multi-format.ts for the per-runtime format mapping rules. + */ +import { existsSync, readdirSync, readFileSync } from "node:fs" +import { join, resolve } from "node:path" +import { + type ClaudeMarketplace, + generateForPlugin, + type MarketplaceEntry, + toCodexMarketplace, + toCursorMarketplace, + writeIfChanged, +} from "./multi-format.ts" + +// This file lives at plugins/plugin-dev/scripts/run.ts, so the repo root is +// three directories up. Resolving from the script location (not CLAUDE_PLUGIN_ROOT) +// keeps the generator pointed at this marketplace's manifests regardless of how +// it is invoked. +const ROOT = resolve(import.meta.dirname!, "..", "..", "..") +const PLUGINS_DIR = join(ROOT, "plugins") + +/** + * Generate Codex, Antigravity, and Cursor manifests for every local plugin so + * the same plugin directory loads across runtimes. + */ +export async function generateMultiFormat() { + const marketplaceJsonPath = join(ROOT, ".claude-plugin", "marketplace.json") + if (!existsSync(marketplaceJsonPath)) { + console.error(`! marketplace not found: ${marketplaceJsonPath}`) + process.exit(1) + } + + const marketplaceJson = JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace + const entriesByPluginDir = new Map() + for (const entry of marketplaceJson.plugins) { + if (typeof entry.source === "string" && entry.source.startsWith("./plugins/")) { + const dirName = entry.source.replace(/^\.\/plugins\//, "") + entriesByPluginDir.set(dirName, entry) + } + } + + console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n") + let pluginsProcessed = 0 + let filesWritten = 0 + const benignSkipped: string[] = [] + const failed: { name: string; error: string }[] = [] + const pluginDirs = readdirSync(PLUGINS_DIR, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name) + .sort() + + for (const dirName of pluginDirs) { + const pluginDir = join(PLUGINS_DIR, dirName) + const entry = entriesByPluginDir.get(dirName) + try { + const result = generateForPlugin(pluginDir, entry) + if (result.reason) { + console.log(` ${dirName}: skipped (${result.reason})`) + benignSkipped.push(dirName) + continue + } + pluginsProcessed++ + if (result.written.length === 0) { + console.log(` ${dirName}: up to date`) + } else { + console.log(` ${dirName}: wrote ${result.written.length} file(s)`) + filesWritten += result.written.length + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + console.error(` ${dirName}: FAILED — ${msg}`) + failed.push({ name: dirName, error: msg }) + } + } + + // Generate Codex marketplace.json from the Claude one. + const codexMarketplace = toCodexMarketplace(marketplaceJson) + const codexMarketplacePath = join(ROOT, ".agents", "plugins", "marketplace.json") + const codexWritten = writeIfChanged(codexMarketplacePath, JSON.stringify(codexMarketplace, null, 2) + "\n") + console.log() + if (codexWritten) { + console.log(`Codex marketplace written: ${codexMarketplacePath}`) + filesWritten++ + } else { + console.log(`Codex marketplace up to date: ${codexMarketplacePath}`) + } + + // Generate Cursor marketplace.json from the Claude one (local plugins only). + const cursorMarketplace = toCursorMarketplace(marketplaceJson) + const cursorMarketplacePath = join(ROOT, ".cursor-plugin", "marketplace.json") + const cursorWritten = writeIfChanged(cursorMarketplacePath, JSON.stringify(cursorMarketplace, null, 2) + "\n") + if (cursorWritten) { + console.log(`Cursor marketplace written: ${cursorMarketplacePath}`) + filesWritten++ + } else { + console.log(`Cursor marketplace up to date: ${cursorMarketplacePath}`) + } + + console.log(`\nProcessed ${pluginsProcessed} plugins, wrote ${filesWritten} file(s).`) + if (benignSkipped.length > 0) console.log(`Skipped (no manifest): ${benignSkipped.join(", ")}`) + if (failed.length > 0) { + console.error(`\n${failed.length} plugin(s) failed:`) + for (const f of failed) console.error(` - ${f.name}: ${f.error}`) + process.exit(1) + } + console.log("Done.") +} + +if (import.meta.main) { + await generateMultiFormat() +} diff --git a/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md b/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md new file mode 100644 index 00000000..29aff948 --- /dev/null +++ b/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md @@ -0,0 +1,131 @@ +--- +name: Migrating Gemini Extensions +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". +--- + +# Migrating a Gemini Extension to a Claude Code Plugin + +Preserve functionality while adopting Claude Code best practices. Keep `gemini-extension.json` for +backwards compatibility. + +## Phase 1 — Analysis + +1. Read the existing structure: `gemini-extension.json`, any context file (e.g. `GEMINI.md`), + commands, MCP servers/tools, custom scripts. +2. Map components: Gemini context → Claude skill (preferred) or commands/agents; Gemini tools → MCP + servers; Gemini scripts → hook scripts. + +## Phase 2 — Structure + +``` +plugin-name/ +├── .claude-plugin/ +│ └── plugin.json # NEW: Claude manifest (source of truth) +├── gemini-extension.json # KEEP: backwards compatibility +├── skills/ # NEW (preferred): convert context → a skill +├── commands/ # optional: convert context → commands +├── hooks/ +│ ├── hooks.json # if you keep SessionStart context loading +│ └── scripts/ # ADAPT: update paths to ${CLAUDE_PLUGIN_ROOT} +└── README.md # UPDATE: add Claude install instructions +``` + +## Phase 3 — Convert the manifest + +`gemini-extension.json` → `.claude-plugin/plugin.json`: + +```json +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "plugin-name", + "version": "1.0.0", + "description": "Description", + "author": { "name": "...", "url": "https://github.com/..." }, + "homepage": "...", + "repository": "...", + "license": "MIT", + "keywords": ["..."] +} +``` + +## Phase 4 — Migrate the context file (prefer a skill) + +**Preferred:** turn `GEMINI.md` into a `skills//SKILL.md` with frontmatter describing when it +should activate. This loads only when relevant instead of every session. See +`../plugin-authoring/references/best-practices.md` for why skills beat SessionStart hooks. + +**Legacy alternative — SessionStart hook** (only if you must load context every session): + +```json +{ + "description": "Load usage instructions at session start", + "hooks": { + "SessionStart": [ + { "matcher": "startup", + "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/context.sh", "timeout": 10 } ] } + ] + } +} +``` + +```bash +#!/usr/bin/env bash +set -euo pipefail +CONTEXT_FILE="${CLAUDE_PLUGIN_ROOT}/hooks/CONTEXT.md" +if [ -f "$CONTEXT_FILE" ]; then + jq -n --arg context "$(cat "$CONTEXT_FILE")" \ + '{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": $context } }' +fi +``` + +Then `chmod +x hooks/context.sh`. + +## Phase 5 — Migrate MCP servers + +Gemini tools → inline `mcpServers` (or `.mcp.json`): + +```json +{ + "mcpServers": { + "server-name": { + "command": "npx", + "args": ["-y", "@package/name"], + "env": { "API_KEY": "${PLUGIN_API_KEY:-}" } + } + } +} +``` + +Key changes: `${CLAUDE_PLUGIN_ROOT}` for local paths, `${VAR:-}` for optional env vars, `npx -y` for +npm packages. + +## Phase 6 — Update docs + +Add a Claude Code install section to the README (marketplace add + `/plugin install ...`) and keep +the Gemini CLI section for backwards compatibility. + +## Phase 7 — Test + +- Claude Code: `claude --debug`, `/plugin list`, run the plugin's components. +- Gemini CLI still works: `gemini ext list`. +- Confirm commands, hooks, and MCP servers behave. + +## Checklist + +- [ ] Create `.claude-plugin/plugin.json`; keep `gemini-extension.json`. +- [ ] Convert context to a skill (or SessionStart hook); make hook scripts executable. +- [ ] Update paths to `${CLAUDE_PLUGIN_ROOT}`; migrate MCP config. +- [ ] Update README (both install methods); update CHANGELOG. +- [ ] Test with Claude Code and Gemini CLI. Submit an upstream PR for third-party extensions. + +## Common issues + +- **Context not loading** → hook script not executable or bad JSON output. +- **MCP server won't start** → paths not using `${CLAUDE_PLUGIN_ROOT}`, or command missing. +- **Commands not appearing** → `commands/` nested in `.claude-plugin/` instead of at root. + +## Reference + +- `docs/lessons-learned/context7.md` — a complete migration example. +- [Claude Code Plugin Documentation](https://docs.claude.com/en/docs/claude-code/plugins) +- [MCP Documentation](https://modelcontextprotocol.io/) diff --git a/plugins/plugin-dev/skills/plugin-authoring/SKILL.md b/plugins/plugin-dev/skills/plugin-authoring/SKILL.md index ed4aa31f..5e940590 100644 --- a/plugins/plugin-dev/skills/plugin-authoring/SKILL.md +++ b/plugins/plugin-dev/skills/plugin-authoring/SKILL.md @@ -1,6 +1,6 @@ --- name: Authoring Plugins (Multi-Runtime) -description: Author a plugin once in Claude Code format, then generate Codex, Antigravity, and Cursor manifests from it so one directory loads in all four runtimes. Use when creating, scaffolding, or editing a plugin in this marketplace, 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", or "Codex/Cursor/Antigravity plugin". +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". --- # Plugin Authoring — Multi-Runtime @@ -9,11 +9,8 @@ This marketplace ships the **same plugin directory across four runtimes**. You a Claude Code format (the source of truth) and **generate** the rest. Never hand-write the Codex, Antigravity, or Cursor manifests. -## The one rule: edit Claude → generate the rest - ``` -.claude-plugin/plugin.json (or root plugin.json) ← author here, source of truth - │ +.claude-plugin/plugin.json ← author here, source of truth │ bun scripts/cli.ts multi-format ▼ .codex-plugin/plugin.json + .mcp.json ← generated @@ -23,58 +20,56 @@ plugin.json (root) + mcp_config.json + hooks.json ← generated (Antigravity) .cursor-plugin/marketplace.json ← generated (Cursor marketplace) ``` -Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live **once** at the plugin root and are -referenced by every manifest. Only manifest-level fields differ per runtime. - ## Workflow -1. **Scaffold / edit** the plugin in Claude Code format — run `/plugin-dev:scaffold` for a new one. - Components go at the plugin **root**, never inside `.claude-plugin/`: +1. **Scaffold / edit** the plugin in Claude Code format. Components go at the plugin **root**, never + inside `.claude-plugin/`: ``` plugins// - ├── .claude-plugin/plugin.json # manifest only (or root plugin.json — see below) - ├── commands/ # at root - ├── agents/ # at root - ├── skills/ # at root - └── hooks/hooks.json # at root + ├── .claude-plugin/plugin.json # manifest only (source of truth) + ├── commands/ agents/ skills/ # at root, auto-discovered + └── hooks/hooks.json # at root ``` -2. **Register** in the source-of-truth marketplace `.claude-plugin/marketplace.json` - (`source: "./plugins/"`). Claude-only fields like `relevance` live **only** here. -3. **Generate** the other runtimes: `/plugin-dev:multi-format` (or `bun scripts/cli.ts multi-format`). + Author the manifest from `references/plugin-json-spec.md` — start with valid defaults, no + `[TODO: ...]` placeholders. Prefer **skills** over SessionStart hooks (they load only when + relevant). Full quality checklist: `references/best-practices.md`. +2. **Register** in `.claude-plugin/marketplace.json` (`source: "./plugins/"`). Claude-only + fields like `relevance` live **only** here. For a new plugin, wire the companion files + (release-please, README) too — see `references/multi-runtime-manifests.md`. +3. **Generate** the other runtimes: + ```bash + bun scripts/cli.ts multi-format + ``` + The generator is bundled in this plugin (`scripts/run.ts` + `scripts/multi-format.ts`). Details, + the per-runtime mapping table, and the `$schema` each manifest carries: + `references/multi-runtime-manifests.md`. 4. **Scope the diff** — the generator rewrites *all* local plugins; `git restore` any unrelated churn so the commit stays atomic. -5. **Validate**: `/plugin-dev:validate`, then `claude plugin validate plugins/`. +5. **Validate** — `claude plugin validate plugins/`; deeper audit via the `validating-plugins` + skill. + +## Manifest location -## Root-level `plugin.json` plugins +Every local plugin's Claude source is `.claude-plugin/plugin.json`; the root `plugin.json` is the +**generated** Antigravity manifest. Edit the `.claude-plugin/` file — never the root one — then +re-run `multi-format`. -A few plugins (e.g. `plugin-dev`, `bun`) use a **root** `plugin.json` that serves as *both* the -Claude Code manifest *and* the Antigravity manifest. Edit that root file as the source of truth; -`multi-format` still regenerates `.codex-plugin/` and `.cursor-plugin/` from it. +## References (load on demand) -## Companion files for a NEW plugin +- `references/plugin-json-spec.md` — manifest + marketplace field guide with canonical samples. +- `references/multi-runtime-manifests.md` — per-runtime mapping, the generator, diff scoping. +- `references/best-practices.md` — quality checklist, pitfalls, testing. -Wire these in the same change (see `.claude/rules/marketplace-sync.md`): +## Related -- `.claude-plugin/marketplace.json` — add the plugin entry (source of truth). -- `release-please-config.json` + `.release-please-manifest.json` — add a `plugins/` entry - covering **every** version-bearing manifest the plugin ships (`.claude-plugin/plugin.json` and, - when multi-format generates them, `.codex-plugin/plugin.json` + root `plugin.json` + - `.cursor-plugin/plugin.json`). *Only if the plugin is release-managed.* -- `README.md` — add the plugin under **Built-in Plugins**. +- `validating-plugins` — audit a plugin's manifest and structure before release. +- `migrating-gemini-extensions` — port a Gemini CLI extension to Claude Code. +- Cross-runtime creation skills: `claude-code-plugin-builder` (Claude Code component deep-dive), + `plugin-creator` (Codex-native), `create-plugin-scaffold` (Cursor-native). ## What NOT to do -- ❌ Hand-edit `.codex-plugin/`, `.cursor-plugin/`, generated root Antigravity `plugin.json`, or the - Codex/Cursor `marketplace.json` — re-run `multi-format`. -- ❌ Put `relevance` (or other Claude-only fields) in the Codex/Cursor marketplaces — the generator - intentionally drops them. +- ❌ Hand-edit `.codex-plugin/`, `.cursor-plugin/`, the generated root Antigravity `plugin.json`, or + the Codex/Cursor `marketplace.json` — re-run `multi-format`. +- ❌ Put `relevance` (or other Claude-only fields) in the Codex/Cursor marketplaces. - ❌ Nest `commands/` / `agents/` / `skills/` inside `.claude-plugin/` — they won't load. - -## Related - -- Deep Claude Code component reference (commands, agents, skills, hooks, MCP, Gemini migration): - the `claude-code-plugin-builder` skill. -- Codex-native creation (inside Codex): the `plugin-creator` skill. -- Cursor-native creation (inside Cursor): the `create-plugin-scaffold` skill. -- Commands: `/plugin-dev:scaffold`, `/plugin-dev:multi-format`, `/plugin-dev:validate`, - `/plugin-dev:migrate-gemini`, `/plugin-dev:best-practices`. diff --git a/plugins/plugin-dev/skills/plugin-authoring/references/best-practices.md b/plugins/plugin-dev/skills/plugin-authoring/references/best-practices.md new file mode 100644 index 00000000..7a35c123 --- /dev/null +++ b/plugins/plugin-dev/skills/plugin-authoring/references/best-practices.md @@ -0,0 +1,68 @@ +# Plugin development best practices + +Standards for reviewing a plugin or answering plugin-development questions. When reviewing, list +specific issues with `file:line` references, prioritise by severity, and include fix examples. + +## Manifest (`.claude-plugin/plugin.json`) + +- Required `name` (kebab-case, no spaces); canonical short package name (`next`, not `nextjs`). +- Quality metadata: `version` (semver), `description`, `author`, `repository`, relevant `keywords`. +- Optional `$schema`: `https://json.schemastore.org/claude-code-plugin-manifest.json`. +- Component paths start with `./` and are relative; `${CLAUDE_PLUGIN_ROOT}` for all plugin paths. + +## Directory structure + +- Manifest at `.claude-plugin/plugin.json`; components (`commands/`, `agents/`, `skills/`, `hooks/`) + at the plugin **root** — never nested inside `.claude-plugin/`. +- Helper scripts in `scripts/` or `hooks/`; include `README.md`, `LICENSE`, `CHANGELOG.md`. + +## Skills (preferred over SessionStart hooks) + +- `skills//SKILL.md` with frontmatter: `name` in gerund form, `description` with specific + activation triggers; keep the body under ~500 lines. Push deep material into a `references/` + subfolder and point to it (progressive disclosure). +- Skills load only when relevant → lower token cost and intelligent activation. Prefer them to + SessionStart hooks, which load every session regardless of need. +- Set `allowed-tools` when the skill drives specific MCP tools. + +## Commands + +- Markdown files, kebab-case names, auto-namespaced `/plugin-name:command`. +- Clear, focused prompts; include usage examples and parameter descriptions. + +## Hooks + +- Configure in `hooks/hooks.json` (auto-loaded) or inline in `plugin.json`. +- Make scripts executable (`chmod +x`); reference with `${CLAUDE_PLUGIN_ROOT}`. +- Return proper JSON (`hookSpecificOutput`); handle failures gracefully; keep fast (<1s). + +## MCP servers + +- `npx -y` for npm-published servers; `${VAR:-}` for optional env vars; `${CLAUDE_PLUGIN_ROOT}` for + local servers. Verify the server starts and its tools are reachable. + +## Documentation & distribution + +- README: installation (marketplace commands), usage, features, examples. Maintain a changelog. +- Semantic versioning (MAJOR.MINOR.PATCH); tag releases; submit to the marketplace. + +## Common pitfalls + +- ❌ Absolute or hardcoded paths (missing `${CLAUDE_PLUGIN_ROOT}`). +- ❌ Commands/agents/skills/hooks inside `.claude-plugin/`. +- ❌ Non-executable hook scripts; invalid JSON; missing required manifest fields. +- ❌ Hand-editing generated Codex/Cursor/Antigravity manifests. + +## Testing & validation + +- Local: `claude --debug` to watch plugin loading; test every slash command and hook; confirm MCP + tools are reachable. Run `claude plugin validate plugins/` (see the `validating-plugins` + skill for the full checklist). + +## Key resources + +- [Claude Code Plugins](https://docs.claude.com/en/docs/claude-code/plugins) +- [Plugin Reference](https://docs.claude.com/en/docs/claude-code/plugins-reference) +- [Plugin Marketplaces](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces) +- [Plugin relevance / suggestions](https://code.claude.com/docs/en/plugin-relevance) +- [MCP Documentation](https://modelcontextprotocol.io/) diff --git a/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md b/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md new file mode 100644 index 00000000..bb72413a --- /dev/null +++ b/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md @@ -0,0 +1,82 @@ +# Multi-runtime manifests & the generator + +This repository ships the **same plugin directory across four runtimes**. The Claude Code manifest +is the source of truth; the others are generated. + +| Runtime | Manifest path | Companion files | Status | +|--------------|------------------------------|---------------------------------------|--------| +| Claude Code | `.claude-plugin/plugin.json` | `hooks/hooks.json`, inline `mcpServers` | **source of truth** | +| Codex | `.codex-plugin/plugin.json` | `.mcp.json` (when MCP present) | generated | +| Antigravity | `plugin.json` (root) | `mcp_config.json`, root `hooks.json` | generated | +| Cursor | `.cursor-plugin/plugin.json` | none — components auto-discovered | generated | + +Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live **once** at the plugin root and are +referenced by every manifest. Only manifest-level fields differ per runtime. Each generated manifest +carries its own `$schema`: + +- Claude source — `https://json.schemastore.org/claude-code-plugin-manifest.json` +- Antigravity root `plugin.json` — `https://antigravity.google/schemas/v1/plugin.json` + +## Where the generator lives + +The generator is bundled **inside plugin-dev** so the plugin is self-contained: + +- `plugins/plugin-dev/scripts/multi-format.ts` — the per-runtime format-mapping rules. +- `plugins/plugin-dev/scripts/run.ts` — the CLI wrapper / entry point. + +Run it either way (same code): + +```bash +bun scripts/cli.ts multi-format # repo-root entry (delegates into the plugin) +bun plugins/plugin-dev/scripts/run.ts # plugin-local entry +``` + +For every local plugin (`source: "./plugins/..."`) it emits: + +- `plugins//.codex-plugin/plugin.json` (+ `.mcp.json` when the plugin defines `mcpServers`) +- `plugins//plugin.json` + `mcp_config.json` + root `hooks.json` (Antigravity) +- `plugins//.cursor-plugin/plugin.json` +- `.agents/plugins/marketplace.json` (Codex marketplace, local plugins only) +- `.cursor-plugin/marketplace.json` (Cursor marketplace, local plugins only) + +It writes only files whose content changed, and prints per-plugin status +(`wrote N file(s)` / `up to date` / `skipped`). + +## New plugin? Wire the marketplace entry FIRST + +The generator resolves each plugin's metadata (notably `category`) from the marketplace entry. A +plugin dir not yet listed in `.claude-plugin/marketplace.json` is generated with no entry — its +Codex manifest falls back to the default category and it is omitted from the emitted Codex/Cursor +marketplaces. Add the entry, then run the generator. + +## Scope the diff to your change + +⚠️ **`multi-format` rewrites all local plugins, not just the one you touched.** If the committed +artifacts had pre-existing drift, the command produces a large unrelated diff. Keep it atomic: + +```bash +git status --short +git restore plugins//... # revert churn from plugins you did NOT intend to change +``` + +Commit only the files for the plugin(s) you actually changed. + +## Claude-only fields do not propagate + +Fields that exist only in the Claude runtime — notably `relevance` — are intentionally **not** +copied to the Codex/Cursor marketplaces. Author them only in `.claude-plugin/marketplace.json`. + +## Companion files that move together (new plugin) + +See `.claude/rules/marketplace-sync.md`: + +- `release-please-config.json` + `.release-please-manifest.json` — a `plugins/` entry whose + `extra-files` cover every version-bearing manifest the plugin ships (`.claude-plugin/plugin.json` + and, when multi-format generates them, `.codex-plugin/plugin.json` + root `plugin.json` + + `.cursor-plugin/plugin.json`). Only if the plugin is release-managed. +- `README.md` — add the plugin under **Built-in Plugins**. + +## Do not hand-edit generated files + +Never edit `.codex-plugin/`, `.cursor-plugin/`, the root Antigravity `plugin.json`, or the generated +`.agents/plugins/marketplace.json` / `.cursor-plugin/marketplace.json` — re-run the generator. diff --git a/plugins/plugin-dev/skills/plugin-authoring/references/plugin-json-spec.md b/plugins/plugin-dev/skills/plugin-authoring/references/plugin-json-spec.md new file mode 100644 index 00000000..2a76995b --- /dev/null +++ b/plugins/plugin-dev/skills/plugin-authoring/references/plugin-json-spec.md @@ -0,0 +1,93 @@ +# Plugin manifest & marketplace spec (Claude Code) + +The Claude Code manifest at `.claude-plugin/plugin.json` is the **single source of truth**. The +Codex, Antigravity, and Cursor manifests are generated from it — never hand-write them (see +`multi-runtime-manifests.md`). + +## `.claude-plugin/plugin.json` — full sample + +```json +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "plugin-name", + "version": "1.2.0", + "description": "Brief plugin description", + "author": { + "name": "Author Name", + "email": "author@example.com", + "url": "https://github.com/author" + }, + "homepage": "https://docs.example.com/plugin", + "repository": "https://github.com/author/plugin", + "license": "MIT", + "keywords": ["keyword1", "keyword2"], + "mcpServers": { + "server-name": { + "command": "npx", + "args": ["-y", "@org/package"], + "env": { "API_KEY": "${PLUGIN_API_KEY:-}" } + } + } +} +``` + +## Field guide + +- `$schema` (`string`, optional) — `https://json.schemastore.org/claude-code-plugin-manifest.json`. + For editor autocomplete/validation only; Claude Code ignores it at load time. +- `name` (`string`, **required**) — kebab-case, no spaces; the canonical short package name + (`next`, not `nextjs`). Used as the component namespace (`/:`). +- `version` (`string`) — semver `MAJOR.MINOR.PATCH`. +- `description` (`string`) — one-line purpose summary. +- `author` (`object`) — `name`, `email`, `url`. Note: the multi-format generator strips `email` + from downstream Codex/Antigravity/Cursor manifests, keeping only `name` + a validated https `url`. +- `homepage`, `repository`, `license` (`string`). +- `keywords` (`string[]`) — for discoverability. +- `mcpServers` (`object | string`) — inline server map, or a path string to an external file. + Use `npx -y` for npm packages and `${VAR:-}` for optional env vars. + +### Component fields — usually omit them + +`commands/`, `agents/`, `skills/`, `hooks/hooks.json` are **auto-discovered** from default folders at +the plugin root. Only set an explicit `commands` / `agents` / `skills` / `hooks` path when using a +non-default location. When set, paths must start with `./`, be relative, and use +`${CLAUDE_PLUGIN_ROOT}` inside command/hook scripts. + +## Marketplace entry (`.claude-plugin/marketplace.json`) + +Each local plugin has an entry in the `plugins[]` array. Order = render order — append new entries. + +```json +{ + "name": "plugin-name", + "description": "Brief description", + "category": "tooling", + "keywords": ["plugin", "development"], + "tags": ["tooling"], + "source": "./plugins/plugin-name", + "relevance": { + "topic": "Stripe", + "signals": { + "cli": ["stripe"], + "hosts": ["api.stripe.com"], + "filesRead": ["**/stripe.config.*"], + "manifestDeps": [{ "file": "[/\\\\]package\\.json$", "pattern": "\"stripe\"\\s*:" }] + } + } +} +``` + +- `source` — `"./plugins/"` for a local plugin. +- `category` — stored lower-case here; surfaced capitalised in the generated Codex/Cursor manifests. +- `relevance` (optional, **Claude-only**) — [plugin suggestion signals](https://code.claude.com/docs/en/plugin-relevance). + Only add **high-signal** triggers; a plugin with no reliable trigger should have no `relevance` + block. The generator intentionally does **not** propagate `relevance` to Codex/Cursor. Signal + types: `cwd`, `cli`, `hosts`, `filesRead`, `manifestDeps` (`file` + `pattern` RegExp source; escape + backslashes twice in JSON). + +## Validation + +```bash +claude plugin validate plugins/ +claude plugin validate .claude-plugin/marketplace.json +``` diff --git a/plugins/plugin-dev/skills/validating-plugins/SKILL.md b/plugins/plugin-dev/skills/validating-plugins/SKILL.md new file mode 100644 index 00000000..8366c4eb --- /dev/null +++ b/plugins/plugin-dev/skills/validating-plugins/SKILL.md @@ -0,0 +1,91 @@ +--- +name: Validating Plugins +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". +--- + +# Validating a Plugin + +Perform a comprehensive validation of the plugin and report findings grouped by severity. Start with +the CLI validator, then check what it does not cover. + +```bash +claude plugin validate plugins/ +claude plugin validate .claude-plugin/marketplace.json +``` + +## 1. Manifest (`.claude-plugin/plugin.json`) + +- ✅ Exists at the correct path (`.claude-plugin/plugin.json`, **not** root only). +- ✅ Valid JSON syntax. +- ✅ Required: `name` (string, kebab-case). +- ✅ Recommended: `version` (valid semver), `description`, `author`, `repository`, `keywords`. +- ✅ Optional `$schema` set to `https://json.schemastore.org/claude-code-plugin-manifest.json`. +- ✅ Custom component paths start with `./` and are relative. +- ✅ Env/command paths use `${CLAUDE_PLUGIN_ROOT}`. + +## 2. Directory structure + +- ✅ `.claude-plugin/` holds `plugin.json` only. +- ✅ `commands/`, `agents/`, `skills/`, `hooks/` are at the plugin **root**. +- ✅ `README.md` and `LICENSE` present. +- ❌ No commands/agents/skills/hooks nested inside `.claude-plugin/`. + +## 3. Commands + +For each `.md` in `commands/`: valid Markdown, kebab-case filename, clear actionable content, +frontmatter where useful, descriptive summary. + +## 4. Skills + +For each `skills//SKILL.md`: YAML frontmatter present with `name` (gerund form) and a +`description` that carries specific activation triggers; body under ~500 lines. + +## 5. Hooks + +- ✅ `hooks/hooks.json` is valid JSON. +- ✅ Hook scripts are executable (`chmod +x`). +- ✅ Scripts use `${CLAUDE_PLUGIN_ROOT}`. +- ✅ Output shape matches the hook spec (`hookSpecificOutput`). +- ⚠️ Hook event names are valid. + +## 6. MCP servers + +- ✅ Valid config in inline `mcpServers` or `.mcp.json`. +- ✅ Command paths exist; `npx -y` pattern where applicable. +- ✅ Optional env vars use the `${VAR:-}` pattern. + +## 7. Agents & documentation + +- ✅ Each agent has a clear description, capabilities, when-to-use guidance, and examples. +- ✅ README has installation instructions, usage examples, features, and a license; `CHANGELOG.md` + recommended. + +## Process + +1. Scan the plugin directory (Glob/Read) and confirm required files exist. +2. Parse and validate the manifest fields, paths, and env vars. +3. Check component integrity and script permissions. +4. Report findings with specific `file:line` references and actionable fixes. + +## Output format + +``` +# Plugin Validation Report: + +## Critical Issues ❌ +- [FILE:LINE] Issue — Fix: specific solution + +## Warnings ⚠️ +- [FILE:LINE] Issue — Recommendation: suggested improvement + +## Suggestions 💡 +- [FILE:LINE] Enhancement — Suggestion: how to improve + +## Summary +- Total: X · Critical: X · Warnings: X · Suggestions: X + +## Next Steps +1. Fix critical issues first +2. Address warnings for quality +3. Consider suggestions +``` diff --git a/scripts/cli.ts b/scripts/cli.ts index 119ea65b..ed29e244 100644 --- a/scripts/cli.ts +++ b/scripts/cli.ts @@ -15,7 +15,12 @@ import { dirname, join, resolve } from "node:path" import type { SubmoduleMeta } from "./meta.ts" import { extensions, submodules, vendors } from "./meta.ts" import { convertMcpServerPaths, parseToml } from "./extension-helpers.ts" -import { generateForPlugin, toCodexMarketplace, toCursorMarketplace, writeIfChanged, type ClaudeMarketplace, type MarketplaceEntry } from "./multi-format.ts" +// The multi-format generator now lives inside the plugin-dev plugin so that +// plugin is self-contained. Re-export it here so the historical +// `bun scripts/cli.ts multi-format` invocation keeps working. +import { generateMultiFormat } from "../plugins/plugin-dev/scripts/run.ts" + +export { generateMultiFormat } const ROOT = resolve(import.meta.dirname!, "..") const PLUGINS_DIR = join(ROOT, "plugins") @@ -691,93 +696,10 @@ export async function cleanup() { // --------------------------------------------------------------------------- // multi-format // --------------------------------------------------------------------------- -/** - * Generate Codex, Antigravity, and Cursor manifests for every local plugin so - * the same plugin directory loads across runtimes. See scripts/multi-format.ts - * for the format mapping rules. - */ -export async function generateMultiFormat() { - const marketplaceJsonPath = join(ROOT, ".claude-plugin", "marketplace.json") - if (!existsSync(marketplaceJsonPath)) { - console.error(`! marketplace not found: ${marketplaceJsonPath}`) - process.exit(1) - } - - const marketplaceJson = JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace - const entriesByPluginDir = new Map() - for (const entry of marketplaceJson.plugins) { - if (typeof entry.source === "string" && entry.source.startsWith("./plugins/")) { - const dirName = entry.source.replace(/^\.\/plugins\//, "") - entriesByPluginDir.set(dirName, entry) - } - } - - console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n") - let pluginsProcessed = 0 - let filesWritten = 0 - const benignSkipped: string[] = [] - const failed: { name: string; error: string }[] = [] - const pluginDirs = readdirSync(PLUGINS_DIR, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => d.name) - .sort() - - for (const dirName of pluginDirs) { - const pluginDir = join(PLUGINS_DIR, dirName) - const entry = entriesByPluginDir.get(dirName) - try { - const result = generateForPlugin(pluginDir, entry) - if (result.reason) { - console.log(` ${dirName}: skipped (${result.reason})`) - benignSkipped.push(dirName) - continue - } - pluginsProcessed++ - if (result.written.length === 0) { - console.log(` ${dirName}: up to date`) - } else { - console.log(` ${dirName}: wrote ${result.written.length} file(s)`) - filesWritten += result.written.length - } - } catch (err) { - const msg = err instanceof Error ? err.message : String(err) - console.error(` ${dirName}: FAILED — ${msg}`) - failed.push({ name: dirName, error: msg }) - } - } - - // Generate Codex marketplace.json from the Claude one. - const codexMarketplace = toCodexMarketplace(marketplaceJson) - const codexMarketplacePath = join(ROOT, ".agents", "plugins", "marketplace.json") - const codexWritten = writeIfChanged(codexMarketplacePath, JSON.stringify(codexMarketplace, null, 2) + "\n") - console.log() - if (codexWritten) { - console.log(`Codex marketplace written: ${codexMarketplacePath}`) - filesWritten++ - } else { - console.log(`Codex marketplace up to date: ${codexMarketplacePath}`) - } - - // Generate Cursor marketplace.json from the Claude one (local plugins only). - const cursorMarketplace = toCursorMarketplace(marketplaceJson) - const cursorMarketplacePath = join(ROOT, ".cursor-plugin", "marketplace.json") - const cursorWritten = writeIfChanged(cursorMarketplacePath, JSON.stringify(cursorMarketplace, null, 2) + "\n") - if (cursorWritten) { - console.log(`Cursor marketplace written: ${cursorMarketplacePath}`) - filesWritten++ - } else { - console.log(`Cursor marketplace up to date: ${cursorMarketplacePath}`) - } - - console.log(`\nProcessed ${pluginsProcessed} plugins, wrote ${filesWritten} file(s).`) - if (benignSkipped.length > 0) console.log(`Skipped (no manifest): ${benignSkipped.join(", ")}`) - if (failed.length > 0) { - console.error(`\n${failed.length} plugin(s) failed:`) - for (const f of failed) console.error(` - ${f.name}: ${f.error}`) - process.exit(1) - } - console.log("Done.") -} +// The generator now lives in plugins/plugin-dev/scripts/ (run.ts + multi-format.ts) +// so the plugin is self-contained. `generateMultiFormat` is re-exported from the +// import block at the top of this file; the `multi-format` command below delegates +// to it. // --------------------------------------------------------------------------- // Main diff --git a/vitest.config.ts b/vitest.config.ts index 7c1f61f5..126d2f05 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ { root: '.', test: { - include: ['scripts/**/*.test.ts'], + include: ['scripts/**/*.test.ts', 'plugins/plugin-dev/scripts/**/*.test.ts'], environment: 'node', }, }, From 94f318da0ab55ff90e967a9560a586b3ed8403a9 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Wed, 8 Jul 2026 10:21:24 +0900 Subject: [PATCH 2/2] 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) --- plugins/plugin-dev/.claude-plugin/plugin.json | 3 + plugins/plugin-dev/.codex-plugin/plugin.json | 3 +- plugins/plugin-dev/scripts/run.ts | 101 ++++++++++-------- .../migrating-gemini-extensions/SKILL.md | 2 +- .../skills/plugin-authoring/SKILL.md | 2 +- .../references/multi-runtime-manifests.md | 7 +- .../skills/validating-plugins/SKILL.md | 2 +- 7 files changed, 69 insertions(+), 51 deletions(-) diff --git a/plugins/plugin-dev/.claude-plugin/plugin.json b/plugins/plugin-dev/.claude-plugin/plugin.json index 0aba57a9..e9807120 100644 --- a/plugins/plugin-dev/.claude-plugin/plugin.json +++ b/plugins/plugin-dev/.claude-plugin/plugin.json @@ -18,5 +18,8 @@ "validation", "guidelines", "scaffold" + ], + "skills": [ + "./skills/" ] } \ No newline at end of file diff --git a/plugins/plugin-dev/.codex-plugin/plugin.json b/plugins/plugin-dev/.codex-plugin/plugin.json index 9a27c61d..dd433e56 100644 --- a/plugins/plugin-dev/.codex-plugin/plugin.json +++ b/plugins/plugin-dev/.codex-plugin/plugin.json @@ -30,5 +30,6 @@ "validation", "guidelines", "scaffold" - ] + ], + "skills": "./skills/" } diff --git a/plugins/plugin-dev/scripts/run.ts b/plugins/plugin-dev/scripts/run.ts index 99fd51a4..8b8e9b15 100644 --- a/plugins/plugin-dev/scripts/run.ts +++ b/plugins/plugin-dev/scripts/run.ts @@ -30,88 +30,101 @@ import { const ROOT = resolve(import.meta.dirname!, "..", "..", "..") const PLUGINS_DIR = join(ROOT, "plugins") -/** - * Generate Codex, Antigravity, and Cursor manifests for every local plugin so - * the same plugin directory loads across runtimes. - */ -export async function generateMultiFormat() { +interface PluginRunResult { + pluginsProcessed: number + filesWritten: number + benignSkipped: string[] + failed: { name: string; error: string }[] +} + +/** Read the Claude marketplace, or exit(1) if it is missing. */ +function loadMarketplace(): ClaudeMarketplace { const marketplaceJsonPath = join(ROOT, ".claude-plugin", "marketplace.json") if (!existsSync(marketplaceJsonPath)) { console.error(`! marketplace not found: ${marketplaceJsonPath}`) process.exit(1) } + return JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace +} - const marketplaceJson = JSON.parse(readFileSync(marketplaceJsonPath, "utf-8")) as ClaudeMarketplace +/** Index local (`./plugins/...`) marketplace entries by their plugin directory name. */ +function localEntriesByDir(marketplaceJson: ClaudeMarketplace): Map { const entriesByPluginDir = new Map() for (const entry of marketplaceJson.plugins) { if (typeof entry.source === "string" && entry.source.startsWith("./plugins/")) { - const dirName = entry.source.replace(/^\.\/plugins\//, "") - entriesByPluginDir.set(dirName, entry) + entriesByPluginDir.set(entry.source.replace(/^\.\/plugins\//, ""), entry) } } + return entriesByPluginDir +} - console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n") - let pluginsProcessed = 0 - let filesWritten = 0 - const benignSkipped: string[] = [] - const failed: { name: string; error: string }[] = [] +/** Generate per-runtime manifests for every plugin directory, logging per-plugin status. */ +function processPlugins(entriesByPluginDir: Map): PluginRunResult { + const out: PluginRunResult = { pluginsProcessed: 0, filesWritten: 0, benignSkipped: [], failed: [] } const pluginDirs = readdirSync(PLUGINS_DIR, { withFileTypes: true }) .filter(d => d.isDirectory()) .map(d => d.name) .sort() for (const dirName of pluginDirs) { - const pluginDir = join(PLUGINS_DIR, dirName) - const entry = entriesByPluginDir.get(dirName) try { - const result = generateForPlugin(pluginDir, entry) + const result = generateForPlugin(join(PLUGINS_DIR, dirName), entriesByPluginDir.get(dirName)) if (result.reason) { console.log(` ${dirName}: skipped (${result.reason})`) - benignSkipped.push(dirName) + out.benignSkipped.push(dirName) continue } - pluginsProcessed++ + out.pluginsProcessed++ if (result.written.length === 0) { console.log(` ${dirName}: up to date`) } else { console.log(` ${dirName}: wrote ${result.written.length} file(s)`) - filesWritten += result.written.length + out.filesWritten += result.written.length } } catch (err) { const msg = err instanceof Error ? err.message : String(err) console.error(` ${dirName}: FAILED — ${msg}`) - failed.push({ name: dirName, error: msg }) + out.failed.push({ name: dirName, error: msg }) } } + return out +} - // Generate Codex marketplace.json from the Claude one. - const codexMarketplace = toCodexMarketplace(marketplaceJson) - const codexMarketplacePath = join(ROOT, ".agents", "plugins", "marketplace.json") - const codexWritten = writeIfChanged(codexMarketplacePath, JSON.stringify(codexMarketplace, null, 2) + "\n") +/** Emit the Codex + Cursor marketplace files from the Claude marketplace; returns files written. */ +function writeMarketplaces(marketplaceJson: ClaudeMarketplace): number { + const targets: { label: string; path: string; doc: unknown }[] = [ + { label: "Codex", path: join(ROOT, ".agents", "plugins", "marketplace.json"), doc: toCodexMarketplace(marketplaceJson) }, + { label: "Cursor", path: join(ROOT, ".cursor-plugin", "marketplace.json"), doc: toCursorMarketplace(marketplaceJson) }, + ] console.log() - if (codexWritten) { - console.log(`Codex marketplace written: ${codexMarketplacePath}`) - filesWritten++ - } else { - console.log(`Codex marketplace up to date: ${codexMarketplacePath}`) + let written = 0 + for (const { label, path, doc } of targets) { + if (writeIfChanged(path, JSON.stringify(doc, null, 2) + "\n")) { + console.log(`${label} marketplace written: ${path}`) + written++ + } else { + console.log(`${label} marketplace up to date: ${path}`) + } } + return written +} - // Generate Cursor marketplace.json from the Claude one (local plugins only). - const cursorMarketplace = toCursorMarketplace(marketplaceJson) - const cursorMarketplacePath = join(ROOT, ".cursor-plugin", "marketplace.json") - const cursorWritten = writeIfChanged(cursorMarketplacePath, JSON.stringify(cursorMarketplace, null, 2) + "\n") - if (cursorWritten) { - console.log(`Cursor marketplace written: ${cursorMarketplacePath}`) - filesWritten++ - } else { - console.log(`Cursor marketplace up to date: ${cursorMarketplacePath}`) - } +/** + * Generate Codex, Antigravity, and Cursor manifests for every local plugin so + * the same plugin directory loads across runtimes. + */ +export async function generateMultiFormat() { + const marketplaceJson = loadMarketplace() + + console.log("Generating Codex + Antigravity + Cursor manifests for local plugins...\n") + const run = processPlugins(localEntriesByDir(marketplaceJson)) + const filesWritten = run.filesWritten + writeMarketplaces(marketplaceJson) - console.log(`\nProcessed ${pluginsProcessed} plugins, wrote ${filesWritten} file(s).`) - if (benignSkipped.length > 0) console.log(`Skipped (no manifest): ${benignSkipped.join(", ")}`) - if (failed.length > 0) { - console.error(`\n${failed.length} plugin(s) failed:`) - for (const f of failed) console.error(` - ${f.name}: ${f.error}`) + console.log(`\nProcessed ${run.pluginsProcessed} plugins, wrote ${filesWritten} file(s).`) + if (run.benignSkipped.length > 0) console.log(`Skipped (no manifest): ${run.benignSkipped.join(", ")}`) + if (run.failed.length > 0) { + console.error(`\n${run.failed.length} plugin(s) failed:`) + for (const f of run.failed) console.error(` - ${f.name}: ${f.error}`) process.exit(1) } console.log("Done.") diff --git a/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md b/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md index 29aff948..0c62decf 100644 --- a/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md +++ b/plugins/plugin-dev/skills/migrating-gemini-extensions/SKILL.md @@ -1,5 +1,5 @@ --- -name: Migrating Gemini Extensions +name: migrating-gemini-extensions 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". --- diff --git a/plugins/plugin-dev/skills/plugin-authoring/SKILL.md b/plugins/plugin-dev/skills/plugin-authoring/SKILL.md index 5e940590..04e4fa59 100644 --- a/plugins/plugin-dev/skills/plugin-authoring/SKILL.md +++ b/plugins/plugin-dev/skills/plugin-authoring/SKILL.md @@ -1,5 +1,5 @@ --- -name: Authoring Plugins (Multi-Runtime) +name: plugin-authoring 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". --- diff --git a/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md b/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md index bb72413a..c3175ad9 100644 --- a/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md +++ b/plugins/plugin-dev/skills/plugin-authoring/references/multi-runtime-manifests.md @@ -11,8 +11,9 @@ is the source of truth; the others are generated. | Cursor | `.cursor-plugin/plugin.json` | none — components auto-discovered | generated | Shared assets (`commands/`, `agents/`, `skills/`, `hooks/`) live **once** at the plugin root and are -referenced by every manifest. Only manifest-level fields differ per runtime. Each generated manifest -carries its own `$schema`: +referenced by every manifest. Only manifest-level fields differ per runtime. The Claude source +manifest and the generated Antigravity manifest each carry a `$schema` (the generated Codex and +Cursor manifests do not): - Claude source — `https://json.schemastore.org/claude-code-plugin-manifest.json` - Antigravity root `plugin.json` — `https://antigravity.google/schemas/v1/plugin.json` @@ -36,7 +37,7 @@ For every local plugin (`source: "./plugins/..."`) it emits: - `plugins//.codex-plugin/plugin.json` (+ `.mcp.json` when the plugin defines `mcpServers`) - `plugins//plugin.json` + `mcp_config.json` + root `hooks.json` (Antigravity) - `plugins//.cursor-plugin/plugin.json` -- `.agents/plugins/marketplace.json` (Codex marketplace, local plugins only) +- `.agents/plugins/marketplace.json` (Codex marketplace; local plugins plus supported external sources) - `.cursor-plugin/marketplace.json` (Cursor marketplace, local plugins only) It writes only files whose content changed, and prints per-plugin status diff --git a/plugins/plugin-dev/skills/validating-plugins/SKILL.md b/plugins/plugin-dev/skills/validating-plugins/SKILL.md index 8366c4eb..6f253948 100644 --- a/plugins/plugin-dev/skills/validating-plugins/SKILL.md +++ b/plugins/plugin-dev/skills/validating-plugins/SKILL.md @@ -1,5 +1,5 @@ --- -name: Validating Plugins +name: validating-plugins 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". ---