diff --git a/README.md b/README.md index 092840ce..ea0dc61b 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ PRPM supports all major AI coding assistants: | `copilot` | [GitHub Copilot](https://github.com/features/copilot) | tool, chatmode | `.github/instructions/`, `.github/chatmodes/` | [Copilot Docs](https://docs.github.com/copilot) | | `kiro` | [Kiro](https://kiro.ai) | rule, agent, tool, hook | `.kiro/steering/`, `.kiro/agents/`, `.kiro/hooks/` | [Kiro Docs](https://docs.kiro.ai) | | `gemini` | [Gemini CLI](https://ai.google.dev/gemini-api/docs) | slash-command | `.gemini/commands/` | [Gemini Docs](https://ai.google.dev/gemini-api/docs) | -| `opencode` | [OpenCode](https://opencode.ai) | agent, slash-command, tool | `.opencode/agent/`, `.opencode/command/`, `.opencode/tool/` | [OpenCode Docs](https://opencode.ai/docs) | +| `opencode` | [OpenCode](https://opencode.ai) | agent, skill, slash-command, tool | `.opencode/agent/`, `.opencode/skills/`, `.opencode/command/`, `.opencode/tool/` | [OpenCode Docs](https://opencode.ai/docs) | | `ruler` | [Ruler](https://ruler.sh) | rule, agent, tool | `.ruler/rules/` | [Ruler Docs](https://ruler.sh/docs) | | `droid` | [Factory Droid](https://factory.ai) | skill, slash-command, hook | `.factory/skills/`, `.factory/commands/`, `.factory/hooks/` | [Factory Droid Docs](https://docs.factory.ai) | | `agents.md` | [Agents.md](https://github.com/agentsmd) | agent, tool | `.agents/`, `AGENTS.md` | [Agents.md Docs](https://github.com/agentsmd) | diff --git a/packages/cli/src/__tests__/install-file-locations.test.ts b/packages/cli/src/__tests__/install-file-locations.test.ts index 4c34ff2b..0b60a026 100644 --- a/packages/cli/src/__tests__/install-file-locations.test.ts +++ b/packages/cli/src/__tests__/install-file-locations.test.ts @@ -727,7 +727,7 @@ Follow TypeScript best practices. ); }); - it('installs opencode skill to native .opencode/skill//.md (native skill support)', async () => { + it('installs opencode skill to native .opencode/skills//SKILL.md (native skill support)', async () => { // OpenCode has native skill support - should NOT use progressive disclosure const mockPackage = { id: 'test-opencode-skill', @@ -748,8 +748,10 @@ Follow TypeScript best practices. await handleInstall('test-opencode-skill', { as: 'opencode' }); - // OpenCode has native skill support - installs to .opencode/skill// - expect(saveFile).toHaveBeenCalledWith('.opencode/skill/test-opencode-skill/test-opencode-skill.md', expect.any(String)); + // OpenCode has native skill support - installs to .opencode/skills// + expect(saveFile).toHaveBeenCalledWith('.opencode/skills/test-opencode-skill/SKILL.md', expect.any(String)); + const destDir = getDestinationDir('opencode', 'skill', 'test-opencode-skill'); + expect(destDir).toBe('.opencode/skills/test-opencode-skill'); // No progressive disclosure manifest update for native format expect(addSkillToManifestMock).not.toHaveBeenCalled(); }); diff --git a/packages/cli/src/__tests__/install-multifile.test.ts b/packages/cli/src/__tests__/install-multifile.test.ts index 55b6e9f8..a2055d44 100644 --- a/packages/cli/src/__tests__/install-multifile.test.ts +++ b/packages/cli/src/__tests__/install-multifile.test.ts @@ -404,7 +404,7 @@ describe('install command - multi-file packages', () => { }); it('should use native skill location for OpenCode skill install', async () => { - // OpenCode has native skill support - skills go to .opencode/skill/ + // OpenCode has native skill support - skills go to .opencode/skills/ const mockPackage = { id: 'nango-skill', name: 'nango-skill', @@ -429,16 +429,16 @@ describe('install command - multi-file packages', () => { await handleInstall('nango-skill', { as: 'opencode' }); - // OpenCode has native skill support - installs to .opencode/skill// + // OpenCode has native skill support - installs to .opencode/skills// expect(saveFile).toHaveBeenCalledWith( - '.opencode/skill/nango-skill/nango-skill.md', + '.opencode/skills/nango-skill/SKILL.md', expect.stringContaining('Builds thin wrapper actions') ); // Verify it did NOT go to the wrong location (.opencode/agent/) const allCalls = (saveFile as Mock).mock.calls; const wrongLocationCalls = allCalls.filter((call: string[]) => - call[0].includes('.opencode/agent/') + call[0].includes('.opencode/agent/') || call[0].includes('.opencode/skill/') ); expect(wrongLocationCalls).toHaveLength(0); }); diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 99d09a03..32fe3aa9 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -71,6 +71,7 @@ import { toCodex, toCursorHooks, validateFormat, + getSubtypeConfig, getNestedIndicator, getFilePatterns, getFileExtension, @@ -1150,6 +1151,7 @@ export async function handleInstall( } // Strip leading dot if present, default to 'md' const fileExtension = registryExtension?.replace(/^\./, '') || 'md'; + const nestedIndicator = getNestedIndicator(effectiveFormat, effectiveSubtype); const packageName = stripAuthorNamespace(packageId); // For Claude skills, use SKILL.md filename in the package directory @@ -1239,6 +1241,9 @@ export async function handleInstall( } else if (effectiveFormat === 'copilot' && effectiveSubtype === 'skill') { // GitHub Copilot skills use SKILL.md inside the skill directory destPath = `${destDir}/SKILL.md`; + } else if (nestedIndicator) { + // Native nested package formats define their required entry file in the registry. + destPath = `${destDir}/${nestedIndicator}`; } else { // Check if this format/subtype needs progressive disclosure // (format supports the subtype but doesn't have native file location for it) @@ -1409,12 +1414,16 @@ export async function handleInstall( // For Claude skills, destDir already includes package name, so use it directly // For Cursor rules converted from Claude skills, use flat structure const isCursorConversion = (effectiveFormat === 'cursor' && pkg.format === 'claude' && pkg.subtype === 'skill'); + const nativeSubtypeConfig = getSubtypeConfig(effectiveFormat, effectiveSubtype); + const usesNativePackageSubdirectory = !needsProgressiveDisclosureMulti && Boolean(nativeSubtypeConfig?.usesPackageSubdirectory); const packageDir = (effectiveFormat === 'claude' && effectiveSubtype === 'skill') ? destDir : isCursorConversion ? destDir // Cursor uses flat structure : needsProgressiveDisclosureMulti ? destDir // Progressive disclosure already includes package name + : usesNativePackageSubdirectory + ? destDir // Native nested formats already include the package name : `${destDir}/${packageName}`; destPath = packageDir; console.log(` 📁 Multi-file package - creating directory: ${packageDir}`); @@ -1464,8 +1473,8 @@ export async function handleInstall( // We want to strip everything up to and including the package-name directory let relativeFileName = file.name; - // Find the skills directory index - const skillsDirIndex = pathParts.indexOf('skills'); + // Find the skills directory index. OpenCode used to use singular "skill". + const skillsDirIndex = pathParts.findIndex(part => part === 'skills' || part === 'skill'); if (skillsDirIndex !== -1 && pathParts.length > skillsDirIndex + 2) { // Skip: .claude/skills/package-name/ and keep the rest relativeFileName = pathParts.slice(skillsDirIndex + 2).join('/'); diff --git a/packages/converters/docs/README.md b/packages/converters/docs/README.md index b055dda6..eff36282 100644 --- a/packages/converters/docs/README.md +++ b/packages/converters/docs/README.md @@ -30,6 +30,7 @@ Complete overview of all supported formats, their subtypes, and official documen | | `agent` | Subagent TOML configs with MCP and skills | [developers.openai.com](https://developers.openai.com/codex/multi-agent/) | | | `rule` | AGENTS.md project instructions | [developers.openai.com](https://developers.openai.com/codex/skills) | | **OpenCode** | `agent` | AI agents with mode, tools, and permissions | [opencode.ai](https://opencode.ai/docs/agents/) | +| | `skill` | Agent Skills SKILL.md format | [opencode.ai](https://opencode.ai/docs/skills/) | | | `slash-command` | User-triggered prompts with templates and placeholders | [opencode.ai](https://opencode.ai/docs/commands/) | | **Gemini CLI** | `slash-command` | Custom slash commands in TOML format | [geminicli.com](https://geminicli.com/docs/commands/) | | | `extension` | Extensions with MCP servers and context files | [geminicli.com](https://geminicli.com/docs/extensions/) | @@ -71,7 +72,7 @@ This directory contains detailed specifications for each AI IDE/tool format that | **Ruler** | [ruler.md](./ruler.md) | Plain markdown rules, centralized management | [okigu.com/ruler](https://okigu.com/ruler) | | **Factory Droid** | [factory-droid.md](./factory-droid.md) | Skills, slash commands, and hooks | [docs.factory.ai](https://docs.factory.ai/) | | **Codex** | [codex.md](./codex.md) | Skills, subagents, and AGENTS.md instructions | [developers.openai.com](https://developers.openai.com/codex/multi-agent/) | -| **OpenCode** | [opencode.md](./opencode.md) | Agents and slash commands with YAML frontmatter | [opencode.ai/docs](https://opencode.ai/docs/) | +| **OpenCode** | [opencode.md](./opencode.md) | Agents, skills, and slash commands with YAML frontmatter | [opencode.ai/docs](https://opencode.ai/docs/) | | **Gemini CLI** | [gemini-plugin.md](./gemini-plugin.md) | Extensions with MCP servers and custom commands | [geminicli.com/docs](https://geminicli.com/docs/extensions/) | | **agents.md** | [agents-md.md](./agents-md.md) | OpenAI format, plain markdown | [github.com/openai/agents.md](https://github.com/openai/agents.md) | | **Trae** | [trae.md](./trae.md) | Plain markdown rules, no frontmatter | [docs.trae.ai](https://docs.trae.ai/ide/rules) | @@ -134,6 +135,7 @@ Each format has a corresponding JSON Schema in `../schemas/` that defines the st - `agent-skills.schema.json` - Agent Skills SKILL.md (shared standard) **OpenCode Subtypes:** +- `agent-skills.schema.json` - Agent Skills SKILL.md (shared standard) - `opencode-slash-command.schema.json` - Template-based commands **Amp Subtypes:** @@ -231,7 +233,7 @@ These specifications serve as the source of truth for: | Kiro Hooks | `.kiro/hooks/*.json` | Multiple JSON files | | Factory Droid | `.factory/skills/*/SKILL.md`, `.factory/commands/*.md` | Skills in subdirs, commands as files | | Codex | `.codex/agents/*.toml`, `.agents/skills/*/SKILL.md` | Agents as TOML, skills in subdirs | -| OpenCode | `.opencode/agent/*.md`, `.opencode/command/*.md` | Agents and commands as separate files | +| OpenCode | `.opencode/agent/*.md`, `.opencode/skills/*/SKILL.md`, `.opencode/command/*.md` | Agents and commands as files, skills in subdirs | | Gemini CLI | `.gemini/extensions/*/gemini-extension.json` | Extensions in subdirectories with JSON config | | agents.md | `agents.md` | Single file | | Trae | `.trae/rules/*.md` | Multiple files in directory | diff --git a/packages/converters/docs/opencode.md b/packages/converters/docs/opencode.md index 2f1fd80c..6bf594a6 100644 --- a/packages/converters/docs/opencode.md +++ b/packages/converters/docs/opencode.md @@ -2,7 +2,7 @@ **File Locations:** - Agents: `.opencode/agent/*.md` or `~/.config/opencode/agent/*.md` -- Skills: `.opencode/skill/${name}/SKILL.md` or `~/.opencode/skill/${name}/SKILL.md` +- Skills: `.opencode/skills/${name}/SKILL.md` or `~/.config/opencode/skills/${name}/SKILL.md` - Slash Commands: `.opencode/command/*.md` or `~/.config/opencode/command/*.md` - Config: `opencode.json` or `opencode.jsonc` (JSON format alternative) @@ -97,7 +97,7 @@ You are an expert code reviewer with deep knowledge of software engineering prin OpenCode skills use the **Agent Skills spec** (shared with Codex and GitHub Copilot). Skills are reusable instruction sets discovered on-demand via the native skill tool. -**Directory:** `.opencode/skill/${name}/SKILL.md` +**Directory:** `.opencode/skills/${name}/SKILL.md` ### Frontmatter Fields @@ -384,7 +384,7 @@ prpm convert agent.md --from claude --to opencode - **2025-12**: Added native skill support - Skills use Agent Skills spec (same as Codex, Copilot) - - Directory: `.opencode/skill/${name}/SKILL.md` + - Directory: `.opencode/skills/${name}/SKILL.md` - Required fields: `name`, `description` - Optional fields: `license`, `compatibility`, `allowed-tools`, `metadata` - Uses `agent-skills.schema.json` for validation diff --git a/packages/converters/src/format-registry.json b/packages/converters/src/format-registry.json index 0f3d3b93..77806616 100644 --- a/packages/converters/src/format-registry.json +++ b/packages/converters/src/format-registry.json @@ -202,7 +202,7 @@ "fileExtension": ".md" }, "skill": { - "directory": ".opencode/skill", + "directory": ".opencode/skills", "filePatterns": ["SKILL.md"], "nested": true, "nestedIndicator": "SKILL.md", diff --git a/packages/converters/src/from-opencode.ts b/packages/converters/src/from-opencode.ts index 5f4c3226..f766f6cd 100644 --- a/packages/converters/src/from-opencode.ts +++ b/packages/converters/src/from-opencode.ts @@ -4,7 +4,7 @@ * * OpenCode stores: * - Agents in .opencode/agent/${name}.md with YAML frontmatter - * - Skills in .opencode/skill/${name}/SKILL.md with YAML frontmatter (has 'name' field, Agent Skills spec) + * - Skills in .opencode/skills/${name}/SKILL.md with YAML frontmatter (has 'name' field, Agent Skills spec) * - Slash commands in .opencode/command/${name}.md with YAML frontmatter (has 'template' field) * * @see https://opencode.ai/docs/agents/ diff --git a/packages/converters/src/to-opencode.ts b/packages/converters/src/to-opencode.ts index e66bcab2..9b256c59 100644 --- a/packages/converters/src/to-opencode.ts +++ b/packages/converters/src/to-opencode.ts @@ -4,7 +4,7 @@ * * OpenCode stores: * - Agents in .opencode/agent/${name}.md with YAML frontmatter - * - Skills in .opencode/skill/${name}/SKILL.md with YAML frontmatter (Agent Skills spec) + * - Skills in .opencode/skills/${name}/SKILL.md with YAML frontmatter (Agent Skills spec) * - Slash commands in .opencode/command/${name}.md with YAML frontmatter * * @see https://opencode.ai/docs/agents/ diff --git a/packages/converters/src/utils/format-capabilities.json b/packages/converters/src/utils/format-capabilities.json index 89f44d2f..300886d9 100644 --- a/packages/converters/src/utils/format-capabilities.json +++ b/packages/converters/src/utils/format-capabilities.json @@ -96,7 +96,7 @@ "supportsAgentsMd": true, "supportsSlashCommands": true, "markdownFallback": "opencode-agent.md", - "notes": "OpenCode supports agents, skills, plugins, and slash commands. Skills use Agent Skills spec in .opencode/skill/${name}/SKILL.md. Plugins are JavaScript/TypeScript modules in .opencode/plugin with 40+ event hooks. Full agents.md support." + "notes": "OpenCode supports agents, skills, plugins, and slash commands. Skills use Agent Skills spec in .opencode/skills/${name}/SKILL.md. Plugins are JavaScript/TypeScript modules in .opencode/plugin with 40+ event hooks. Full agents.md support." }, "ruler": { "name": "Ruler", diff --git a/packages/types/src/package.ts b/packages/types/src/package.ts index da90de11..953f4771 100644 --- a/packages/types/src/package.ts +++ b/packages/types/src/package.ts @@ -118,7 +118,7 @@ export const FORMAT_SUBTYPES: Record = { gemini: ["slash-command", "extension"], "gemini-extension": ["extension", "plugin"], "gemini.md": ["skill", "agent"], - opencode: ["agent", "slash-command", "tool", "plugin", "skill"], // skill via progressive disclosure + opencode: ["agent", "slash-command", "tool", "plugin", "skill"], // Native skills via .opencode/skills/ ruler: ["rule"], droid: ["skill", "slash-command", "hook", "agent"], // agent via progressive disclosure trae: ["rule"], @@ -157,7 +157,7 @@ export const FORMAT_NATIVE_SUBTYPES: Partial> copilot: ["rule", "chatmode", "skill"], // Native skill support via .github/skills/ kiro: ["rule", "hook", "agent"], // No native skill - uses AGENTS.md gemini: ["slash-command", "extension"], // Full native support - opencode: ["agent", "slash-command", "tool", "plugin", "skill"], // Native skill support in .opencode/skill/ + opencode: ["agent", "slash-command", "tool", "plugin", "skill"], // Native skill support in .opencode/skills/ droid: ["skill", "slash-command", "hook"], // No native agent - uses AGENTS.md zed: ["rule", "slash-command", "extension"], // No native skill/agent - uses AGENTS.md amp: ["skill", "slash-command"], // Native skills (.agents/skills/) and commands (.agents/commands/)