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
--*-homeflags exclusively. - Droid defaults to
~/.factoryunless overridden by environment logic. - Cursor and Gemini nest under the base
--outputdirectory inside hidden folders (.cursor,.gemini). - OpenCode uses the base
outputRootdirectly, 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): UsesresolveCursorPathsto write directly to.cursordirectories or nest accordingly. - Gemini (
src/targets/gemini.ts): UsesresolveGeminiPathswith identical logic to Cursor, ensuring.geminifolder structure. - Droid (
src/targets/droid.ts): UsesresolveDroidPathsto handle~/.factoryglobal installs or local.factorynesting.
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
--outputto set the base directory for all target platforms. - Override specific platforms with
--codex-homeand--pi-homefor global configuration directories. - The
resolveTargetOutputRootfunction insrc/commands/convert.tsimplements 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →