Port an existing plugin
Turn a Claude Code plugin, Cursor rules, Copilot prompts, or another native layout into one portable plugin.ts.
Already have a plugin or rules folder for one harness? Port it once into a portable definePlugin and compile it to Claude Code, Codex, Gemini CLI, Copilot, Cursor, Windsurf, Pi, and OpenCode. You keep your files on disk; the generated plugin definition points at them.
The fast path: ap-sdk port
npx ap-sdk port ./my-plugin # writes ./my-plugin/plugin.ts
npx ap-sdk port ./my-plugin --dry-run # preview it firstport detects the source layout, reads manifests, instruction files, SKILL.md skills, commands, agents, hooks, and companion directories, then writes a portable plugin.ts that loads files with readTextFrom, readBodyFrom, and readDir.
npx ap-sdk check plugin.ts
npx ap-sdk build plugin.tsReview any generated TODO comments. Unmapped native hook events are flagged in code so you can choose the right portable event.
Source layouts
| Source layout | Detector looks for | Guide |
|---|---|---|
| Claude Code plugin | .claude-plugin/plugin.json, CLAUDE.md, skills/**/SKILL.md, commands/, agents/, hooks/ | Claude Code |
| Gemini CLI extension | gemini-extension.json, GEMINI.md, .gemini/agents | Gemini CLI |
| OpenCode plugin | opencode.json plus generic content folders | OpenCode |
| GitHub Copilot | .github/copilot-instructions.md, .github/prompts, .github/agents | Copilot |
| Cursor | .cursor plus generic content folders | Cursor |
| Windsurf | .windsurf plus generic content folders | Windsurf |
| Codex | .codex or .agents, .codex/prompts | Codex |
| Pi | .pi plus generic content folders | Pi |
| Generic tree | skills/, commands/, agents/, instruction files | Generic tree |
Keep your files on disk
plugin.ts
import { definePlugin, readDir, readTextFrom } from "@jalco/ap-sdk";
const read = readTextFrom(import.meta.url);
export default definePlugin({
id: "alp-river",
description: "Multi-step agent refinement pipeline.",
instructions: read("./CLAUDE.md"),
files: readDir("./hooks", import.meta.url, "hooks"),
});A harness with no native form for a feature drops just that piece with a warning, never a broken file.