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:

  1. Extract the base name of the provided outputRoot path
  2. Compare against the target's native configuration directory name (e.g., .opencode, .codex, .factory)
  3. 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/.

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.

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/.

Pi

The resolvePiPaths function in src/targets/pi.ts handles three distinct cases:

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 outputRoot ends with the target's native config name (.opencode, .codex, .factory, .cursor, or agent for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →