@every-env/compound-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/.claude-plugin/marketplace.json +37 -0
  2. package/.github/workflows/deploy-docs.yml +39 -0
  3. package/AGENTS.md +48 -0
  4. package/CLAUDE.md +380 -0
  5. package/LICENSE +21 -0
  6. package/README.md +65 -0
  7. package/bun.lock +30 -0
  8. package/docs/css/docs.css +675 -0
  9. package/docs/css/style.css +2886 -0
  10. package/docs/index.html +1046 -0
  11. package/docs/js/main.js +225 -0
  12. package/docs/pages/agents.html +649 -0
  13. package/docs/pages/changelog.html +495 -0
  14. package/docs/pages/commands.html +523 -0
  15. package/docs/pages/getting-started.html +582 -0
  16. package/docs/pages/mcp-servers.html +409 -0
  17. package/docs/pages/skills.html +611 -0
  18. package/docs/solutions/plugin-versioning-requirements.md +77 -0
  19. package/docs/specs/claude-code.md +67 -0
  20. package/docs/specs/codex.md +59 -0
  21. package/docs/specs/opencode.md +57 -0
  22. package/package.json +26 -0
  23. package/plans/grow-your-own-garden-plugin-architecture.md +102 -0
  24. package/plans/landing-page-launchkit-refresh.md +279 -0
  25. package/plugins/coding-tutor/.claude-plugin/plugin.json +9 -0
  26. package/plugins/coding-tutor/README.md +37 -0
  27. package/plugins/coding-tutor/commands/quiz-me.md +1 -0
  28. package/plugins/coding-tutor/commands/sync-tutorials.md +25 -0
  29. package/plugins/coding-tutor/commands/teach-me.md +1 -0
  30. package/plugins/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
  31. package/plugins/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +207 -0
  32. package/plugins/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +193 -0
  33. package/plugins/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
  34. package/plugins/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +118 -0
  35. package/plugins/compound-engineering/.claude-plugin/plugin.json +33 -0
  36. package/plugins/compound-engineering/CHANGELOG.md +393 -0
  37. package/plugins/compound-engineering/CLAUDE.md +90 -0
  38. package/plugins/compound-engineering/LICENSE +21 -0
  39. package/plugins/compound-engineering/README.md +219 -0
  40. package/plugins/compound-engineering/agents/design/design-implementation-reviewer.md +94 -0
  41. package/plugins/compound-engineering/agents/design/design-iterator.md +197 -0
  42. package/plugins/compound-engineering/agents/design/figma-design-sync.md +172 -0
  43. package/plugins/compound-engineering/agents/docs/ankane-readme-writer.md +50 -0
  44. package/plugins/compound-engineering/agents/research/best-practices-researcher.md +100 -0
  45. package/plugins/compound-engineering/agents/research/framework-docs-researcher.md +83 -0
  46. package/plugins/compound-engineering/agents/research/git-history-analyzer.md +42 -0
  47. package/plugins/compound-engineering/agents/research/repo-research-analyst.md +113 -0
  48. package/plugins/compound-engineering/agents/review/agent-native-reviewer.md +246 -0
  49. package/plugins/compound-engineering/agents/review/architecture-strategist.md +52 -0
  50. package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +85 -0
  51. package/plugins/compound-engineering/agents/review/data-integrity-guardian.md +70 -0
  52. package/plugins/compound-engineering/agents/review/data-migration-expert.md +97 -0
  53. package/plugins/compound-engineering/agents/review/deployment-verification-agent.md +159 -0
  54. package/plugins/compound-engineering/agents/review/dhh-rails-reviewer.md +45 -0
  55. package/plugins/compound-engineering/agents/review/julik-frontend-races-reviewer.md +222 -0
  56. package/plugins/compound-engineering/agents/review/kieran-python-reviewer.md +104 -0
  57. package/plugins/compound-engineering/agents/review/kieran-rails-reviewer.md +86 -0
  58. package/plugins/compound-engineering/agents/review/kieran-typescript-reviewer.md +95 -0
  59. package/plugins/compound-engineering/agents/review/pattern-recognition-specialist.md +57 -0
  60. package/plugins/compound-engineering/agents/review/performance-oracle.md +110 -0
  61. package/plugins/compound-engineering/agents/review/security-sentinel.md +93 -0
  62. package/plugins/compound-engineering/agents/workflow/bug-reproduction-validator.md +67 -0
  63. package/plugins/compound-engineering/agents/workflow/every-style-editor.md +64 -0
  64. package/plugins/compound-engineering/agents/workflow/lint.md +16 -0
  65. package/plugins/compound-engineering/agents/workflow/pr-comment-resolver.md +69 -0
  66. package/plugins/compound-engineering/agents/workflow/spec-flow-analyzer.md +113 -0
  67. package/plugins/compound-engineering/commands/agent-native-audit.md +277 -0
  68. package/plugins/compound-engineering/commands/changelog.md +137 -0
  69. package/plugins/compound-engineering/commands/create-agent-skill.md +8 -0
  70. package/plugins/compound-engineering/commands/deepen-plan.md +546 -0
  71. package/plugins/compound-engineering/commands/deploy-docs.md +112 -0
  72. package/plugins/compound-engineering/commands/feature-video.md +342 -0
  73. package/plugins/compound-engineering/commands/generate_command.md +162 -0
  74. package/plugins/compound-engineering/commands/heal-skill.md +142 -0
  75. package/plugins/compound-engineering/commands/lfg.md +19 -0
  76. package/plugins/compound-engineering/commands/plan_review.md +7 -0
  77. package/plugins/compound-engineering/commands/release-docs.md +211 -0
  78. package/plugins/compound-engineering/commands/report-bug.md +150 -0
  79. package/plugins/compound-engineering/commands/reproduce-bug.md +99 -0
  80. package/plugins/compound-engineering/commands/resolve_parallel.md +34 -0
  81. package/plugins/compound-engineering/commands/resolve_pr_parallel.md +49 -0
  82. package/plugins/compound-engineering/commands/resolve_todo_parallel.md +35 -0
  83. package/plugins/compound-engineering/commands/test-browser.md +339 -0
  84. package/plugins/compound-engineering/commands/triage.md +310 -0
  85. package/plugins/compound-engineering/commands/workflows/compound.md +202 -0
  86. package/plugins/compound-engineering/commands/workflows/plan.md +466 -0
  87. package/plugins/compound-engineering/commands/workflows/review.md +514 -0
  88. package/plugins/compound-engineering/commands/workflows/work.md +363 -0
  89. package/plugins/compound-engineering/commands/xcode-test.md +331 -0
  90. package/plugins/compound-engineering/skills/agent-browser/SKILL.md +223 -0
  91. package/plugins/compound-engineering/skills/agent-native-architecture/SKILL.md +435 -0
  92. package/plugins/compound-engineering/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
  93. package/plugins/compound-engineering/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
  94. package/plugins/compound-engineering/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
  95. package/plugins/compound-engineering/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
  96. package/plugins/compound-engineering/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
  97. package/plugins/compound-engineering/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
  98. package/plugins/compound-engineering/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
  99. package/plugins/compound-engineering/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
  100. package/plugins/compound-engineering/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
  101. package/plugins/compound-engineering/skills/agent-native-architecture/references/product-implications.md +443 -0
  102. package/plugins/compound-engineering/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
  103. package/plugins/compound-engineering/skills/agent-native-architecture/references/self-modification.md +269 -0
  104. package/plugins/compound-engineering/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
  105. package/plugins/compound-engineering/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
  106. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/SKILL.md +184 -0
  107. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  108. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  109. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  110. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/resources.md +119 -0
  111. package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  112. package/plugins/compound-engineering/skills/compound-docs/SKILL.md +510 -0
  113. package/plugins/compound-engineering/skills/compound-docs/assets/critical-pattern-template.md +34 -0
  114. package/plugins/compound-engineering/skills/compound-docs/assets/resolution-template.md +93 -0
  115. package/plugins/compound-engineering/skills/compound-docs/references/yaml-schema.md +65 -0
  116. package/plugins/compound-engineering/skills/compound-docs/schema.yaml +176 -0
  117. package/plugins/compound-engineering/skills/create-agent-skills/SKILL.md +299 -0
  118. package/plugins/compound-engineering/skills/create-agent-skills/references/api-security.md +226 -0
  119. package/plugins/compound-engineering/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
  120. package/plugins/compound-engineering/skills/create-agent-skills/references/best-practices.md +404 -0
  121. package/plugins/compound-engineering/skills/create-agent-skills/references/common-patterns.md +595 -0
  122. package/plugins/compound-engineering/skills/create-agent-skills/references/core-principles.md +437 -0
  123. package/plugins/compound-engineering/skills/create-agent-skills/references/executable-code.md +175 -0
  124. package/plugins/compound-engineering/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
  125. package/plugins/compound-engineering/skills/create-agent-skills/references/official-spec.md +185 -0
  126. package/plugins/compound-engineering/skills/create-agent-skills/references/recommended-structure.md +168 -0
  127. package/plugins/compound-engineering/skills/create-agent-skills/references/skill-structure.md +372 -0
  128. package/plugins/compound-engineering/skills/create-agent-skills/references/using-scripts.md +113 -0
  129. package/plugins/compound-engineering/skills/create-agent-skills/references/using-templates.md +112 -0
  130. package/plugins/compound-engineering/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
  131. package/plugins/compound-engineering/skills/create-agent-skills/templates/router-skill.md +73 -0
  132. package/plugins/compound-engineering/skills/create-agent-skills/templates/simple-skill.md +33 -0
  133. package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-reference.md +96 -0
  134. package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-script.md +93 -0
  135. package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-template.md +74 -0
  136. package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-workflow.md +120 -0
  137. package/plugins/compound-engineering/skills/create-agent-skills/workflows/audit-skill.md +138 -0
  138. package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
  139. package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
  140. package/plugins/compound-engineering/skills/create-agent-skills/workflows/get-guidance.md +121 -0
  141. package/plugins/compound-engineering/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
  142. package/plugins/compound-engineering/skills/create-agent-skills/workflows/verify-skill.md +204 -0
  143. package/plugins/compound-engineering/skills/dhh-rails-style/SKILL.md +185 -0
  144. package/plugins/compound-engineering/skills/dhh-rails-style/references/architecture.md +653 -0
  145. package/plugins/compound-engineering/skills/dhh-rails-style/references/controllers.md +303 -0
  146. package/plugins/compound-engineering/skills/dhh-rails-style/references/frontend.md +510 -0
  147. package/plugins/compound-engineering/skills/dhh-rails-style/references/gems.md +266 -0
  148. package/plugins/compound-engineering/skills/dhh-rails-style/references/models.md +359 -0
  149. package/plugins/compound-engineering/skills/dhh-rails-style/references/testing.md +338 -0
  150. package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +594 -0
  151. package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +359 -0
  152. package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +326 -0
  153. package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +143 -0
  154. package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +265 -0
  155. package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +623 -0
  156. package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +338 -0
  157. package/plugins/compound-engineering/skills/every-style-editor/SKILL.md +134 -0
  158. package/plugins/compound-engineering/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  159. package/plugins/compound-engineering/skills/file-todos/SKILL.md +251 -0
  160. package/plugins/compound-engineering/skills/file-todos/assets/todo-template.md +155 -0
  161. package/plugins/compound-engineering/skills/frontend-design/SKILL.md +42 -0
  162. package/plugins/compound-engineering/skills/gemini-imagegen/SKILL.md +237 -0
  163. package/plugins/compound-engineering/skills/gemini-imagegen/requirements.txt +2 -0
  164. package/plugins/compound-engineering/skills/gemini-imagegen/scripts/compose_images.py +157 -0
  165. package/plugins/compound-engineering/skills/gemini-imagegen/scripts/edit_image.py +144 -0
  166. package/plugins/compound-engineering/skills/gemini-imagegen/scripts/gemini_images.py +263 -0
  167. package/plugins/compound-engineering/skills/gemini-imagegen/scripts/generate_image.py +133 -0
  168. package/plugins/compound-engineering/skills/gemini-imagegen/scripts/multi_turn_chat.py +216 -0
  169. package/plugins/compound-engineering/skills/git-worktree/SKILL.md +302 -0
  170. package/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh +345 -0
  171. package/plugins/compound-engineering/skills/rclone/SKILL.md +150 -0
  172. package/plugins/compound-engineering/skills/rclone/scripts/check_setup.sh +60 -0
  173. package/plugins/compound-engineering/skills/skill-creator/SKILL.md +209 -0
  174. package/plugins/compound-engineering/skills/skill-creator/scripts/init_skill.py +303 -0
  175. package/plugins/compound-engineering/skills/skill-creator/scripts/package_skill.py +110 -0
  176. package/plugins/compound-engineering/skills/skill-creator/scripts/quick_validate.py +65 -0
  177. package/src/commands/convert.ts +156 -0
  178. package/src/commands/install.ts +221 -0
  179. package/src/commands/list.ts +37 -0
  180. package/src/converters/claude-to-codex.ts +124 -0
  181. package/src/converters/claude-to-opencode.ts +392 -0
  182. package/src/index.ts +20 -0
  183. package/src/parsers/claude.ts +248 -0
  184. package/src/targets/codex.ts +91 -0
  185. package/src/targets/index.ts +29 -0
  186. package/src/targets/opencode.ts +48 -0
  187. package/src/types/claude.ts +88 -0
  188. package/src/types/codex.ts +23 -0
  189. package/src/types/opencode.ts +54 -0
  190. package/src/utils/codex-agents.ts +64 -0
  191. package/src/utils/files.ts +64 -0
  192. package/src/utils/frontmatter.ts +65 -0
  193. package/tests/claude-parser.test.ts +89 -0
  194. package/tests/cli.test.ts +289 -0
  195. package/tests/codex-agents.test.ts +62 -0
  196. package/tests/codex-converter.test.ts +121 -0
  197. package/tests/codex-writer.test.ts +76 -0
  198. package/tests/converter.test.ts +171 -0
  199. package/tests/fixtures/custom-paths/.claude-plugin/plugin.json +8 -0
  200. package/tests/fixtures/custom-paths/agents/default-agent.md +5 -0
  201. package/tests/fixtures/custom-paths/commands/default-command.md +5 -0
  202. package/tests/fixtures/custom-paths/custom-agents/custom-agent.md +5 -0
  203. package/tests/fixtures/custom-paths/custom-commands/custom-command.md +5 -0
  204. package/tests/fixtures/custom-paths/custom-hooks/hooks.json +7 -0
  205. package/tests/fixtures/custom-paths/custom-skills/custom-skill/SKILL.md +5 -0
  206. package/tests/fixtures/custom-paths/hooks/hooks.json +7 -0
  207. package/tests/fixtures/custom-paths/skills/default-skill/SKILL.md +5 -0
  208. package/tests/fixtures/invalid-command-path/.claude-plugin/plugin.json +5 -0
  209. package/tests/fixtures/invalid-hooks-path/.claude-plugin/plugin.json +5 -0
  210. package/tests/fixtures/invalid-mcp-path/.claude-plugin/plugin.json +5 -0
  211. package/tests/fixtures/mcp-file/.claude-plugin/plugin.json +5 -0
  212. package/tests/fixtures/mcp-file/.mcp.json +6 -0
  213. package/tests/fixtures/sample-plugin/.claude-plugin/plugin.json +30 -0
  214. package/tests/fixtures/sample-plugin/agents/agent-one.md +10 -0
  215. package/tests/fixtures/sample-plugin/agents/security-reviewer.md +7 -0
  216. package/tests/fixtures/sample-plugin/commands/command-one.md +7 -0
  217. package/tests/fixtures/sample-plugin/commands/model-command.md +8 -0
  218. package/tests/fixtures/sample-plugin/commands/nested/command-two.md +9 -0
  219. package/tests/fixtures/sample-plugin/commands/pattern-command.md +7 -0
  220. package/tests/fixtures/sample-plugin/commands/skill-command.md +7 -0
  221. package/tests/fixtures/sample-plugin/commands/todo-command.md +7 -0
  222. package/tests/fixtures/sample-plugin/hooks/hooks.json +156 -0
  223. package/tests/fixtures/sample-plugin/skills/skill-one/SKILL.md +6 -0
  224. package/tests/frontmatter.test.ts +20 -0
  225. package/tests/opencode-writer.test.ts +62 -0
  226. package/tsconfig.json +14 -0
@@ -0,0 +1,392 @@
1
+ import { formatFrontmatter } from "../utils/frontmatter"
2
+ import type {
3
+ ClaudeAgent,
4
+ ClaudeCommand,
5
+ ClaudeHooks,
6
+ ClaudePlugin,
7
+ ClaudeMcpServer,
8
+ } from "../types/claude"
9
+ import type {
10
+ OpenCodeBundle,
11
+ OpenCodeCommandConfig,
12
+ OpenCodeConfig,
13
+ OpenCodeMcpServer,
14
+ } from "../types/opencode"
15
+
16
+ export type PermissionMode = "none" | "broad" | "from-commands"
17
+
18
+ export type ClaudeToOpenCodeOptions = {
19
+ agentMode: "primary" | "subagent"
20
+ inferTemperature: boolean
21
+ permissions: PermissionMode
22
+ }
23
+
24
+ const TOOL_MAP: Record<string, string> = {
25
+ bash: "bash",
26
+ read: "read",
27
+ write: "write",
28
+ edit: "edit",
29
+ grep: "grep",
30
+ glob: "glob",
31
+ list: "list",
32
+ webfetch: "webfetch",
33
+ skill: "skill",
34
+ patch: "patch",
35
+ task: "task",
36
+ question: "question",
37
+ todowrite: "todowrite",
38
+ todoread: "todoread",
39
+ }
40
+
41
+ type HookEventMapping = {
42
+ events: string[]
43
+ type: "tool" | "session" | "permission" | "message"
44
+ requireError?: boolean
45
+ note?: string
46
+ }
47
+
48
+ const HOOK_EVENT_MAP: Record<string, HookEventMapping> = {
49
+ PreToolUse: { events: ["tool.execute.before"], type: "tool" },
50
+ PostToolUse: { events: ["tool.execute.after"], type: "tool" },
51
+ PostToolUseFailure: { events: ["tool.execute.after"], type: "tool", requireError: true, note: "Claude PostToolUseFailure" },
52
+ SessionStart: { events: ["session.created"], type: "session" },
53
+ SessionEnd: { events: ["session.deleted"], type: "session" },
54
+ Stop: { events: ["session.idle"], type: "session" },
55
+ PreCompact: { events: ["experimental.session.compacting"], type: "session" },
56
+ PermissionRequest: { events: ["permission.requested", "permission.replied"], type: "permission", note: "Claude PermissionRequest" },
57
+ UserPromptSubmit: { events: ["message.created", "message.updated"], type: "message", note: "Claude UserPromptSubmit" },
58
+ Notification: { events: ["message.updated"], type: "message", note: "Claude Notification" },
59
+ Setup: { events: ["session.created"], type: "session", note: "Claude Setup" },
60
+ SubagentStart: { events: ["message.updated"], type: "message", note: "Claude SubagentStart" },
61
+ SubagentStop: { events: ["message.updated"], type: "message", note: "Claude SubagentStop" },
62
+ }
63
+
64
+ export function convertClaudeToOpenCode(
65
+ plugin: ClaudePlugin,
66
+ options: ClaudeToOpenCodeOptions,
67
+ ): OpenCodeBundle {
68
+ const agentFiles = plugin.agents.map((agent) => convertAgent(agent, options))
69
+ const commandMap = convertCommands(plugin.commands)
70
+ const mcp = plugin.mcpServers ? convertMcp(plugin.mcpServers) : undefined
71
+ const plugins = plugin.hooks ? [convertHooks(plugin.hooks)] : []
72
+
73
+ const config: OpenCodeConfig = {
74
+ $schema: "https://opencode.ai/config.json",
75
+ command: Object.keys(commandMap).length > 0 ? commandMap : undefined,
76
+ mcp: mcp && Object.keys(mcp).length > 0 ? mcp : undefined,
77
+ }
78
+
79
+ applyPermissions(config, plugin.commands, options.permissions)
80
+
81
+ return {
82
+ config,
83
+ agents: agentFiles,
84
+ plugins,
85
+ skillDirs: plugin.skills.map((skill) => ({ sourceDir: skill.sourceDir, name: skill.name })),
86
+ }
87
+ }
88
+
89
+ function convertAgent(agent: ClaudeAgent, options: ClaudeToOpenCodeOptions) {
90
+ const frontmatter: Record<string, unknown> = {
91
+ description: agent.description,
92
+ mode: options.agentMode,
93
+ }
94
+
95
+ if (agent.model && agent.model !== "inherit") {
96
+ frontmatter.model = normalizeModel(agent.model)
97
+ }
98
+
99
+ if (options.inferTemperature) {
100
+ const temperature = inferTemperature(agent)
101
+ if (temperature !== undefined) {
102
+ frontmatter.temperature = temperature
103
+ }
104
+ }
105
+
106
+ const content = formatFrontmatter(frontmatter, agent.body)
107
+
108
+ return {
109
+ name: agent.name,
110
+ content,
111
+ }
112
+ }
113
+
114
+ function convertCommands(commands: ClaudeCommand[]): Record<string, OpenCodeCommandConfig> {
115
+ const result: Record<string, OpenCodeCommandConfig> = {}
116
+ for (const command of commands) {
117
+ const entry: OpenCodeCommandConfig = {
118
+ description: command.description,
119
+ template: command.body,
120
+ }
121
+ if (command.model && command.model !== "inherit") {
122
+ entry.model = normalizeModel(command.model)
123
+ }
124
+ result[command.name] = entry
125
+ }
126
+ return result
127
+ }
128
+
129
+ function convertMcp(servers: Record<string, ClaudeMcpServer>): Record<string, OpenCodeMcpServer> {
130
+ const result: Record<string, OpenCodeMcpServer> = {}
131
+ for (const [name, server] of Object.entries(servers)) {
132
+ if (server.command) {
133
+ result[name] = {
134
+ type: "local",
135
+ command: [server.command, ...(server.args ?? [])],
136
+ environment: server.env,
137
+ enabled: true,
138
+ }
139
+ continue
140
+ }
141
+
142
+ if (server.url) {
143
+ result[name] = {
144
+ type: "remote",
145
+ url: server.url,
146
+ headers: server.headers,
147
+ enabled: true,
148
+ }
149
+ }
150
+ }
151
+ return result
152
+ }
153
+
154
+ function convertHooks(hooks: ClaudeHooks) {
155
+ const handlerBlocks: string[] = []
156
+ const hookMap = hooks.hooks
157
+ const unmappedEvents: string[] = []
158
+
159
+ for (const [eventName, matchers] of Object.entries(hookMap)) {
160
+ const mapping = HOOK_EVENT_MAP[eventName]
161
+ if (!mapping) {
162
+ unmappedEvents.push(eventName)
163
+ continue
164
+ }
165
+ if (matchers.length === 0) continue
166
+ for (const event of mapping.events) {
167
+ handlerBlocks.push(
168
+ renderHookHandlers(event, matchers, {
169
+ useToolMatcher: mapping.type === "tool" || mapping.type === "permission",
170
+ requireError: mapping.requireError ?? false,
171
+ note: mapping.note,
172
+ }),
173
+ )
174
+ }
175
+ }
176
+
177
+ const unmappedComment = unmappedEvents.length > 0
178
+ ? `// Unmapped Claude hook events: ${unmappedEvents.join(", ")}\n`
179
+ : ""
180
+
181
+ const content = `${unmappedComment}import type { Plugin } from "@opencode-ai/plugin"\n\nexport const ConvertedHooks: Plugin = async ({ $ }) => {\n return {\n${handlerBlocks.join(",\n")}\n }\n}\n\nexport default ConvertedHooks\n`
182
+
183
+ return {
184
+ name: "converted-hooks.ts",
185
+ content,
186
+ }
187
+ }
188
+
189
+ function renderHookHandlers(
190
+ event: string,
191
+ matchers: ClaudeHooks["hooks"][string],
192
+ options: { useToolMatcher: boolean; requireError: boolean; note?: string },
193
+ ) {
194
+ const statements: string[] = []
195
+ for (const matcher of matchers) {
196
+ statements.push(...renderHookStatements(matcher, options.useToolMatcher))
197
+ }
198
+ const rendered = statements.map((line) => ` ${line}`).join("\n")
199
+ const wrapped = options.requireError
200
+ ? ` if (input?.error) {\n${statements.map((line) => ` ${line}`).join("\n")}\n }`
201
+ : rendered
202
+ const note = options.note ? ` // ${options.note}\n` : ""
203
+ return ` "${event}": async (input) => {\n${note}${wrapped}\n }`
204
+ }
205
+
206
+ function renderHookStatements(
207
+ matcher: ClaudeHooks["hooks"][string][number],
208
+ useToolMatcher: boolean,
209
+ ): string[] {
210
+ if (!matcher.hooks || matcher.hooks.length === 0) return []
211
+ const tools = matcher.matcher
212
+ .split("|")
213
+ .map((tool) => tool.trim().toLowerCase())
214
+ .filter(Boolean)
215
+
216
+ const useMatcher = useToolMatcher && tools.length > 0 && !tools.includes("*")
217
+ const condition = useMatcher
218
+ ? tools.map((tool) => `input.tool === "${tool}"`).join(" || ")
219
+ : null
220
+ const statements: string[] = []
221
+
222
+ for (const hook of matcher.hooks) {
223
+ if (hook.type === "command") {
224
+ if (condition) {
225
+ statements.push(`if (${condition}) { await $\`${hook.command}\` }`)
226
+ } else {
227
+ statements.push(`await $\`${hook.command}\``)
228
+ }
229
+ if (hook.timeout) {
230
+ statements.push(`// timeout: ${hook.timeout}s (not enforced)`)
231
+ }
232
+ continue
233
+ }
234
+ if (hook.type === "prompt") {
235
+ statements.push(`// Prompt hook for ${matcher.matcher}: ${hook.prompt.replace(/\n/g, " ")}`)
236
+ continue
237
+ }
238
+ statements.push(`// Agent hook for ${matcher.matcher}: ${hook.agent}`)
239
+ }
240
+
241
+ return statements
242
+ }
243
+
244
+ function normalizeModel(model: string): string {
245
+ if (model.includes("/")) return model
246
+ if (/^claude-/.test(model)) return `anthropic/${model}`
247
+ if (/^(gpt-|o1-|o3-)/.test(model)) return `openai/${model}`
248
+ if (/^gemini-/.test(model)) return `google/${model}`
249
+ return `anthropic/${model}`
250
+ }
251
+
252
+ function inferTemperature(agent: ClaudeAgent): number | undefined {
253
+ const sample = `${agent.name} ${agent.description ?? ""}`.toLowerCase()
254
+ if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
255
+ return 0.1
256
+ }
257
+ if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
258
+ return 0.2
259
+ }
260
+ if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
261
+ return 0.3
262
+ }
263
+ if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
264
+ return 0.6
265
+ }
266
+ return 0.3
267
+ }
268
+
269
+ function applyPermissions(
270
+ config: OpenCodeConfig,
271
+ commands: ClaudeCommand[],
272
+ mode: PermissionMode,
273
+ ) {
274
+ if (mode === "none") return
275
+
276
+ const sourceTools = [
277
+ "read",
278
+ "write",
279
+ "edit",
280
+ "bash",
281
+ "grep",
282
+ "glob",
283
+ "list",
284
+ "webfetch",
285
+ "skill",
286
+ "patch",
287
+ "task",
288
+ "question",
289
+ "todowrite",
290
+ "todoread",
291
+ ]
292
+ let enabled = new Set<string>()
293
+ const patterns: Record<string, Set<string>> = {}
294
+
295
+ if (mode === "broad") {
296
+ enabled = new Set(sourceTools)
297
+ } else {
298
+ for (const command of commands) {
299
+ if (!command.allowedTools) continue
300
+ for (const tool of command.allowedTools) {
301
+ const parsed = parseToolSpec(tool)
302
+ if (!parsed.tool) continue
303
+ enabled.add(parsed.tool)
304
+ if (parsed.pattern) {
305
+ const normalizedPattern = normalizePattern(parsed.tool, parsed.pattern)
306
+ if (!patterns[parsed.tool]) patterns[parsed.tool] = new Set()
307
+ patterns[parsed.tool].add(normalizedPattern)
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ const permission: Record<string, "allow" | "deny"> = {}
314
+ const tools: Record<string, boolean> = {}
315
+
316
+ for (const tool of sourceTools) {
317
+ tools[tool] = mode === "broad" ? true : enabled.has(tool)
318
+ }
319
+
320
+ if (mode === "broad") {
321
+ for (const tool of sourceTools) {
322
+ permission[tool] = "allow"
323
+ }
324
+ } else {
325
+ for (const tool of sourceTools) {
326
+ const toolPatterns = patterns[tool]
327
+ if (toolPatterns && toolPatterns.size > 0) {
328
+ const patternPermission: Record<string, "allow" | "deny"> = { "*": "deny" }
329
+ for (const pattern of toolPatterns) {
330
+ patternPermission[pattern] = "allow"
331
+ }
332
+ ;(permission as Record<string, typeof patternPermission>)[tool] = patternPermission
333
+ } else {
334
+ permission[tool] = enabled.has(tool) ? "allow" : "deny"
335
+ }
336
+ }
337
+ }
338
+
339
+ if (mode !== "broad") {
340
+ for (const [tool, toolPatterns] of Object.entries(patterns)) {
341
+ if (!toolPatterns || toolPatterns.size === 0) continue
342
+ const patternPermission: Record<string, "allow" | "deny"> = { "*": "deny" }
343
+ for (const pattern of toolPatterns) {
344
+ patternPermission[pattern] = "allow"
345
+ }
346
+ ;(permission as Record<string, typeof patternPermission>)[tool] = patternPermission
347
+ }
348
+ }
349
+
350
+ if (enabled.has("write") || enabled.has("edit")) {
351
+ if (typeof permission.edit === "string") permission.edit = "allow"
352
+ if (typeof permission.write === "string") permission.write = "allow"
353
+ }
354
+ if (patterns.write || patterns.edit) {
355
+ const combined = new Set<string>()
356
+ for (const pattern of patterns.write ?? []) combined.add(pattern)
357
+ for (const pattern of patterns.edit ?? []) combined.add(pattern)
358
+ const combinedPermission: Record<string, "allow" | "deny"> = { "*": "deny" }
359
+ for (const pattern of combined) {
360
+ combinedPermission[pattern] = "allow"
361
+ }
362
+ ;(permission as Record<string, typeof combinedPermission>).edit = combinedPermission
363
+ ;(permission as Record<string, typeof combinedPermission>).write = combinedPermission
364
+ }
365
+
366
+ config.permission = permission
367
+ config.tools = tools
368
+ }
369
+
370
+ function normalizeTool(raw: string): string | null {
371
+ return parseToolSpec(raw).tool
372
+ }
373
+
374
+ function parseToolSpec(raw: string): { tool: string | null; pattern?: string } {
375
+ const trimmed = raw.trim()
376
+ if (!trimmed) return { tool: null }
377
+ const [namePart, patternPart] = trimmed.split("(", 2)
378
+ const name = namePart.trim().toLowerCase()
379
+ const tool = TOOL_MAP[name] ?? null
380
+ if (!patternPart) return { tool }
381
+ const normalizedPattern = patternPart.endsWith(")")
382
+ ? patternPart.slice(0, -1).trim()
383
+ : patternPart.trim()
384
+ return { tool, pattern: normalizedPattern }
385
+ }
386
+
387
+ function normalizePattern(tool: string, pattern: string): string {
388
+ if (tool === "bash") {
389
+ return pattern.replace(/:/g, " ").trim()
390
+ }
391
+ return pattern
392
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bun
2
+ import { defineCommand, runMain } from "citty"
3
+ import convert from "./commands/convert"
4
+ import install from "./commands/install"
5
+ import listCommand from "./commands/list"
6
+
7
+ const main = defineCommand({
8
+ meta: {
9
+ name: "compound-plugin",
10
+ version: "0.1.0",
11
+ description: "Convert Claude Code plugins into other agent formats",
12
+ },
13
+ subCommands: {
14
+ convert: () => convert,
15
+ install: () => install,
16
+ list: () => listCommand,
17
+ },
18
+ })
19
+
20
+ runMain(main)
@@ -0,0 +1,248 @@
1
+ import path from "path"
2
+ import { parseFrontmatter } from "../utils/frontmatter"
3
+ import { readJson, readText, pathExists, walkFiles } from "../utils/files"
4
+ import type {
5
+ ClaudeAgent,
6
+ ClaudeCommand,
7
+ ClaudeHooks,
8
+ ClaudeManifest,
9
+ ClaudeMcpServer,
10
+ ClaudePlugin,
11
+ ClaudeSkill,
12
+ } from "../types/claude"
13
+
14
+ const PLUGIN_MANIFEST = path.join(".claude-plugin", "plugin.json")
15
+
16
+ export async function loadClaudePlugin(inputPath: string): Promise<ClaudePlugin> {
17
+ const root = await resolveClaudeRoot(inputPath)
18
+ const manifestPath = path.join(root, PLUGIN_MANIFEST)
19
+ const manifest = await readJson<ClaudeManifest>(manifestPath)
20
+
21
+ const agents = await loadAgents(resolveComponentDirs(root, "agents", manifest.agents))
22
+ const commands = await loadCommands(resolveComponentDirs(root, "commands", manifest.commands))
23
+ const skills = await loadSkills(resolveComponentDirs(root, "skills", manifest.skills))
24
+ const hooks = await loadHooks(root, manifest.hooks)
25
+
26
+ const mcpServers = await loadMcpServers(root, manifest)
27
+
28
+ return {
29
+ root,
30
+ manifest,
31
+ agents,
32
+ commands,
33
+ skills,
34
+ hooks,
35
+ mcpServers,
36
+ }
37
+ }
38
+
39
+ async function resolveClaudeRoot(inputPath: string): Promise<string> {
40
+ const absolute = path.resolve(inputPath)
41
+ const manifestAtPath = path.join(absolute, PLUGIN_MANIFEST)
42
+ if (await pathExists(manifestAtPath)) {
43
+ return absolute
44
+ }
45
+
46
+ if (absolute.endsWith(PLUGIN_MANIFEST)) {
47
+ return path.dirname(path.dirname(absolute))
48
+ }
49
+
50
+ if (absolute.endsWith("plugin.json")) {
51
+ return path.dirname(path.dirname(absolute))
52
+ }
53
+
54
+ throw new Error(`Could not find ${PLUGIN_MANIFEST} under ${inputPath}`)
55
+ }
56
+
57
+ async function loadAgents(agentsDirs: string[]): Promise<ClaudeAgent[]> {
58
+ const files = await collectMarkdownFiles(agentsDirs)
59
+
60
+ const agents: ClaudeAgent[] = []
61
+ for (const file of files) {
62
+ const raw = await readText(file)
63
+ const { data, body } = parseFrontmatter(raw)
64
+ const name = (data.name as string) ?? path.basename(file, ".md")
65
+ agents.push({
66
+ name,
67
+ description: data.description as string | undefined,
68
+ capabilities: data.capabilities as string[] | undefined,
69
+ model: data.model as string | undefined,
70
+ body: body.trim(),
71
+ sourcePath: file,
72
+ })
73
+ }
74
+ return agents
75
+ }
76
+
77
+ async function loadCommands(commandsDirs: string[]): Promise<ClaudeCommand[]> {
78
+ const files = await collectMarkdownFiles(commandsDirs)
79
+
80
+ const commands: ClaudeCommand[] = []
81
+ for (const file of files) {
82
+ const raw = await readText(file)
83
+ const { data, body } = parseFrontmatter(raw)
84
+ const name = (data.name as string) ?? path.basename(file, ".md")
85
+ const allowedTools = parseAllowedTools(data["allowed-tools"])
86
+ commands.push({
87
+ name,
88
+ description: data.description as string | undefined,
89
+ argumentHint: data["argument-hint"] as string | undefined,
90
+ model: data.model as string | undefined,
91
+ allowedTools,
92
+ body: body.trim(),
93
+ sourcePath: file,
94
+ })
95
+ }
96
+ return commands
97
+ }
98
+
99
+ async function loadSkills(skillsDirs: string[]): Promise<ClaudeSkill[]> {
100
+ const entries = await collectFiles(skillsDirs)
101
+ const skillFiles = entries.filter((file) => path.basename(file) === "SKILL.md")
102
+ const skills: ClaudeSkill[] = []
103
+ for (const file of skillFiles) {
104
+ const raw = await readText(file)
105
+ const { data } = parseFrontmatter(raw)
106
+ const name = (data.name as string) ?? path.basename(path.dirname(file))
107
+ skills.push({
108
+ name,
109
+ description: data.description as string | undefined,
110
+ sourceDir: path.dirname(file),
111
+ skillPath: file,
112
+ })
113
+ }
114
+ return skills
115
+ }
116
+
117
+ async function loadHooks(root: string, hooksField?: ClaudeManifest["hooks"]): Promise<ClaudeHooks | undefined> {
118
+ const hookConfigs: ClaudeHooks[] = []
119
+
120
+ const defaultPath = path.join(root, "hooks", "hooks.json")
121
+ if (await pathExists(defaultPath)) {
122
+ hookConfigs.push(await readJson<ClaudeHooks>(defaultPath))
123
+ }
124
+
125
+ if (hooksField) {
126
+ if (typeof hooksField === "string" || Array.isArray(hooksField)) {
127
+ const hookPaths = toPathList(hooksField)
128
+ for (const hookPath of hookPaths) {
129
+ const resolved = resolveWithinRoot(root, hookPath, "hooks path")
130
+ if (await pathExists(resolved)) {
131
+ hookConfigs.push(await readJson<ClaudeHooks>(resolved))
132
+ }
133
+ }
134
+ } else {
135
+ hookConfigs.push(hooksField)
136
+ }
137
+ }
138
+
139
+ if (hookConfigs.length === 0) return undefined
140
+ return mergeHooks(hookConfigs)
141
+ }
142
+
143
+ async function loadMcpServers(
144
+ root: string,
145
+ manifest: ClaudeManifest,
146
+ ): Promise<Record<string, ClaudeMcpServer> | undefined> {
147
+ const field = manifest.mcpServers
148
+ if (field) {
149
+ if (typeof field === "string" || Array.isArray(field)) {
150
+ return mergeMcpConfigs(await loadMcpPaths(root, field))
151
+ }
152
+ return field as Record<string, ClaudeMcpServer>
153
+ }
154
+
155
+ const mcpPath = path.join(root, ".mcp.json")
156
+ if (await pathExists(mcpPath)) {
157
+ return readJson<Record<string, ClaudeMcpServer>>(mcpPath)
158
+ }
159
+
160
+ return undefined
161
+ }
162
+
163
+ function parseAllowedTools(value: unknown): string[] | undefined {
164
+ if (!value) return undefined
165
+ if (Array.isArray(value)) {
166
+ return value.map((item) => String(item))
167
+ }
168
+ if (typeof value === "string") {
169
+ return value
170
+ .split(/,/)
171
+ .map((item) => item.trim())
172
+ .filter(Boolean)
173
+ }
174
+ return undefined
175
+ }
176
+
177
+ function resolveComponentDirs(
178
+ root: string,
179
+ defaultDir: string,
180
+ custom?: string | string[],
181
+ ): string[] {
182
+ const dirs = [path.join(root, defaultDir)]
183
+ for (const entry of toPathList(custom)) {
184
+ dirs.push(resolveWithinRoot(root, entry, `${defaultDir} path`))
185
+ }
186
+ return dirs
187
+ }
188
+
189
+ function toPathList(value?: string | string[]): string[] {
190
+ if (!value) return []
191
+ if (Array.isArray(value)) return value
192
+ return [value]
193
+ }
194
+
195
+ async function collectMarkdownFiles(dirs: string[]): Promise<string[]> {
196
+ const entries = await collectFiles(dirs)
197
+ return entries.filter((file) => file.endsWith(".md"))
198
+ }
199
+
200
+ async function collectFiles(dirs: string[]): Promise<string[]> {
201
+ const files: string[] = []
202
+ for (const dir of dirs) {
203
+ if (!(await pathExists(dir))) continue
204
+ const entries = await walkFiles(dir)
205
+ files.push(...entries)
206
+ }
207
+ return files
208
+ }
209
+
210
+ function mergeHooks(hooksList: ClaudeHooks[]): ClaudeHooks {
211
+ const merged: ClaudeHooks = { hooks: {} }
212
+ for (const hooks of hooksList) {
213
+ for (const [event, matchers] of Object.entries(hooks.hooks)) {
214
+ if (!merged.hooks[event]) {
215
+ merged.hooks[event] = []
216
+ }
217
+ merged.hooks[event].push(...matchers)
218
+ }
219
+ }
220
+ return merged
221
+ }
222
+
223
+ async function loadMcpPaths(
224
+ root: string,
225
+ value: string | string[],
226
+ ): Promise<Record<string, ClaudeMcpServer>[]> {
227
+ const configs: Record<string, ClaudeMcpServer>[] = []
228
+ for (const entry of toPathList(value)) {
229
+ const resolved = resolveWithinRoot(root, entry, "mcpServers path")
230
+ if (await pathExists(resolved)) {
231
+ configs.push(await readJson<Record<string, ClaudeMcpServer>>(resolved))
232
+ }
233
+ }
234
+ return configs
235
+ }
236
+
237
+ function mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {
238
+ return configs.reduce((acc, config) => ({ ...acc, ...config }), {})
239
+ }
240
+
241
+ function resolveWithinRoot(root: string, entry: string, label: string): string {
242
+ const resolvedRoot = path.resolve(root)
243
+ const resolvedPath = path.resolve(root, entry)
244
+ if (resolvedPath === resolvedRoot || resolvedPath.startsWith(resolvedRoot + path.sep)) {
245
+ return resolvedPath
246
+ }
247
+ throw new Error(`Invalid ${label}: ${entry}. Paths must stay within the plugin root.`)
248
+ }