How the Output Directory Structure Is Determined for Each Target Platform in Compound Engineering
The compound-engineering CLI determines output directory structures by inspecting the basename of the outputRoot argument through target-specific resolver functions, writing files directly into native configuration directories when detected or creating hidden subdirectories otherwise.
The EveryInc/compound-engineering-plugin repository provides a conversion CLI that transforms Claude plugins into platform-specific formats. When converting to targets like OpenCode, Codex, Droid, Cursor, or Pi, the tool must decide whether to write files directly into the specified output path or nest them within platform-specific hidden folders to maintain clean project structures.
Path Resolution Architecture
Each target platform implements a dedicated resolver function that analyzes path.basename(outputRoot) to determine the final layout. These helpers—resolveOpenCodePaths, resolveCodexRoot, resolveDroidPaths, resolveCursorPaths, and resolvePiPaths—separate the concern of where to write from what to write.
The decision logic follows a consistent pattern across all targets:
- Extract the base name of the provided
outputRootpath - Compare against the target's native configuration directory name (e.g.,
.opencode,.codex,.factory) - Write directly if matched; otherwise create a nested hidden directory
Target-Specific Directory Structures
OpenCode
In src/targets/opencode.ts, the resolveOpenCodePaths function checks if the basename equals "opencode" (global install) or ".opencode" (project install). If matched, files write directly; otherwise they nest under .opencode/.
opencode.jsonor.opencode/opencode.jsonagents/or.opencode/agents/plugins/or.opencode/plugins/skills/or.opencode/skills/
Codex
The resolveCodexRoot function in src/targets/codex.ts returns outputRoot unchanged when the basename is ".codex". Any other base name triggers nesting under outputRoot/.codex.
config.toml(or.codex/config.toml)prompts/skills/
Droid
Implemented in src/targets/droid.ts, resolveDroidPaths recognizes .factory as the native Droid configuration directory. Other outputs receive a .factory/ subdirectory.
commands/or.factory/commands/droids/or.factory/droids/skills/or.factory/skills/
Cursor
In src/targets/cursor.ts, the resolveCursorPaths function treats .cursor as the leaf directory. Non-matching bases result in outputRoot/.cursor/.
rules/commands/skills/mcp.json
Pi
The resolvePiPaths function in src/targets/pi.ts handles three distinct cases:
-
Global install:
~/.pi/agent(basenameagent) -
Project-local:
.pidirectory -
Default: Creates
.pi/subdirectory -
skills/ -
prompts/ -
extensions/ -
AGENTS.md(agents block injected)
Practical Examples
Converting to OpenCode with Custom Output
When the output directory does not match the native .opencode name, the CLI creates a nested structure:
bun run src/commands/convert.ts --from ./my-plugin --to opencode --output ./my-plugin-output
Resulting structure:
my-plugin-output/
└── .opencode/
├── opencode.json
├── agents/
├── plugins/
└── skills/
Global Installation for Pi
Targeting the global Pi installation path bypasses subdirectory creation:
bun run src/commands/convert.ts --from ./my-plugin --to pi --output ~/.pi/agent
This writes directly to:
~/.pi/agent/
├── skills/
├── prompts/
├── extensions/
├── compound-engineering/
│ └── mcporter.json
└── AGENTS.md
Codex with Non-Standard Output Path
bun run src/commands/convert.ts --from ./my-plugin --to codex --output ./codex-output
Because codex-output does not equal .codex, the tool nests files:
codex-output/
└── .codex/
├── config.toml
├── prompts/
└── skills/
Key Implementation Files
| File | Responsibility |
|---|---|
src/targets/index.ts |
Registers targets and wires convert and write functions |
src/targets/opencode.ts |
OpenCode conversion logic and resolveOpenCodePaths |
src/targets/codex.ts |
Codex output and resolveCodexRoot |
src/targets/droid.ts |
Droid implementation and resolveDroidPaths |
src/targets/cursor.ts |
Cursor handling and resolveCursorPaths |
src/targets/pi.ts |
Pi platform logic and resolvePiPaths |
src/utils/files.ts |
Shared filesystem utilities for all writers |
Summary
- Basename inspection drives layout: Each target's resolver function examines
path.basename(outputRoot)to determine whether to write files directly or create nested hidden directories. - Native directories write flat: When
outputRootends with the target's native config name (.opencode,.codex,.factory,.cursor, oragentfor Pi), files write directly into that path. - Automatic nesting: Non-matching output paths automatically receive hidden subdirectories (
.opencode/,.codex/, etc.) to maintain clean project structures. - Separation of concerns: Resolver functions handle path logic while writer functions focus on content generation, as implemented across
src/targets/*.ts.
Frequently Asked Questions
How does the CLI know whether to create a subdirectory or write directly to the output path?
The CLI extracts the basename of the outputRoot argument and compares it against the target platform's native configuration directory name. If the basename matches (e.g., .codex for Codex targets or agent for global Pi installs), it writes directly; otherwise it creates a hidden subdirectory with the appropriate name.
Can I force the CLI to write directly to a specific directory without automatic nesting?
Yes, by naming your output directory to match the target's native configuration folder. For example, use --output ./.codex for Codex targets or --output ~/.pi/agent for global Pi installations. The resolver functions in src/targets/*.ts explicitly check for these basenames to bypass subdirectory creation.
What happens when converting to Droid if my output folder is not named .factory?
The resolveDroidPaths function in src/targets/droid.ts creates a .factory/ subdirectory within your specified output path. Your generated files then populate outputRoot/.factory/commands/, outputRoot/.factory/droids/, and outputRoot/.factory/skills/ rather than writing directly to the root.
Where are the resolver functions defined for each target platform?
Each target platform defines its own resolver in its respective file under src/targets/: resolveOpenCodePaths in opencode.ts, resolveCodexRoot in codex.ts, resolveDroidPaths in droid.ts, resolveCursorPaths in cursor.ts, and resolvePiPaths in pi.ts. These functions are imported and used by the main conversion logic in src/targets/index.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →