How to Configure Custom Output Directories for Each Target Platform in Compound Engineering Plugin

Use the --output flag to set a base directory for all targets, and override specific platforms with --codex-home, --pi-home, or target-specific path resolution logic in src/commands/convert.ts.

The compound-engineering-plugin from EveryInc is a CLI tool that converts plugin definitions into agent-compatible bundles for multiple AI platforms. When you need to customize where these generated files land—whether to a global config directory, a project-local folder, or a CI artifact path—the plugin provides granular control through CLI flags and intelligent path resolution.

CLI Flags for Custom Output Directories

The conversion command in src/commands/convert.ts accepts several flags that determine the final write location for each target platform.

The --output Flag (Base Directory)

The --output flag defines the project-relative root for conversion output. When omitted, the CLI defaults to the current working directory.

npx compound-engineering-plugin convert ./my-plugin --output ./build/agents

In src/commands/convert.ts, this value is processed by resolveOutputRoot(args.output) to establish the base path for most targets.

Target-Specific Override Flags

For platforms that typically reside in global config directories, you can override the base directory with dedicated flags:

Flag Target Purpose
--codex-home Codex Absolute path to a .codex directory (e.g., ~/.codex).
--pi-home Pi Absolute path to a .pi/agent directory (e.g., ~/.pi/agent).

These flags are resolved in src/commands/convert.ts using resolveTargetHome, which expands tildes and applies sensible defaults:

// src/commands/convert.ts
const codexHome = resolveTargetHome(
  args.codexHome,
  path.join(os.homedir(), ".codex")
);
const piHome = resolveTargetHome(
  args.piHome,
  path.join(os.homedir(), ".pi", "agent")
);

How Output Paths Are Resolved Internally

After parsing CLI arguments, the resolveTargetOutputRoot function in src/commands/convert.ts maps each target name to its final directory. This function implements the precedence logic that determines whether to use a global home directory, a nested subfolder, or the base output root.

// src/commands/convert.ts
function resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome) {
  if (targetName === "codex")  return codexHome;
  if (targetName === "pi")     return piHome;
  if (targetName === "droid")  return path.join(os.homedir(), ".factory");
  if (targetName === "cursor") return path.join(outputRoot, ".cursor");
  if (targetName === "gemini") return path.join(outputRoot, ".gemini");
  return outputRoot; // OpenCode and others
}

Key behaviors:

  • Codex and Pi respect their respective --*-home flags exclusively.
  • Droid defaults to ~/.factory unless overridden by environment logic.
  • Cursor and Gemini nest under the base --output directory inside hidden folders (.cursor, .gemini).
  • OpenCode uses the base outputRoot directly, with nesting logic handled at the writer level.

Target-Specific Resolver Behavior

Each target platform implements a resolver function that finalizes the directory structure under the root provided by resolveTargetOutputRoot. These resolvers live in src/targets/<target>.ts and handle platform-specific conventions.

OpenCode (opencode.ts)

The resolveOpenCodePaths function in src/targets/opencode.ts determines nesting based on the basename of the output root:

// src/targets/opencode.ts
function resolveOpenCodePaths(outputRoot: string) {
  const base = path.basename(outputRoot);
  
  // If already pointing to .opencode or opencode, use directly
  if (base === ".opencode" || base === "opencode") {
    return { root: outputRoot, /* ... */ };
  }
  
  // Otherwise nest under hidden .opencode folder
  return {
    root: path.join(outputRoot, ".opencode"),
    // ...
  };
}

Codex (codex.ts)

The resolveCodexRoot function in src/targets/codex.ts ensures the .codex extension is present:

// src/targets/codex.ts
function resolveCodexRoot(outputRoot: string) {
  if (path.basename(outputRoot).endsWith(".codex")) {
    return outputRoot; // Use as-is
  }
  // Create .codex subdirectory
  return path.join(outputRoot, ".codex");
}

Pi (pi.ts)

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

// src/targets/pi.ts
function resolvePiPaths(outputRoot: string) {
  const base = path.basename(outputRoot);
  
  // Case 1: Global install at ~/.pi/agent
  if (base === "agent") {
    return {
      skillsDir: path.join(outputRoot, "skills"),
      promptsDir: path.join(outputRoot, "prompts"),
      extensionsDir: path.join(outputRoot, "extensions"),
      mcporterConfigPath: path.join(outputRoot, "compound-engineering", "mcporter.json"),
      agentsPath: path.join(outputRoot, "AGENTS.md"),
    };
  }
  
  // Case 2: Project-local .pi directory
  if (base === ".pi") {
    return { /* same layout, no extra nesting */ };
  }
  
  // Case 3: Custom output root → auto-prefix with .pi
  return {
    skillsDir: path.join(outputRoot, ".pi", "skills"),
    promptsDir: path.join(outputRoot, ".pi", "prompts"),
    extensionsDir: path.join(outputRoot, ".pi", "extensions"),
    mcporterConfigPath: path.join(outputRoot, ".pi", "compound-engineering", "mcporter.json"),
    agentsPath: path.join(outputRoot, "AGENTS.md"),
  };
}

Other Targets (Cursor, Gemini, Droid)

  • Cursor (src/targets/cursor.ts): Uses resolveCursorPaths to write directly to .cursor directories or nest accordingly.
  • Gemini (src/targets/gemini.ts): Uses resolveGeminiPaths with identical logic to Cursor, ensuring .gemini folder structure.
  • Droid (src/targets/droid.ts): Uses resolveDroidPaths to handle ~/.factory global installs or local .factory nesting.

Practical Examples

Basic Conversion with Default OpenCode Output

npx compound-engineering-plugin convert ./my-plugin

# Files appear under $(pwd)/.opencode

Explicit OpenCode Destination

npx compound-engineering-plugin convert ./my-plugin \
  --output ~/.config/opencode

# Writes directly to ~/.config/opencode (no .opencode nesting)

Custom Codex and Pi Outputs

npx compound-engineering-plugin convert ./my-plugin \
  --codex-home ~/.codex \
  --pi-home ./.pi \
  --also codex

# Results:

#   OpenCode → $(pwd)/.opencode

#   Codex    → ~/.codex (direct)

#   Pi       → ./.pi (direct)

Custom Root for Cursor Files

To place Cursor files under build/agent-files:

npx compound-engineering-plugin convert ./my-plugin \
  --output build/agent-files \
  --also cursor

The resolveTargetOutputRoot function maps cursor to build/agent-files/.cursor, and the Cursor writer creates the necessary subdirectories.

Summary

  • Use --output to set the base directory for all target platforms.
  • Override specific platforms with --codex-home and --pi-home for global configuration directories.
  • The resolveTargetOutputRoot function in src/commands/convert.ts implements the precedence logic for mapping targets to directories.
  • Each target implements a resolver (e.g., resolveOpenCodePaths, resolvePiPaths) that handles platform-specific nesting conventions.
  • Codex and Pi support absolute paths to home directories, while Cursor, Gemini, and Droid nest under the base output root by default.

Frequently Asked Questions

What is the difference between --output and --codex-home?

The --output flag sets the base directory for most targets, including OpenCode, Cursor, and Gemini. The --codex-home flag specifically overrides the output location for the Codex target, allowing you to point directly to a global configuration directory like ~/.codex regardless of the base output setting.

How do I prevent the plugin from creating nested .opencode or .cursor folders?

Pass a path that already ends with the target's expected directory name. For OpenCode, use --output ~/.config/opencode (basename opencode). For Cursor, the resolver checks if the basename is .cursor and uses it directly. If the basename does not match, the plugin automatically nests files under the appropriate hidden folder to maintain platform conventions.

Can I write Pi files to the global agent directory while keeping other targets local?

Yes. Use the --pi-home flag to specify the global path while omitting or using --output for local targets. For example: npx compound-engineering-plugin convert ./my-plugin --pi-home ~/.pi/agent --output ./local-agents. This writes Pi files to the global ~/.pi/agent directory while placing OpenCode and other local targets under ./local-agents.

Where is the logic that decides which directory each target uses?

The primary logic resides in src/commands/convert.ts inside the resolveTargetOutputRoot function. This function takes the target name, base output root, and any home directory overrides, then returns the final path. Each target also has its own resolver function (e.g., resolvePiPaths in src/targets/pi.ts) that handles platform-specific directory layout and nesting conventions.

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 →