compound-engineering-pi 0.2.3
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/LICENSE +21 -0
- package/README.md +124 -0
- package/bin/compound-engineering-pi +12 -0
- package/bin/compound-plugin +12 -0
- package/compound-engineering-pi +12 -0
- package/compound-plugin +5 -0
- package/docs/pi.md +152 -0
- package/extensions/compound-engineering-compat.ts +452 -0
- package/package.json +84 -0
- package/pi-resources/compound-engineering/mcporter.json +7 -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 +457 -0
- package/plugins/compound-engineering/CLAUDE.md +89 -0
- package/plugins/compound-engineering/LICENSE +21 -0
- package/plugins/compound-engineering/README.md +232 -0
- package/plugins/compound-engineering/agents/design/design-implementation-reviewer.md +109 -0
- package/plugins/compound-engineering/agents/design/design-iterator.md +224 -0
- package/plugins/compound-engineering/agents/design/figma-design-sync.md +190 -0
- package/plugins/compound-engineering/agents/docs/ankane-readme-writer.md +65 -0
- package/plugins/compound-engineering/agents/research/best-practices-researcher.md +126 -0
- package/plugins/compound-engineering/agents/research/framework-docs-researcher.md +106 -0
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +59 -0
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +264 -0
- package/plugins/compound-engineering/agents/research/repo-research-analyst.md +135 -0
- package/plugins/compound-engineering/agents/review/agent-native-reviewer.md +261 -0
- package/plugins/compound-engineering/agents/review/architecture-strategist.md +67 -0
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +101 -0
- package/plugins/compound-engineering/agents/review/data-integrity-guardian.md +85 -0
- package/plugins/compound-engineering/agents/review/data-migration-expert.md +112 -0
- package/plugins/compound-engineering/agents/review/deployment-verification-agent.md +174 -0
- package/plugins/compound-engineering/agents/review/dhh-rails-reviewer.md +66 -0
- package/plugins/compound-engineering/agents/review/julik-frontend-races-reviewer.md +221 -0
- package/plugins/compound-engineering/agents/review/kieran-python-reviewer.md +133 -0
- package/plugins/compound-engineering/agents/review/kieran-rails-reviewer.md +115 -0
- package/plugins/compound-engineering/agents/review/kieran-typescript-reviewer.md +124 -0
- package/plugins/compound-engineering/agents/review/pattern-recognition-specialist.md +72 -0
- package/plugins/compound-engineering/agents/review/performance-oracle.md +137 -0
- package/plugins/compound-engineering/agents/review/schema-drift-detector.md +154 -0
- package/plugins/compound-engineering/agents/review/security-sentinel.md +114 -0
- package/plugins/compound-engineering/agents/workflow/bug-reproduction-validator.md +82 -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 +84 -0
- package/plugins/compound-engineering/agents/workflow/spec-flow-analyzer.md +134 -0
- package/plugins/compound-engineering/commands/agent-native-audit.md +278 -0
- package/plugins/compound-engineering/commands/changelog.md +138 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +9 -0
- package/plugins/compound-engineering/commands/deepen-plan.md +546 -0
- package/plugins/compound-engineering/commands/deploy-docs.md +113 -0
- package/plugins/compound-engineering/commands/feature-video.md +342 -0
- package/plugins/compound-engineering/commands/generate_command.md +163 -0
- package/plugins/compound-engineering/commands/heal-skill.md +143 -0
- package/plugins/compound-engineering/commands/lfg.md +20 -0
- package/plugins/compound-engineering/commands/release-docs.md +212 -0
- package/plugins/compound-engineering/commands/report-bug.md +151 -0
- package/plugins/compound-engineering/commands/reproduce-bug.md +100 -0
- package/plugins/compound-engineering/commands/resolve_parallel.md +35 -0
- package/plugins/compound-engineering/commands/resolve_todo_parallel.md +37 -0
- package/plugins/compound-engineering/commands/slfg.md +32 -0
- package/plugins/compound-engineering/commands/technical_review.md +8 -0
- package/plugins/compound-engineering/commands/test-browser.md +339 -0
- package/plugins/compound-engineering/commands/test-xcode.md +332 -0
- package/plugins/compound-engineering/commands/triage.md +311 -0
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +124 -0
- package/plugins/compound-engineering/commands/workflows/compound.md +239 -0
- package/plugins/compound-engineering/commands/workflows/plan.md +551 -0
- package/plugins/compound-engineering/commands/workflows/review.md +526 -0
- package/plugins/compound-engineering/commands/workflows/work.md +433 -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/brainstorming/SKILL.md +190 -0
- package/plugins/compound-engineering/skills/compound-docs/SKILL.md +511 -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 +275 -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 +134 -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 +152 -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/document-review/SKILL.md +87 -0
- package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +737 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +187 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +300 -0
- package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +221 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +674 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/observability.md +366 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +603 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +418 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/toolsets.md +502 -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 +252 -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 +337 -0
- package/plugins/compound-engineering/skills/orchestrating-swarms/SKILL.md +1718 -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/resolve-pr-parallel/SKILL.md +89 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/scripts/get-pr-comments +68 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/scripts/resolve-pr-thread +23 -0
- package/plugins/compound-engineering/skills/skill-creator/SKILL.md +210 -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/prompts/deepen-plan.md +549 -0
- package/prompts/feature-video.md +341 -0
- package/prompts/resolve_todo_parallel.md +36 -0
- package/prompts/test-browser.md +342 -0
- package/prompts/workflows-brainstorm.md +123 -0
- package/prompts/workflows-compound.md +238 -0
- package/prompts/workflows-plan.md +550 -0
- package/prompts/workflows-review.md +529 -0
- package/prompts/workflows-work.md +432 -0
- package/skills/agent-browser/SKILL.md +223 -0
- package/skills/agent-native-architecture/SKILL.md +435 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/skills/agent-native-reviewer/SKILL.md +260 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +184 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +119 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/ankane-readme-writer/SKILL.md +63 -0
- package/skills/architecture-strategist/SKILL.md +66 -0
- package/skills/best-practices-researcher/SKILL.md +125 -0
- package/skills/brainstorming/SKILL.md +190 -0
- package/skills/bug-reproduction-validator/SKILL.md +81 -0
- package/skills/code-simplicity-reviewer/SKILL.md +100 -0
- package/skills/compound-docs/SKILL.md +511 -0
- package/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/skills/compound-docs/assets/resolution-template.md +93 -0
- package/skills/compound-docs/references/yaml-schema.md +65 -0
- package/skills/compound-docs/schema.yaml +176 -0
- package/skills/create-agent-skills/SKILL.md +275 -0
- package/skills/create-agent-skills/references/api-security.md +226 -0
- package/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/skills/create-agent-skills/references/best-practices.md +404 -0
- package/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/skills/create-agent-skills/references/core-principles.md +437 -0
- package/skills/create-agent-skills/references/executable-code.md +175 -0
- package/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/skills/create-agent-skills/references/official-spec.md +134 -0
- package/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/skills/create-agent-skills/references/skill-structure.md +152 -0
- package/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/skills/create-agent-skills/references/using-templates.md +112 -0
- package/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/skills/data-integrity-guardian/SKILL.md +84 -0
- package/skills/data-migration-expert/SKILL.md +111 -0
- package/skills/deployment-verification-agent/SKILL.md +173 -0
- package/skills/design-implementation-reviewer/SKILL.md +107 -0
- package/skills/design-iterator/SKILL.md +222 -0
- package/skills/dhh-rails-reviewer/SKILL.md +65 -0
- package/skills/dhh-rails-style/SKILL.md +185 -0
- package/skills/dhh-rails-style/references/architecture.md +653 -0
- package/skills/dhh-rails-style/references/controllers.md +303 -0
- package/skills/dhh-rails-style/references/frontend.md +510 -0
- package/skills/dhh-rails-style/references/gems.md +266 -0
- package/skills/dhh-rails-style/references/models.md +359 -0
- package/skills/dhh-rails-style/references/testing.md +338 -0
- package/skills/document-review/SKILL.md +87 -0
- package/skills/dspy-ruby/SKILL.md +737 -0
- package/skills/dspy-ruby/assets/config-template.rb +187 -0
- package/skills/dspy-ruby/assets/module-template.rb +300 -0
- package/skills/dspy-ruby/assets/signature-template.rb +221 -0
- package/skills/dspy-ruby/references/core-concepts.md +674 -0
- package/skills/dspy-ruby/references/observability.md +366 -0
- package/skills/dspy-ruby/references/optimization.md +603 -0
- package/skills/dspy-ruby/references/providers.md +418 -0
- package/skills/dspy-ruby/references/toolsets.md +502 -0
- package/skills/every-style-editor/SKILL.md +134 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/every-style-editor-2/SKILL.md +62 -0
- package/skills/figma-design-sync/SKILL.md +188 -0
- package/skills/file-todos/SKILL.md +252 -0
- package/skills/file-todos/assets/todo-template.md +155 -0
- package/skills/framework-docs-researcher/SKILL.md +105 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/gemini-imagegen/SKILL.md +237 -0
- package/skills/gemini-imagegen/requirements.txt +2 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +157 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +144 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +263 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +133 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +216 -0
- package/skills/git-history-analyzer/SKILL.md +58 -0
- package/skills/git-worktree/SKILL.md +302 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +337 -0
- package/skills/julik-frontend-races-reviewer/SKILL.md +220 -0
- package/skills/kieran-python-reviewer/SKILL.md +132 -0
- package/skills/kieran-rails-reviewer/SKILL.md +114 -0
- package/skills/kieran-typescript-reviewer/SKILL.md +123 -0
- package/skills/learnings-researcher/SKILL.md +263 -0
- package/skills/lint/SKILL.md +14 -0
- package/skills/orchestrating-swarms/SKILL.md +1718 -0
- package/skills/pattern-recognition-specialist/SKILL.md +71 -0
- package/skills/performance-oracle/SKILL.md +136 -0
- package/skills/pr-comment-resolver/SKILL.md +82 -0
- package/skills/rclone/SKILL.md +150 -0
- package/skills/rclone/scripts/check_setup.sh +60 -0
- package/skills/repo-research-analyst/SKILL.md +134 -0
- package/skills/resolve_pr_parallel/SKILL.md +89 -0
- package/skills/resolve_pr_parallel/scripts/get-pr-comments +68 -0
- package/skills/resolve_pr_parallel/scripts/resolve-pr-thread +23 -0
- package/skills/schema-drift-detector/SKILL.md +153 -0
- package/skills/security-sentinel/SKILL.md +113 -0
- package/skills/skill-creator/SKILL.md +210 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/skills/spec-flow-analyzer/SKILL.md +133 -0
- package/src/commands/convert.ts +183 -0
- package/src/commands/install.ts +273 -0
- package/src/commands/list.ts +37 -0
- package/src/commands/sync.ts +89 -0
- package/src/converters/claude-to-codex.ts +182 -0
- package/src/converters/claude-to-opencode.ts +395 -0
- package/src/converters/claude-to-pi.ts +205 -0
- package/src/index.ts +22 -0
- package/src/parsers/claude-home.ts +65 -0
- package/src/parsers/claude.ts +252 -0
- package/src/sync/codex.ts +92 -0
- package/src/sync/opencode.ts +75 -0
- package/src/sync/pi.ts +88 -0
- package/src/targets/codex.ts +96 -0
- package/src/targets/index.ts +38 -0
- package/src/targets/opencode.ts +57 -0
- package/src/targets/pi.ts +131 -0
- package/src/templates/pi/compat-extension.ts +452 -0
- package/src/types/claude.ts +90 -0
- package/src/types/codex.ts +23 -0
- package/src/types/opencode.ts +54 -0
- package/src/types/pi.ts +40 -0
- package/src/utils/codex-agents.ts +64 -0
- package/src/utils/files.ts +77 -0
- package/src/utils/frontmatter.ts +65 -0
- package/src/utils/symlink.ts +43 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { formatFrontmatter } from "../utils/frontmatter"
|
|
2
|
+
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
|
|
3
|
+
import type {
|
|
4
|
+
PiBundle,
|
|
5
|
+
PiGeneratedSkill,
|
|
6
|
+
PiMcporterConfig,
|
|
7
|
+
PiMcporterServer,
|
|
8
|
+
} from "../types/pi"
|
|
9
|
+
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
|
|
10
|
+
import { PI_COMPAT_EXTENSION_SOURCE } from "../templates/pi/compat-extension"
|
|
11
|
+
|
|
12
|
+
export type ClaudeToPiOptions = ClaudeToOpenCodeOptions
|
|
13
|
+
|
|
14
|
+
const PI_DESCRIPTION_MAX_LENGTH = 1024
|
|
15
|
+
|
|
16
|
+
export function convertClaudeToPi(
|
|
17
|
+
plugin: ClaudePlugin,
|
|
18
|
+
_options: ClaudeToPiOptions,
|
|
19
|
+
): PiBundle {
|
|
20
|
+
const promptNames = new Set<string>()
|
|
21
|
+
const usedSkillNames = new Set<string>(plugin.skills.map((skill) => normalizeName(skill.name)))
|
|
22
|
+
|
|
23
|
+
const prompts = plugin.commands
|
|
24
|
+
.filter((command) => !command.disableModelInvocation)
|
|
25
|
+
.map((command) => convertPrompt(command, promptNames))
|
|
26
|
+
|
|
27
|
+
const generatedSkills = plugin.agents.map((agent) => convertAgent(agent, usedSkillNames))
|
|
28
|
+
|
|
29
|
+
const extensions = [
|
|
30
|
+
{
|
|
31
|
+
name: "compound-engineering-compat.ts",
|
|
32
|
+
content: PI_COMPAT_EXTENSION_SOURCE,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
prompts,
|
|
38
|
+
skillDirs: plugin.skills.map((skill) => ({
|
|
39
|
+
name: skill.name,
|
|
40
|
+
sourceDir: skill.sourceDir,
|
|
41
|
+
})),
|
|
42
|
+
generatedSkills,
|
|
43
|
+
extensions,
|
|
44
|
+
mcporterConfig: plugin.mcpServers ? convertMcpToMcporter(plugin.mcpServers) : undefined,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function convertPrompt(command: ClaudeCommand, usedNames: Set<string>) {
|
|
49
|
+
const name = uniqueName(normalizeName(command.name), usedNames)
|
|
50
|
+
const frontmatter: Record<string, unknown> = {
|
|
51
|
+
description: command.description,
|
|
52
|
+
"argument-hint": command.argumentHint,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let body = transformContentForPi(command.body)
|
|
56
|
+
body = appendCompatibilityNoteIfNeeded(body)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
content: formatFrontmatter(frontmatter, body.trim()),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function convertAgent(agent: ClaudeAgent, usedNames: Set<string>): PiGeneratedSkill {
|
|
65
|
+
const name = uniqueName(normalizeName(agent.name), usedNames)
|
|
66
|
+
const description = sanitizeDescription(
|
|
67
|
+
agent.description ?? `Converted from Claude agent ${agent.name}`,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const frontmatter: Record<string, unknown> = {
|
|
71
|
+
name,
|
|
72
|
+
description,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sections: string[] = []
|
|
76
|
+
if (agent.capabilities && agent.capabilities.length > 0) {
|
|
77
|
+
sections.push(`## Capabilities\n${agent.capabilities.map((capability) => `- ${capability}`).join("\n")}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const body = [
|
|
81
|
+
...sections,
|
|
82
|
+
agent.body.trim().length > 0
|
|
83
|
+
? agent.body.trim()
|
|
84
|
+
: `Instructions converted from the ${agent.name} agent.`,
|
|
85
|
+
].join("\n\n")
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
name,
|
|
89
|
+
content: formatFrontmatter(frontmatter, body),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function transformContentForPi(body: string): string {
|
|
94
|
+
let result = body
|
|
95
|
+
|
|
96
|
+
// Task repo-research-analyst(feature_description)
|
|
97
|
+
// -> Run subagent with agent="repo-research-analyst" and task="feature_description"
|
|
98
|
+
const taskPattern = /^(\s*-?\s*)Task\s+([a-z][a-z0-9-]*)\(([^)]+)\)/gm
|
|
99
|
+
result = result.replace(taskPattern, (_match, prefix: string, agentName: string, args: string) => {
|
|
100
|
+
const skillName = normalizeName(agentName)
|
|
101
|
+
const trimmedArgs = args.trim().replace(/\s+/g, " ")
|
|
102
|
+
return `${prefix}Run subagent with agent=\"${skillName}\" and task=\"${trimmedArgs}\".`
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Claude-specific tool references
|
|
106
|
+
result = result.replace(/\bAskUserQuestion\b/g, "ask_user_question")
|
|
107
|
+
result = result.replace(/\bTodoWrite\b/g, "file-based todos (todos/ + /skill:file-todos)")
|
|
108
|
+
result = result.replace(/\bTodoRead\b/g, "file-based todos (todos/ + /skill:file-todos)")
|
|
109
|
+
|
|
110
|
+
// /command-name or /workflows:command-name -> /workflows-command-name
|
|
111
|
+
const slashCommandPattern = /(?<![:\w])\/([a-z][a-z0-9_:-]*?)(?=[\s,."')\]}`]|$)/gi
|
|
112
|
+
result = result.replace(slashCommandPattern, (match, commandName: string) => {
|
|
113
|
+
if (commandName.includes("/")) return match
|
|
114
|
+
if (["dev", "tmp", "etc", "usr", "var", "bin", "home"].includes(commandName)) {
|
|
115
|
+
return match
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (commandName.startsWith("skill:")) {
|
|
119
|
+
const skillName = commandName.slice("skill:".length)
|
|
120
|
+
return `/skill:${normalizeName(skillName)}`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const withoutPrefix = commandName.startsWith("prompts:")
|
|
124
|
+
? commandName.slice("prompts:".length)
|
|
125
|
+
: commandName
|
|
126
|
+
|
|
127
|
+
return `/${normalizeName(withoutPrefix)}`
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function appendCompatibilityNoteIfNeeded(body: string): string {
|
|
134
|
+
if (!/\bmcp\b/i.test(body)) return body
|
|
135
|
+
|
|
136
|
+
const note = [
|
|
137
|
+
"",
|
|
138
|
+
"## Pi + MCPorter note",
|
|
139
|
+
"For MCP access in Pi, use MCPorter via the generated tools:",
|
|
140
|
+
"- `mcporter_list` to inspect available MCP tools",
|
|
141
|
+
"- `mcporter_call` to invoke a tool",
|
|
142
|
+
"",
|
|
143
|
+
].join("\n")
|
|
144
|
+
|
|
145
|
+
return body + note
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function convertMcpToMcporter(servers: Record<string, ClaudeMcpServer>): PiMcporterConfig {
|
|
149
|
+
const mcpServers: Record<string, PiMcporterServer> = {}
|
|
150
|
+
|
|
151
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
152
|
+
if (server.command) {
|
|
153
|
+
mcpServers[name] = {
|
|
154
|
+
command: server.command,
|
|
155
|
+
args: server.args,
|
|
156
|
+
env: server.env,
|
|
157
|
+
headers: server.headers,
|
|
158
|
+
}
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (server.url) {
|
|
163
|
+
mcpServers[name] = {
|
|
164
|
+
baseUrl: server.url,
|
|
165
|
+
headers: server.headers,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { mcpServers }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeName(value: string): string {
|
|
174
|
+
const trimmed = value.trim()
|
|
175
|
+
if (!trimmed) return "item"
|
|
176
|
+
const normalized = trimmed
|
|
177
|
+
.toLowerCase()
|
|
178
|
+
.replace(/[\\/]+/g, "-")
|
|
179
|
+
.replace(/[:\s]+/g, "-")
|
|
180
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
181
|
+
.replace(/-+/g, "-")
|
|
182
|
+
.replace(/^-+|-+$/g, "")
|
|
183
|
+
return normalized || "item"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function sanitizeDescription(value: string, maxLength = PI_DESCRIPTION_MAX_LENGTH): string {
|
|
187
|
+
const normalized = value.replace(/\s+/g, " ").trim()
|
|
188
|
+
if (normalized.length <= maxLength) return normalized
|
|
189
|
+
const ellipsis = "..."
|
|
190
|
+
return normalized.slice(0, Math.max(0, maxLength - ellipsis.length)).trimEnd() + ellipsis
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function uniqueName(base: string, used: Set<string>): string {
|
|
194
|
+
if (!used.has(base)) {
|
|
195
|
+
used.add(base)
|
|
196
|
+
return base
|
|
197
|
+
}
|
|
198
|
+
let index = 2
|
|
199
|
+
while (used.has(`${base}-${index}`)) {
|
|
200
|
+
index += 1
|
|
201
|
+
}
|
|
202
|
+
const name = `${base}-${index}`
|
|
203
|
+
used.add(name)
|
|
204
|
+
return name
|
|
205
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
import sync from "./commands/sync"
|
|
7
|
+
|
|
8
|
+
const main = defineCommand({
|
|
9
|
+
meta: {
|
|
10
|
+
name: "compound-plugin",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
description: "Convert Claude Code plugins into other agent formats",
|
|
13
|
+
},
|
|
14
|
+
subCommands: {
|
|
15
|
+
convert: () => convert,
|
|
16
|
+
install: () => install,
|
|
17
|
+
list: () => listCommand,
|
|
18
|
+
sync: () => sync,
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
runMain(main)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import os from "os"
|
|
3
|
+
import fs from "fs/promises"
|
|
4
|
+
import type { ClaudeSkill, ClaudeMcpServer } from "../types/claude"
|
|
5
|
+
|
|
6
|
+
export interface ClaudeHomeConfig {
|
|
7
|
+
skills: ClaudeSkill[]
|
|
8
|
+
mcpServers: Record<string, ClaudeMcpServer>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function loadClaudeHome(claudeHome?: string): Promise<ClaudeHomeConfig> {
|
|
12
|
+
const home = claudeHome ?? path.join(os.homedir(), ".claude")
|
|
13
|
+
|
|
14
|
+
const [skills, mcpServers] = await Promise.all([
|
|
15
|
+
loadPersonalSkills(path.join(home, "skills")),
|
|
16
|
+
loadSettingsMcp(path.join(home, "settings.json")),
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
return { skills, mcpServers }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function loadPersonalSkills(skillsDir: string): Promise<ClaudeSkill[]> {
|
|
23
|
+
try {
|
|
24
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true })
|
|
25
|
+
const skills: ClaudeSkill[] = []
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
// Check if directory or symlink (symlinks are common for skills)
|
|
29
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
|
|
30
|
+
|
|
31
|
+
const entryPath = path.join(skillsDir, entry.name)
|
|
32
|
+
const skillPath = path.join(entryPath, "SKILL.md")
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(skillPath)
|
|
36
|
+
// Resolve symlink to get the actual source directory
|
|
37
|
+
const sourceDir = entry.isSymbolicLink()
|
|
38
|
+
? await fs.realpath(entryPath)
|
|
39
|
+
: entryPath
|
|
40
|
+
skills.push({
|
|
41
|
+
name: entry.name,
|
|
42
|
+
sourceDir,
|
|
43
|
+
skillPath,
|
|
44
|
+
})
|
|
45
|
+
} catch {
|
|
46
|
+
// No SKILL.md, skip
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return skills
|
|
50
|
+
} catch {
|
|
51
|
+
return [] // Directory doesn't exist
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function loadSettingsMcp(
|
|
56
|
+
settingsPath: string,
|
|
57
|
+
): Promise<Record<string, ClaudeMcpServer>> {
|
|
58
|
+
try {
|
|
59
|
+
const content = await fs.readFile(settingsPath, "utf-8")
|
|
60
|
+
const settings = JSON.parse(content) as { mcpServers?: Record<string, ClaudeMcpServer> }
|
|
61
|
+
return settings.mcpServers ?? {}
|
|
62
|
+
} catch {
|
|
63
|
+
return {} // File doesn't exist or invalid JSON
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
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
|
+
const disableModelInvocation = data["disable-model-invocation"] === true ? true : undefined
|
|
87
|
+
commands.push({
|
|
88
|
+
name,
|
|
89
|
+
description: data.description as string | undefined,
|
|
90
|
+
argumentHint: data["argument-hint"] as string | undefined,
|
|
91
|
+
model: data.model as string | undefined,
|
|
92
|
+
allowedTools,
|
|
93
|
+
disableModelInvocation,
|
|
94
|
+
body: body.trim(),
|
|
95
|
+
sourcePath: file,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
return commands
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function loadSkills(skillsDirs: string[]): Promise<ClaudeSkill[]> {
|
|
102
|
+
const entries = await collectFiles(skillsDirs)
|
|
103
|
+
const skillFiles = entries.filter((file) => path.basename(file) === "SKILL.md")
|
|
104
|
+
const skills: ClaudeSkill[] = []
|
|
105
|
+
for (const file of skillFiles) {
|
|
106
|
+
const raw = await readText(file)
|
|
107
|
+
const { data } = parseFrontmatter(raw)
|
|
108
|
+
const name = (data.name as string) ?? path.basename(path.dirname(file))
|
|
109
|
+
const disableModelInvocation = data["disable-model-invocation"] === true ? true : undefined
|
|
110
|
+
skills.push({
|
|
111
|
+
name,
|
|
112
|
+
description: data.description as string | undefined,
|
|
113
|
+
disableModelInvocation,
|
|
114
|
+
sourceDir: path.dirname(file),
|
|
115
|
+
skillPath: file,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return skills
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function loadHooks(root: string, hooksField?: ClaudeManifest["hooks"]): Promise<ClaudeHooks | undefined> {
|
|
122
|
+
const hookConfigs: ClaudeHooks[] = []
|
|
123
|
+
|
|
124
|
+
const defaultPath = path.join(root, "hooks", "hooks.json")
|
|
125
|
+
if (await pathExists(defaultPath)) {
|
|
126
|
+
hookConfigs.push(await readJson<ClaudeHooks>(defaultPath))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (hooksField) {
|
|
130
|
+
if (typeof hooksField === "string" || Array.isArray(hooksField)) {
|
|
131
|
+
const hookPaths = toPathList(hooksField)
|
|
132
|
+
for (const hookPath of hookPaths) {
|
|
133
|
+
const resolved = resolveWithinRoot(root, hookPath, "hooks path")
|
|
134
|
+
if (await pathExists(resolved)) {
|
|
135
|
+
hookConfigs.push(await readJson<ClaudeHooks>(resolved))
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
hookConfigs.push(hooksField)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (hookConfigs.length === 0) return undefined
|
|
144
|
+
return mergeHooks(hookConfigs)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function loadMcpServers(
|
|
148
|
+
root: string,
|
|
149
|
+
manifest: ClaudeManifest,
|
|
150
|
+
): Promise<Record<string, ClaudeMcpServer> | undefined> {
|
|
151
|
+
const field = manifest.mcpServers
|
|
152
|
+
if (field) {
|
|
153
|
+
if (typeof field === "string" || Array.isArray(field)) {
|
|
154
|
+
return mergeMcpConfigs(await loadMcpPaths(root, field))
|
|
155
|
+
}
|
|
156
|
+
return field as Record<string, ClaudeMcpServer>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const mcpPath = path.join(root, ".mcp.json")
|
|
160
|
+
if (await pathExists(mcpPath)) {
|
|
161
|
+
return readJson<Record<string, ClaudeMcpServer>>(mcpPath)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseAllowedTools(value: unknown): string[] | undefined {
|
|
168
|
+
if (!value) return undefined
|
|
169
|
+
if (Array.isArray(value)) {
|
|
170
|
+
return value.map((item) => String(item))
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "string") {
|
|
173
|
+
return value
|
|
174
|
+
.split(/,/)
|
|
175
|
+
.map((item) => item.trim())
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
}
|
|
178
|
+
return undefined
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveComponentDirs(
|
|
182
|
+
root: string,
|
|
183
|
+
defaultDir: string,
|
|
184
|
+
custom?: string | string[],
|
|
185
|
+
): string[] {
|
|
186
|
+
const dirs = [path.join(root, defaultDir)]
|
|
187
|
+
for (const entry of toPathList(custom)) {
|
|
188
|
+
dirs.push(resolveWithinRoot(root, entry, `${defaultDir} path`))
|
|
189
|
+
}
|
|
190
|
+
return dirs
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function toPathList(value?: string | string[]): string[] {
|
|
194
|
+
if (!value) return []
|
|
195
|
+
if (Array.isArray(value)) return value
|
|
196
|
+
return [value]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function collectMarkdownFiles(dirs: string[]): Promise<string[]> {
|
|
200
|
+
const entries = await collectFiles(dirs)
|
|
201
|
+
return entries.filter((file) => file.endsWith(".md"))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function collectFiles(dirs: string[]): Promise<string[]> {
|
|
205
|
+
const files: string[] = []
|
|
206
|
+
for (const dir of dirs) {
|
|
207
|
+
if (!(await pathExists(dir))) continue
|
|
208
|
+
const entries = await walkFiles(dir)
|
|
209
|
+
files.push(...entries)
|
|
210
|
+
}
|
|
211
|
+
return files
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function mergeHooks(hooksList: ClaudeHooks[]): ClaudeHooks {
|
|
215
|
+
const merged: ClaudeHooks = { hooks: {} }
|
|
216
|
+
for (const hooks of hooksList) {
|
|
217
|
+
for (const [event, matchers] of Object.entries(hooks.hooks)) {
|
|
218
|
+
if (!merged.hooks[event]) {
|
|
219
|
+
merged.hooks[event] = []
|
|
220
|
+
}
|
|
221
|
+
merged.hooks[event].push(...matchers)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return merged
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function loadMcpPaths(
|
|
228
|
+
root: string,
|
|
229
|
+
value: string | string[],
|
|
230
|
+
): Promise<Record<string, ClaudeMcpServer>[]> {
|
|
231
|
+
const configs: Record<string, ClaudeMcpServer>[] = []
|
|
232
|
+
for (const entry of toPathList(value)) {
|
|
233
|
+
const resolved = resolveWithinRoot(root, entry, "mcpServers path")
|
|
234
|
+
if (await pathExists(resolved)) {
|
|
235
|
+
configs.push(await readJson<Record<string, ClaudeMcpServer>>(resolved))
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return configs
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {
|
|
242
|
+
return configs.reduce((acc, config) => ({ ...acc, ...config }), {})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function resolveWithinRoot(root: string, entry: string, label: string): string {
|
|
246
|
+
const resolvedRoot = path.resolve(root)
|
|
247
|
+
const resolvedPath = path.resolve(root, entry)
|
|
248
|
+
if (resolvedPath === resolvedRoot || resolvedPath.startsWith(resolvedRoot + path.sep)) {
|
|
249
|
+
return resolvedPath
|
|
250
|
+
}
|
|
251
|
+
throw new Error(`Invalid ${label}: ${entry}. Paths must stay within the plugin root.`)
|
|
252
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from "fs/promises"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import type { ClaudeHomeConfig } from "../parsers/claude-home"
|
|
4
|
+
import type { ClaudeMcpServer } from "../types/claude"
|
|
5
|
+
import { forceSymlink, isValidSkillName } from "../utils/symlink"
|
|
6
|
+
|
|
7
|
+
export async function syncToCodex(
|
|
8
|
+
config: ClaudeHomeConfig,
|
|
9
|
+
outputRoot: string,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
// Ensure output directories exist
|
|
12
|
+
const skillsDir = path.join(outputRoot, "skills")
|
|
13
|
+
await fs.mkdir(skillsDir, { recursive: true })
|
|
14
|
+
|
|
15
|
+
// Symlink skills (with validation)
|
|
16
|
+
for (const skill of config.skills) {
|
|
17
|
+
if (!isValidSkillName(skill.name)) {
|
|
18
|
+
console.warn(`Skipping skill with invalid name: ${skill.name}`)
|
|
19
|
+
continue
|
|
20
|
+
}
|
|
21
|
+
const target = path.join(skillsDir, skill.name)
|
|
22
|
+
await forceSymlink(skill.sourceDir, target)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Write MCP servers to config.toml (TOML format)
|
|
26
|
+
if (Object.keys(config.mcpServers).length > 0) {
|
|
27
|
+
const configPath = path.join(outputRoot, "config.toml")
|
|
28
|
+
const mcpToml = convertMcpForCodex(config.mcpServers)
|
|
29
|
+
|
|
30
|
+
// Read existing config and merge idempotently
|
|
31
|
+
let existingContent = ""
|
|
32
|
+
try {
|
|
33
|
+
existingContent = await fs.readFile(configPath, "utf-8")
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
36
|
+
throw err
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Remove any existing Claude Code MCP section to make idempotent
|
|
41
|
+
const marker = "# MCP servers synced from Claude Code"
|
|
42
|
+
const markerIndex = existingContent.indexOf(marker)
|
|
43
|
+
if (markerIndex !== -1) {
|
|
44
|
+
existingContent = existingContent.slice(0, markerIndex).trimEnd()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const newContent = existingContent
|
|
48
|
+
? existingContent + "\n\n" + marker + "\n" + mcpToml
|
|
49
|
+
: "# Codex config - synced from Claude Code\n\n" + mcpToml
|
|
50
|
+
|
|
51
|
+
await fs.writeFile(configPath, newContent, { mode: 0o600 })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Escape a string for TOML double-quoted strings */
|
|
56
|
+
function escapeTomlString(str: string): string {
|
|
57
|
+
return str
|
|
58
|
+
.replace(/\\/g, "\\\\")
|
|
59
|
+
.replace(/"/g, '\\"')
|
|
60
|
+
.replace(/\n/g, "\\n")
|
|
61
|
+
.replace(/\r/g, "\\r")
|
|
62
|
+
.replace(/\t/g, "\\t")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function convertMcpForCodex(servers: Record<string, ClaudeMcpServer>): string {
|
|
66
|
+
const sections: string[] = []
|
|
67
|
+
|
|
68
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
69
|
+
if (!server.command) continue
|
|
70
|
+
|
|
71
|
+
const lines: string[] = []
|
|
72
|
+
lines.push(`[mcp_servers.${name}]`)
|
|
73
|
+
lines.push(`command = "${escapeTomlString(server.command)}"`)
|
|
74
|
+
|
|
75
|
+
if (server.args && server.args.length > 0) {
|
|
76
|
+
const argsStr = server.args.map((arg) => `"${escapeTomlString(arg)}"`).join(", ")
|
|
77
|
+
lines.push(`args = [${argsStr}]`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
81
|
+
lines.push("")
|
|
82
|
+
lines.push(`[mcp_servers.${name}.env]`)
|
|
83
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
84
|
+
lines.push(`${key} = "${escapeTomlString(value)}"`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
sections.push(lines.join("\n"))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return sections.join("\n\n") + "\n"
|
|
92
|
+
}
|