@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.
- package/.claude-plugin/marketplace.json +37 -0
- package/.github/workflows/deploy-docs.yml +39 -0
- package/AGENTS.md +48 -0
- package/CLAUDE.md +380 -0
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/bun.lock +30 -0
- package/docs/css/docs.css +675 -0
- package/docs/css/style.css +2886 -0
- package/docs/index.html +1046 -0
- package/docs/js/main.js +225 -0
- package/docs/pages/agents.html +649 -0
- package/docs/pages/changelog.html +495 -0
- package/docs/pages/commands.html +523 -0
- package/docs/pages/getting-started.html +582 -0
- package/docs/pages/mcp-servers.html +409 -0
- package/docs/pages/skills.html +611 -0
- package/docs/solutions/plugin-versioning-requirements.md +77 -0
- package/docs/specs/claude-code.md +67 -0
- package/docs/specs/codex.md +59 -0
- package/docs/specs/opencode.md +57 -0
- package/package.json +26 -0
- package/plans/grow-your-own-garden-plugin-architecture.md +102 -0
- package/plans/landing-page-launchkit-refresh.md +279 -0
- package/plugins/coding-tutor/.claude-plugin/plugin.json +9 -0
- package/plugins/coding-tutor/README.md +37 -0
- package/plugins/coding-tutor/commands/quiz-me.md +1 -0
- package/plugins/coding-tutor/commands/sync-tutorials.md +25 -0
- package/plugins/coding-tutor/commands/teach-me.md +1 -0
- package/plugins/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
- package/plugins/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +207 -0
- package/plugins/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +193 -0
- package/plugins/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
- package/plugins/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +118 -0
- package/plugins/compound-engineering/.claude-plugin/plugin.json +33 -0
- package/plugins/compound-engineering/CHANGELOG.md +393 -0
- package/plugins/compound-engineering/CLAUDE.md +90 -0
- package/plugins/compound-engineering/LICENSE +21 -0
- package/plugins/compound-engineering/README.md +219 -0
- package/plugins/compound-engineering/agents/design/design-implementation-reviewer.md +94 -0
- package/plugins/compound-engineering/agents/design/design-iterator.md +197 -0
- package/plugins/compound-engineering/agents/design/figma-design-sync.md +172 -0
- package/plugins/compound-engineering/agents/docs/ankane-readme-writer.md +50 -0
- package/plugins/compound-engineering/agents/research/best-practices-researcher.md +100 -0
- package/plugins/compound-engineering/agents/research/framework-docs-researcher.md +83 -0
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +42 -0
- package/plugins/compound-engineering/agents/research/repo-research-analyst.md +113 -0
- package/plugins/compound-engineering/agents/review/agent-native-reviewer.md +246 -0
- package/plugins/compound-engineering/agents/review/architecture-strategist.md +52 -0
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +85 -0
- package/plugins/compound-engineering/agents/review/data-integrity-guardian.md +70 -0
- package/plugins/compound-engineering/agents/review/data-migration-expert.md +97 -0
- package/plugins/compound-engineering/agents/review/deployment-verification-agent.md +159 -0
- package/plugins/compound-engineering/agents/review/dhh-rails-reviewer.md +45 -0
- package/plugins/compound-engineering/agents/review/julik-frontend-races-reviewer.md +222 -0
- package/plugins/compound-engineering/agents/review/kieran-python-reviewer.md +104 -0
- package/plugins/compound-engineering/agents/review/kieran-rails-reviewer.md +86 -0
- package/plugins/compound-engineering/agents/review/kieran-typescript-reviewer.md +95 -0
- package/plugins/compound-engineering/agents/review/pattern-recognition-specialist.md +57 -0
- package/plugins/compound-engineering/agents/review/performance-oracle.md +110 -0
- package/plugins/compound-engineering/agents/review/security-sentinel.md +93 -0
- package/plugins/compound-engineering/agents/workflow/bug-reproduction-validator.md +67 -0
- package/plugins/compound-engineering/agents/workflow/every-style-editor.md +64 -0
- package/plugins/compound-engineering/agents/workflow/lint.md +16 -0
- package/plugins/compound-engineering/agents/workflow/pr-comment-resolver.md +69 -0
- package/plugins/compound-engineering/agents/workflow/spec-flow-analyzer.md +113 -0
- package/plugins/compound-engineering/commands/agent-native-audit.md +277 -0
- package/plugins/compound-engineering/commands/changelog.md +137 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +8 -0
- package/plugins/compound-engineering/commands/deepen-plan.md +546 -0
- package/plugins/compound-engineering/commands/deploy-docs.md +112 -0
- package/plugins/compound-engineering/commands/feature-video.md +342 -0
- package/plugins/compound-engineering/commands/generate_command.md +162 -0
- package/plugins/compound-engineering/commands/heal-skill.md +142 -0
- package/plugins/compound-engineering/commands/lfg.md +19 -0
- package/plugins/compound-engineering/commands/plan_review.md +7 -0
- package/plugins/compound-engineering/commands/release-docs.md +211 -0
- package/plugins/compound-engineering/commands/report-bug.md +150 -0
- package/plugins/compound-engineering/commands/reproduce-bug.md +99 -0
- package/plugins/compound-engineering/commands/resolve_parallel.md +34 -0
- package/plugins/compound-engineering/commands/resolve_pr_parallel.md +49 -0
- package/plugins/compound-engineering/commands/resolve_todo_parallel.md +35 -0
- package/plugins/compound-engineering/commands/test-browser.md +339 -0
- package/plugins/compound-engineering/commands/triage.md +310 -0
- package/plugins/compound-engineering/commands/workflows/compound.md +202 -0
- package/plugins/compound-engineering/commands/workflows/plan.md +466 -0
- package/plugins/compound-engineering/commands/workflows/review.md +514 -0
- package/plugins/compound-engineering/commands/workflows/work.md +363 -0
- package/plugins/compound-engineering/commands/xcode-test.md +331 -0
- package/plugins/compound-engineering/skills/agent-browser/SKILL.md +223 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/SKILL.md +435 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/plugins/compound-engineering/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/SKILL.md +184 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/resources.md +119 -0
- package/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/plugins/compound-engineering/skills/compound-docs/SKILL.md +510 -0
- package/plugins/compound-engineering/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/plugins/compound-engineering/skills/compound-docs/assets/resolution-template.md +93 -0
- package/plugins/compound-engineering/skills/compound-docs/references/yaml-schema.md +65 -0
- package/plugins/compound-engineering/skills/compound-docs/schema.yaml +176 -0
- package/plugins/compound-engineering/skills/create-agent-skills/SKILL.md +299 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/api-security.md +226 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/best-practices.md +404 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/core-principles.md +437 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/executable-code.md +175 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/official-spec.md +185 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/skill-structure.md +372 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/using-templates.md +112 -0
- package/plugins/compound-engineering/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/plugins/compound-engineering/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/plugins/compound-engineering/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/SKILL.md +185 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/architecture.md +653 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/controllers.md +303 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/frontend.md +510 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/gems.md +266 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/models.md +359 -0
- package/plugins/compound-engineering/skills/dhh-rails-style/references/testing.md +338 -0
- package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +594 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +265 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +623 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +338 -0
- package/plugins/compound-engineering/skills/every-style-editor/SKILL.md +134 -0
- package/plugins/compound-engineering/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/plugins/compound-engineering/skills/file-todos/SKILL.md +251 -0
- package/plugins/compound-engineering/skills/file-todos/assets/todo-template.md +155 -0
- package/plugins/compound-engineering/skills/frontend-design/SKILL.md +42 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/SKILL.md +237 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/requirements.txt +2 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/scripts/compose_images.py +157 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/scripts/edit_image.py +144 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/scripts/gemini_images.py +263 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/scripts/generate_image.py +133 -0
- package/plugins/compound-engineering/skills/gemini-imagegen/scripts/multi_turn_chat.py +216 -0
- package/plugins/compound-engineering/skills/git-worktree/SKILL.md +302 -0
- package/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh +345 -0
- package/plugins/compound-engineering/skills/rclone/SKILL.md +150 -0
- package/plugins/compound-engineering/skills/rclone/scripts/check_setup.sh +60 -0
- package/plugins/compound-engineering/skills/skill-creator/SKILL.md +209 -0
- package/plugins/compound-engineering/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/compound-engineering/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/compound-engineering/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/src/commands/convert.ts +156 -0
- package/src/commands/install.ts +221 -0
- package/src/commands/list.ts +37 -0
- package/src/converters/claude-to-codex.ts +124 -0
- package/src/converters/claude-to-opencode.ts +392 -0
- package/src/index.ts +20 -0
- package/src/parsers/claude.ts +248 -0
- package/src/targets/codex.ts +91 -0
- package/src/targets/index.ts +29 -0
- package/src/targets/opencode.ts +48 -0
- package/src/types/claude.ts +88 -0
- package/src/types/codex.ts +23 -0
- package/src/types/opencode.ts +54 -0
- package/src/utils/codex-agents.ts +64 -0
- package/src/utils/files.ts +64 -0
- package/src/utils/frontmatter.ts +65 -0
- package/tests/claude-parser.test.ts +89 -0
- package/tests/cli.test.ts +289 -0
- package/tests/codex-agents.test.ts +62 -0
- package/tests/codex-converter.test.ts +121 -0
- package/tests/codex-writer.test.ts +76 -0
- package/tests/converter.test.ts +171 -0
- package/tests/fixtures/custom-paths/.claude-plugin/plugin.json +8 -0
- package/tests/fixtures/custom-paths/agents/default-agent.md +5 -0
- package/tests/fixtures/custom-paths/commands/default-command.md +5 -0
- package/tests/fixtures/custom-paths/custom-agents/custom-agent.md +5 -0
- package/tests/fixtures/custom-paths/custom-commands/custom-command.md +5 -0
- package/tests/fixtures/custom-paths/custom-hooks/hooks.json +7 -0
- package/tests/fixtures/custom-paths/custom-skills/custom-skill/SKILL.md +5 -0
- package/tests/fixtures/custom-paths/hooks/hooks.json +7 -0
- package/tests/fixtures/custom-paths/skills/default-skill/SKILL.md +5 -0
- package/tests/fixtures/invalid-command-path/.claude-plugin/plugin.json +5 -0
- package/tests/fixtures/invalid-hooks-path/.claude-plugin/plugin.json +5 -0
- package/tests/fixtures/invalid-mcp-path/.claude-plugin/plugin.json +5 -0
- package/tests/fixtures/mcp-file/.claude-plugin/plugin.json +5 -0
- package/tests/fixtures/mcp-file/.mcp.json +6 -0
- package/tests/fixtures/sample-plugin/.claude-plugin/plugin.json +30 -0
- package/tests/fixtures/sample-plugin/agents/agent-one.md +10 -0
- package/tests/fixtures/sample-plugin/agents/security-reviewer.md +7 -0
- package/tests/fixtures/sample-plugin/commands/command-one.md +7 -0
- package/tests/fixtures/sample-plugin/commands/model-command.md +8 -0
- package/tests/fixtures/sample-plugin/commands/nested/command-two.md +9 -0
- package/tests/fixtures/sample-plugin/commands/pattern-command.md +7 -0
- package/tests/fixtures/sample-plugin/commands/skill-command.md +7 -0
- package/tests/fixtures/sample-plugin/commands/todo-command.md +7 -0
- package/tests/fixtures/sample-plugin/hooks/hooks.json +156 -0
- package/tests/fixtures/sample-plugin/skills/skill-one/SKILL.md +6 -0
- package/tests/frontmatter.test.ts +20 -0
- package/tests/opencode-writer.test.ts +62 -0
- 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
|
+
})
|