@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,91 @@
1
+ import path from "path"
2
+ import { copyDir, ensureDir, writeText } from "../utils/files"
3
+ import type { CodexBundle } from "../types/codex"
4
+ import type { ClaudeMcpServer } from "../types/claude"
5
+
6
+ export async function writeCodexBundle(outputRoot: string, bundle: CodexBundle): Promise<void> {
7
+ const codexRoot = resolveCodexRoot(outputRoot)
8
+ await ensureDir(codexRoot)
9
+
10
+ if (bundle.prompts.length > 0) {
11
+ const promptsDir = path.join(codexRoot, "prompts")
12
+ for (const prompt of bundle.prompts) {
13
+ await writeText(path.join(promptsDir, `${prompt.name}.md`), prompt.content + "\n")
14
+ }
15
+ }
16
+
17
+ if (bundle.skillDirs.length > 0) {
18
+ const skillsRoot = path.join(codexRoot, "skills")
19
+ for (const skill of bundle.skillDirs) {
20
+ await copyDir(skill.sourceDir, path.join(skillsRoot, skill.name))
21
+ }
22
+ }
23
+
24
+ if (bundle.generatedSkills.length > 0) {
25
+ const skillsRoot = path.join(codexRoot, "skills")
26
+ for (const skill of bundle.generatedSkills) {
27
+ await writeText(path.join(skillsRoot, skill.name, "SKILL.md"), skill.content + "\n")
28
+ }
29
+ }
30
+
31
+ const config = renderCodexConfig(bundle.mcpServers)
32
+ if (config) {
33
+ await writeText(path.join(codexRoot, "config.toml"), config)
34
+ }
35
+ }
36
+
37
+ function resolveCodexRoot(outputRoot: string): string {
38
+ return path.basename(outputRoot) === ".codex" ? outputRoot : path.join(outputRoot, ".codex")
39
+ }
40
+
41
+ export function renderCodexConfig(mcpServers?: Record<string, ClaudeMcpServer>): string | null {
42
+ if (!mcpServers || Object.keys(mcpServers).length === 0) return null
43
+
44
+ const lines: string[] = ["# Generated by compound-plugin", ""]
45
+
46
+ for (const [name, server] of Object.entries(mcpServers)) {
47
+ const key = formatTomlKey(name)
48
+ lines.push(`[mcp_servers.${key}]`)
49
+
50
+ if (server.command) {
51
+ lines.push(`command = ${formatTomlString(server.command)}`)
52
+ if (server.args && server.args.length > 0) {
53
+ const args = server.args.map((arg) => formatTomlString(arg)).join(", ")
54
+ lines.push(`args = [${args}]`)
55
+ }
56
+
57
+ if (server.env && Object.keys(server.env).length > 0) {
58
+ lines.push("")
59
+ lines.push(`[mcp_servers.${key}.env]`)
60
+ for (const [envKey, value] of Object.entries(server.env)) {
61
+ lines.push(`${formatTomlKey(envKey)} = ${formatTomlString(value)}`)
62
+ }
63
+ }
64
+ } else if (server.url) {
65
+ lines.push(`url = ${formatTomlString(server.url)}`)
66
+ if (server.headers && Object.keys(server.headers).length > 0) {
67
+ lines.push(`http_headers = ${formatTomlInlineTable(server.headers)}`)
68
+ }
69
+ }
70
+
71
+ lines.push("")
72
+ }
73
+
74
+ return lines.join("\n")
75
+ }
76
+
77
+ function formatTomlString(value: string): string {
78
+ return JSON.stringify(value)
79
+ }
80
+
81
+ function formatTomlKey(value: string): string {
82
+ if (/^[A-Za-z0-9_-]+$/.test(value)) return value
83
+ return JSON.stringify(value)
84
+ }
85
+
86
+ function formatTomlInlineTable(entries: Record<string, string>): string {
87
+ const parts = Object.entries(entries).map(
88
+ ([key, value]) => `${formatTomlKey(key)} = ${formatTomlString(value)}`,
89
+ )
90
+ return `{ ${parts.join(", ")} }`
91
+ }
@@ -0,0 +1,29 @@
1
+ import type { ClaudePlugin } from "../types/claude"
2
+ import type { OpenCodeBundle } from "../types/opencode"
3
+ import type { CodexBundle } from "../types/codex"
4
+ import { convertClaudeToOpenCode, type ClaudeToOpenCodeOptions } from "../converters/claude-to-opencode"
5
+ import { convertClaudeToCodex } from "../converters/claude-to-codex"
6
+ import { writeOpenCodeBundle } from "./opencode"
7
+ import { writeCodexBundle } from "./codex"
8
+
9
+ export type TargetHandler<TBundle = unknown> = {
10
+ name: string
11
+ implemented: boolean
12
+ convert: (plugin: ClaudePlugin, options: ClaudeToOpenCodeOptions) => TBundle | null
13
+ write: (outputRoot: string, bundle: TBundle) => Promise<void>
14
+ }
15
+
16
+ export const targets: Record<string, TargetHandler> = {
17
+ opencode: {
18
+ name: "opencode",
19
+ implemented: true,
20
+ convert: convertClaudeToOpenCode,
21
+ write: writeOpenCodeBundle,
22
+ },
23
+ codex: {
24
+ name: "codex",
25
+ implemented: true,
26
+ convert: convertClaudeToCodex as TargetHandler<CodexBundle>["convert"],
27
+ write: writeCodexBundle as TargetHandler<CodexBundle>["write"],
28
+ },
29
+ }
@@ -0,0 +1,48 @@
1
+ import path from "path"
2
+ import { copyDir, ensureDir, writeJson, writeText } from "../utils/files"
3
+ import type { OpenCodeBundle } from "../types/opencode"
4
+
5
+ export async function writeOpenCodeBundle(outputRoot: string, bundle: OpenCodeBundle): Promise<void> {
6
+ const paths = resolveOpenCodePaths(outputRoot)
7
+ await ensureDir(paths.root)
8
+ await writeJson(paths.configPath, bundle.config)
9
+
10
+ const agentsDir = paths.agentsDir
11
+ for (const agent of bundle.agents) {
12
+ await writeText(path.join(agentsDir, `${agent.name}.md`), agent.content + "\n")
13
+ }
14
+
15
+ if (bundle.plugins.length > 0) {
16
+ const pluginsDir = paths.pluginsDir
17
+ for (const plugin of bundle.plugins) {
18
+ await writeText(path.join(pluginsDir, plugin.name), plugin.content + "\n")
19
+ }
20
+ }
21
+
22
+ if (bundle.skillDirs.length > 0) {
23
+ const skillsRoot = paths.skillsDir
24
+ for (const skill of bundle.skillDirs) {
25
+ await copyDir(skill.sourceDir, path.join(skillsRoot, skill.name))
26
+ }
27
+ }
28
+ }
29
+
30
+ function resolveOpenCodePaths(outputRoot: string) {
31
+ if (path.basename(outputRoot) === ".opencode") {
32
+ return {
33
+ root: outputRoot,
34
+ configPath: path.join(outputRoot, "opencode.json"),
35
+ agentsDir: path.join(outputRoot, "agents"),
36
+ pluginsDir: path.join(outputRoot, "plugins"),
37
+ skillsDir: path.join(outputRoot, "skills"),
38
+ }
39
+ }
40
+
41
+ return {
42
+ root: outputRoot,
43
+ configPath: path.join(outputRoot, "opencode.json"),
44
+ agentsDir: path.join(outputRoot, ".opencode", "agents"),
45
+ pluginsDir: path.join(outputRoot, ".opencode", "plugins"),
46
+ skillsDir: path.join(outputRoot, ".opencode", "skills"),
47
+ }
48
+ }
@@ -0,0 +1,88 @@
1
+ export type ClaudeMcpServer = {
2
+ type?: string
3
+ command?: string
4
+ args?: string[]
5
+ url?: string
6
+ env?: Record<string, string>
7
+ headers?: Record<string, string>
8
+ }
9
+
10
+ export type ClaudeManifest = {
11
+ name: string
12
+ version: string
13
+ description?: string
14
+ author?: {
15
+ name?: string
16
+ email?: string
17
+ url?: string
18
+ }
19
+ keywords?: string[]
20
+ agents?: string | string[]
21
+ commands?: string | string[]
22
+ skills?: string | string[]
23
+ hooks?: string | string[] | ClaudeHooks
24
+ mcpServers?: Record<string, ClaudeMcpServer> | string | string[]
25
+ }
26
+
27
+ export type ClaudeAgent = {
28
+ name: string
29
+ description?: string
30
+ capabilities?: string[]
31
+ model?: string
32
+ body: string
33
+ sourcePath: string
34
+ }
35
+
36
+ export type ClaudeCommand = {
37
+ name: string
38
+ description?: string
39
+ argumentHint?: string
40
+ model?: string
41
+ allowedTools?: string[]
42
+ body: string
43
+ sourcePath: string
44
+ }
45
+
46
+ export type ClaudeSkill = {
47
+ name: string
48
+ description?: string
49
+ sourceDir: string
50
+ skillPath: string
51
+ }
52
+
53
+ export type ClaudePlugin = {
54
+ root: string
55
+ manifest: ClaudeManifest
56
+ agents: ClaudeAgent[]
57
+ commands: ClaudeCommand[]
58
+ skills: ClaudeSkill[]
59
+ hooks?: ClaudeHooks
60
+ mcpServers?: Record<string, ClaudeMcpServer>
61
+ }
62
+
63
+ export type ClaudeHookCommand = {
64
+ type: "command"
65
+ command: string
66
+ timeout?: number
67
+ }
68
+
69
+ export type ClaudeHookPrompt = {
70
+ type: "prompt"
71
+ prompt: string
72
+ }
73
+
74
+ export type ClaudeHookAgent = {
75
+ type: "agent"
76
+ agent: string
77
+ }
78
+
79
+ export type ClaudeHookEntry = ClaudeHookCommand | ClaudeHookPrompt | ClaudeHookAgent
80
+
81
+ export type ClaudeHookMatcher = {
82
+ matcher: string
83
+ hooks: ClaudeHookEntry[]
84
+ }
85
+
86
+ export type ClaudeHooks = {
87
+ hooks: Record<string, ClaudeHookMatcher[]>
88
+ }
@@ -0,0 +1,23 @@
1
+ import type { ClaudeMcpServer } from "./claude"
2
+
3
+ export type CodexPrompt = {
4
+ name: string
5
+ content: string
6
+ }
7
+
8
+ export type CodexSkillDir = {
9
+ name: string
10
+ sourceDir: string
11
+ }
12
+
13
+ export type CodexGeneratedSkill = {
14
+ name: string
15
+ content: string
16
+ }
17
+
18
+ export type CodexBundle = {
19
+ prompts: CodexPrompt[]
20
+ skillDirs: CodexSkillDir[]
21
+ generatedSkills: CodexGeneratedSkill[]
22
+ mcpServers?: Record<string, ClaudeMcpServer>
23
+ }
@@ -0,0 +1,54 @@
1
+ export type OpenCodePermission = "allow" | "ask" | "deny"
2
+
3
+ export type OpenCodeConfig = {
4
+ $schema?: string
5
+ model?: string
6
+ default_agent?: string
7
+ tools?: Record<string, boolean>
8
+ permission?: Record<string, OpenCodePermission | Record<string, OpenCodePermission>>
9
+ agent?: Record<string, OpenCodeAgentConfig>
10
+ command?: Record<string, OpenCodeCommandConfig>
11
+ mcp?: Record<string, OpenCodeMcpServer>
12
+ }
13
+
14
+ export type OpenCodeAgentConfig = {
15
+ description?: string
16
+ mode?: "primary" | "subagent"
17
+ model?: string
18
+ temperature?: number
19
+ tools?: Record<string, boolean>
20
+ permission?: Record<string, OpenCodePermission>
21
+ }
22
+
23
+ export type OpenCodeCommandConfig = {
24
+ description?: string
25
+ model?: string
26
+ agent?: string
27
+ template: string
28
+ }
29
+
30
+ export type OpenCodeMcpServer = {
31
+ type: "local" | "remote"
32
+ command?: string[]
33
+ url?: string
34
+ environment?: Record<string, string>
35
+ headers?: Record<string, string>
36
+ enabled?: boolean
37
+ }
38
+
39
+ export type OpenCodeAgentFile = {
40
+ name: string
41
+ content: string
42
+ }
43
+
44
+ export type OpenCodePluginFile = {
45
+ name: string
46
+ content: string
47
+ }
48
+
49
+ export type OpenCodeBundle = {
50
+ config: OpenCodeConfig
51
+ agents: OpenCodeAgentFile[]
52
+ plugins: OpenCodePluginFile[]
53
+ skillDirs: { sourceDir: string; name: string }[]
54
+ }
@@ -0,0 +1,64 @@
1
+ import path from "path"
2
+ import { ensureDir, pathExists, readText, writeText } from "./files"
3
+
4
+ export const CODEX_AGENTS_BLOCK_START = "<!-- BEGIN COMPOUND CODEX TOOL MAP -->"
5
+ export const CODEX_AGENTS_BLOCK_END = "<!-- END COMPOUND CODEX TOOL MAP -->"
6
+
7
+ const CODEX_AGENTS_BLOCK_BODY = `## Compound Codex Tool Mapping (Claude Compatibility)
8
+
9
+ This section maps Claude Code plugin tool references to Codex behavior.
10
+ Only this block is managed automatically.
11
+
12
+ Tool mapping:
13
+ - Read: use shell reads (cat/sed) or rg
14
+ - Write: create files via shell redirection or apply_patch
15
+ - Edit/MultiEdit: use apply_patch
16
+ - Bash: use shell_command
17
+ - Grep: use rg (fallback: grep)
18
+ - Glob: use rg --files or find
19
+ - LS: use ls via shell_command
20
+ - WebFetch/WebSearch: use curl or Context7 for library docs
21
+ - AskUserQuestion/Question: ask the user in chat
22
+ - Task/Subagent/Parallel: run sequentially in main thread; use multi_tool_use.parallel for tool calls
23
+ - TodoWrite/TodoRead: use file-based todos in todos/ with file-todos skill
24
+ - Skill: open the referenced SKILL.md and follow it
25
+ - ExitPlanMode: ignore
26
+ `
27
+
28
+ export async function ensureCodexAgentsFile(codexHome: string): Promise<void> {
29
+ await ensureDir(codexHome)
30
+ const filePath = path.join(codexHome, "AGENTS.md")
31
+ const block = buildCodexAgentsBlock()
32
+
33
+ if (!(await pathExists(filePath))) {
34
+ await writeText(filePath, block + "\n")
35
+ return
36
+ }
37
+
38
+ const existing = await readText(filePath)
39
+ const updated = upsertBlock(existing, block)
40
+ if (updated !== existing) {
41
+ await writeText(filePath, updated)
42
+ }
43
+ }
44
+
45
+ function buildCodexAgentsBlock(): string {
46
+ return [CODEX_AGENTS_BLOCK_START, CODEX_AGENTS_BLOCK_BODY.trim(), CODEX_AGENTS_BLOCK_END].join("\n")
47
+ }
48
+
49
+ function upsertBlock(existing: string, block: string): string {
50
+ const startIndex = existing.indexOf(CODEX_AGENTS_BLOCK_START)
51
+ const endIndex = existing.indexOf(CODEX_AGENTS_BLOCK_END)
52
+
53
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
54
+ const before = existing.slice(0, startIndex).trimEnd()
55
+ const after = existing.slice(endIndex + CODEX_AGENTS_BLOCK_END.length).trimStart()
56
+ return [before, block, after].filter(Boolean).join("\n\n") + "\n"
57
+ }
58
+
59
+ if (existing.trim().length === 0) {
60
+ return block + "\n"
61
+ }
62
+
63
+ return existing.trimEnd() + "\n\n" + block + "\n"
64
+ }
@@ -0,0 +1,64 @@
1
+ import { promises as fs } from "fs"
2
+ import path from "path"
3
+
4
+ export async function pathExists(filePath: string): Promise<boolean> {
5
+ try {
6
+ await fs.access(filePath)
7
+ return true
8
+ } catch {
9
+ return false
10
+ }
11
+ }
12
+
13
+ export async function ensureDir(dirPath: string): Promise<void> {
14
+ await fs.mkdir(dirPath, { recursive: true })
15
+ }
16
+
17
+ export async function readText(filePath: string): Promise<string> {
18
+ return fs.readFile(filePath, "utf8")
19
+ }
20
+
21
+ export async function readJson<T>(filePath: string): Promise<T> {
22
+ const raw = await readText(filePath)
23
+ return JSON.parse(raw) as T
24
+ }
25
+
26
+ export async function writeText(filePath: string, content: string): Promise<void> {
27
+ await ensureDir(path.dirname(filePath))
28
+ await fs.writeFile(filePath, content, "utf8")
29
+ }
30
+
31
+ export async function writeJson(filePath: string, data: unknown): Promise<void> {
32
+ const content = JSON.stringify(data, null, 2)
33
+ await writeText(filePath, content + "\n")
34
+ }
35
+
36
+ export async function walkFiles(root: string): Promise<string[]> {
37
+ const entries = await fs.readdir(root, { withFileTypes: true })
38
+ const results: string[] = []
39
+ for (const entry of entries) {
40
+ const fullPath = path.join(root, entry.name)
41
+ if (entry.isDirectory()) {
42
+ const nested = await walkFiles(fullPath)
43
+ results.push(...nested)
44
+ } else if (entry.isFile()) {
45
+ results.push(fullPath)
46
+ }
47
+ }
48
+ return results
49
+ }
50
+
51
+ export async function copyDir(sourceDir: string, targetDir: string): Promise<void> {
52
+ await ensureDir(targetDir)
53
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true })
54
+ for (const entry of entries) {
55
+ const sourcePath = path.join(sourceDir, entry.name)
56
+ const targetPath = path.join(targetDir, entry.name)
57
+ if (entry.isDirectory()) {
58
+ await copyDir(sourcePath, targetPath)
59
+ } else if (entry.isFile()) {
60
+ await ensureDir(path.dirname(targetPath))
61
+ await fs.copyFile(sourcePath, targetPath)
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,65 @@
1
+ import { load } from "js-yaml"
2
+
3
+ export type FrontmatterResult = {
4
+ data: Record<string, unknown>
5
+ body: string
6
+ }
7
+
8
+ export function parseFrontmatter(raw: string): FrontmatterResult {
9
+ const lines = raw.split(/\r?\n/)
10
+ if (lines.length === 0 || lines[0].trim() !== "---") {
11
+ return { data: {}, body: raw }
12
+ }
13
+
14
+ let endIndex = -1
15
+ for (let i = 1; i < lines.length; i += 1) {
16
+ if (lines[i].trim() === "---") {
17
+ endIndex = i
18
+ break
19
+ }
20
+ }
21
+
22
+ if (endIndex === -1) {
23
+ return { data: {}, body: raw }
24
+ }
25
+
26
+ const yamlText = lines.slice(1, endIndex).join("\n")
27
+ const body = lines.slice(endIndex + 1).join("\n")
28
+ const parsed = load(yamlText)
29
+ const data = (parsed && typeof parsed === "object") ? (parsed as Record<string, unknown>) : {}
30
+ return { data, body }
31
+ }
32
+
33
+ export function formatFrontmatter(data: Record<string, unknown>, body: string): string {
34
+ const yaml = Object.entries(data)
35
+ .filter(([, value]) => value !== undefined)
36
+ .map(([key, value]) => formatYamlLine(key, value))
37
+ .join("\n")
38
+
39
+ if (yaml.trim().length === 0) {
40
+ return body
41
+ }
42
+
43
+ return [`---`, yaml, `---`, "", body].join("\n")
44
+ }
45
+
46
+ function formatYamlLine(key: string, value: unknown): string {
47
+ if (Array.isArray(value)) {
48
+ const items = value.map((item) => ` - ${formatYamlValue(item)}`)
49
+ return [key + ":", ...items].join("\n")
50
+ }
51
+ return `${key}: ${formatYamlValue(value)}`
52
+ }
53
+
54
+ function formatYamlValue(value: unknown): string {
55
+ if (value === null || value === undefined) return ""
56
+ if (typeof value === "number" || typeof value === "boolean") return String(value)
57
+ const raw = String(value)
58
+ if (raw.includes("\n")) {
59
+ return `|\n${raw.split("\n").map((line) => ` ${line}`).join("\n")}`
60
+ }
61
+ if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{")) {
62
+ return JSON.stringify(raw)
63
+ }
64
+ return raw
65
+ }
@@ -0,0 +1,89 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import path from "path"
3
+ import { loadClaudePlugin } from "../src/parsers/claude"
4
+
5
+ const fixtureRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
6
+ const mcpFixtureRoot = path.join(import.meta.dir, "fixtures", "mcp-file")
7
+ const customPathsRoot = path.join(import.meta.dir, "fixtures", "custom-paths")
8
+ const invalidCommandPathRoot = path.join(import.meta.dir, "fixtures", "invalid-command-path")
9
+ const invalidHooksPathRoot = path.join(import.meta.dir, "fixtures", "invalid-hooks-path")
10
+ const invalidMcpPathRoot = path.join(import.meta.dir, "fixtures", "invalid-mcp-path")
11
+
12
+ describe("loadClaudePlugin", () => {
13
+ test("loads manifest, agents, commands, skills, hooks", async () => {
14
+ const plugin = await loadClaudePlugin(fixtureRoot)
15
+
16
+ expect(plugin.manifest.name).toBe("compound-engineering")
17
+ expect(plugin.agents.length).toBe(2)
18
+ expect(plugin.commands.length).toBe(6)
19
+ expect(plugin.skills.length).toBe(1)
20
+ expect(plugin.hooks).toBeDefined()
21
+ expect(plugin.mcpServers).toBeDefined()
22
+
23
+ const researchAgent = plugin.agents.find((agent) => agent.name === "repo-research-analyst")
24
+ expect(researchAgent?.capabilities).toEqual(["Capability A", "Capability B"])
25
+
26
+ const reviewCommand = plugin.commands.find((command) => command.name === "workflows:review")
27
+ expect(reviewCommand?.allowedTools).toEqual([
28
+ "Read",
29
+ "Write",
30
+ "Edit",
31
+ "Bash(ls:*)",
32
+ "Bash(git:*)",
33
+ "Grep",
34
+ "Glob",
35
+ "List",
36
+ "Patch",
37
+ "Task",
38
+ ])
39
+
40
+ const planReview = plugin.commands.find((command) => command.name === "plan_review")
41
+ expect(planReview?.allowedTools).toEqual(["Read", "Edit"])
42
+
43
+ const skillCommand = plugin.commands.find((command) => command.name === "create-agent-skill")
44
+ expect(skillCommand?.allowedTools).toEqual(["Skill(create-agent-skills)"])
45
+
46
+ const modelCommand = plugin.commands.find((command) => command.name === "workflows:work")
47
+ expect(modelCommand?.allowedTools).toEqual(["WebFetch"])
48
+
49
+ const patternCommand = plugin.commands.find((command) => command.name === "report-bug")
50
+ expect(patternCommand?.allowedTools).toEqual(["Read(.env)", "Bash(git:*)"])
51
+
52
+ const planCommand = plugin.commands.find((command) => command.name === "workflows:plan")
53
+ expect(planCommand?.allowedTools).toEqual(["Question", "TodoWrite", "TodoRead"])
54
+
55
+ expect(plugin.mcpServers?.context7?.url).toBe("https://mcp.context7.com/mcp")
56
+ })
57
+
58
+ test("loads MCP servers from .mcp.json when manifest is empty", async () => {
59
+ const plugin = await loadClaudePlugin(mcpFixtureRoot)
60
+ expect(plugin.mcpServers?.remote?.url).toBe("https://example.com/stream")
61
+ })
62
+
63
+ test("merges default and custom component paths", async () => {
64
+ const plugin = await loadClaudePlugin(customPathsRoot)
65
+ expect(plugin.agents.map((agent) => agent.name).sort()).toEqual(["custom-agent", "default-agent"])
66
+ expect(plugin.commands.map((command) => command.name).sort()).toEqual(["custom-command", "default-command"])
67
+ expect(plugin.skills.map((skill) => skill.name).sort()).toEqual(["custom-skill", "default-skill"])
68
+ expect(plugin.hooks?.hooks.PreToolUse?.[0]?.hooks[0]?.command).toBe("echo default")
69
+ expect(plugin.hooks?.hooks.PostToolUse?.[0]?.hooks[0]?.command).toBe("echo custom")
70
+ })
71
+
72
+ test("rejects custom component paths that escape the plugin root", async () => {
73
+ await expect(loadClaudePlugin(invalidCommandPathRoot)).rejects.toThrow(
74
+ "Invalid commands path: ../outside-commands. Paths must stay within the plugin root.",
75
+ )
76
+ })
77
+
78
+ test("rejects hook paths that escape the plugin root", async () => {
79
+ await expect(loadClaudePlugin(invalidHooksPathRoot)).rejects.toThrow(
80
+ "Invalid hooks path: ../outside-hooks.json. Paths must stay within the plugin root.",
81
+ )
82
+ })
83
+
84
+ test("rejects MCP paths that escape the plugin root", async () => {
85
+ await expect(loadClaudePlugin(invalidMcpPathRoot)).rejects.toThrow(
86
+ "Invalid mcpServers path: ../outside-mcp.json. Paths must stay within the plugin root.",
87
+ )
88
+ })
89
+ })