How to Add a New Target Provider to the Converter Architecture in Compound Engineering
To add a new target provider to the converter architecture, you must define a bundle type, implement a converter function, create a provider-specific writer module, and register the provider in the target registry.
The EveryInc/compound-engineering-plugin CLI uses a modular conversion pipeline built around target providers. Each provider transforms Claude plugin data into a platform-specific bundle format. When you add a new target provider to the converter architecture, you extend the CLI to support additional output formats like Codex, OpenCode, or custom internal tools.
Core Components of the Converter Architecture
Every target provider consists of three integrated components that work together to transform and persist plugin data.
Target Registry
The target registry declares the provider’s metadata, implementation status, and handler functions. It lives in src/targets/index.ts and maps provider names to their conversion and writing logic. The registry entry for Codex spans lines 35‑40【/tmp/instagit__i48kegw/src/targets/index.ts#L35-L40】.
Writer Module
The writer module materializes the provider-specific bundle on disk. It handles directory creation, file writing, and configuration generation. Each provider has its own writer file at src/targets/<provider>.ts. The Codex writer’s core logic appears at lines 6‑8【/tmp/instagit__i48kegw/src/targets/codex.ts#L6-L8】.
Bundle Type and Converter
The bundle type defines the data structure the writer expects, while the converter transforms Claude plugin data into that structure. Types reside in src/types/<provider>.ts and converters in src/converters/claude-to-<provider>.ts. The Codex bundle type is defined at lines 18‑23【/tmp/instagit__i48kegw/src/types/codex.ts#L18-L23】, and the converter function starts at line 10【/tmp/instagit__i48kegw/src/converters/claude-to-codex.ts#L10-L15】.
Step-by-Step Implementation Guide
Follow these steps to integrate a new target provider into the converter architecture.
Step 1: Define the Bundle Type
Create src/types/<provider>.ts to describe the data structure your writer will receive. Follow the pattern established by the Codex implementation:
// src/types/<provider>.ts
export type <Provider>Prompt = { name: string; content: string }
export type <Provider>SkillDir = { name: string; sourceDir: string }
export type <Provider>GeneratedSkill = { name: string; content: string }
export type <Provider>Bundle = {
prompts: <Provider>Prompt[]
skillDirs: <Provider>SkillDir[]
generatedSkills: <Provider>GeneratedSkill[]
mcpServers?: Record<string, ClaudeMcpServer>
}
Replace <Provider> with your provider’s PascalCase name.
Step 2: Implement the Converter
Create src/converters/claude-to-<provider>.ts to transform ClaudePlugin data into your bundle type. The function must accept a ClaudePlugin and return your <Provider>Bundle:
// src/converters/claude-to-<provider>.ts
import type { ClaudePlugin } from "../types/claude"
import type { <Provider>Bundle } from "../types/<provider>"
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
export function convertClaudeTo<Provider>(
plugin: ClaudePlugin,
_opts: ClaudeToOpenCodeOptions,
): <Provider>Bundle {
// Transform plugin data into provider-specific structures
const skillDirs = plugin.skills.map(s => ({ name: s.name, sourceDir: s.sourceDir }))
return {
prompts: [],
skillDirs,
generatedSkills: [],
mcpServers: plugin.mcpServers
}
}
Step 3: Create the Writer Module
Add src/targets/<provider>.ts to handle disk operations. This module writes the bundle to the appropriate directory structure:
// src/targets/<provider>.ts
import path from "path"
import { ensureDir, copyDir, writeText } from "../utils/files"
import type { <Provider>Bundle } from "../types/<provider>"
export async function write<Provider>Bundle(
outputRoot: string,
bundle: <Provider>Bundle
): Promise<void> {
const root = path.join(outputRoot, ".<provider>")
await ensureDir(root)
// Write prompts
for (const prompt of bundle.prompts) {
await writeText(
path.join(root, "prompts", `${prompt.name}.md`),
prompt.content + "\n"
)
}
// Copy skill directories
for (const skill of bundle.skillDirs) {
await copyDir(skill.sourceDir, path.join(root, "skills", skill.name))
}
// Write generated skills
for (const gen of bundle.generatedSkills) {
await writeText(
path.join(root, "skills", gen.name, "SKILL.md"),
gen.content + "\n"
)
}
}
Step 4: Register the Provider
Update src/targets/index.ts to include your provider in the targets map. Import your converter and writer, then add the registry entry:
// src/targets/index.ts
import { convertClaudeTo<Provider> } from "../converters/claude-to-<provider>"
import { write<Provider>Bundle } from "./<provider>"
// Inside the targets object:
<provider>: {
name: "<provider>",
implemented: true,
convert: convertClaudeTo<Provider> as TargetHandler<<Provider>Bundle>["convert"],
write: write<Provider>Bundle as TargetHandler<<Provider>Bundle>["write"],
},
Step 5: Add Tests
Create test files following the existing patterns:
tests/<provider>-converter.test.ts– VerifyconvertClaudeTo<Provider>produces correct bundle shapes using fixtures fromtests/fixtures/sample-plugin.tests/<provider>-writer.test.ts– Verifywrite<Provider>Bundlecreates expected directory structures and files.
Step 6: Document the Provider
Add docs/specs/<provider>.md describing:
- File layout and directory structure
- Configuration format (if applicable)
- MCP server integration details
- Any provider-specific metadata
Minimal Working Example
Here is a complete skeleton for a hypothetical myprovider target:
// src/types/myprovider.ts
import type { ClaudeMcpServer } from "./claude"
export type MyProviderPrompt = { name: string; content: string }
export type MyProviderSkillDir = { name: string; sourceDir: string }
export type MyProviderGeneratedSkill = { name: string; content: string }
export type MyProviderBundle = {
prompts: MyProviderPrompt[]
skillDirs: MyProviderSkillDir[]
generatedSkills: MyProviderGeneratedSkill[]
mcpServers?: Record<string, ClaudeMcpServer>
}
// src/converters/claude-to-myprovider.ts
import type { ClaudePlugin } from "../types/claude"
import type { MyProviderBundle } from "../types/myprovider"
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
export function convertClaudeToMyProvider(
plugin: ClaudePlugin,
_opts: ClaudeToOpenCodeOptions,
): MyProviderBundle {
const skillDirs = plugin.skills.map(s => ({ name: s.name, sourceDir: s.sourceDir }))
return { prompts: [], skillDirs, generatedSkills: [], mcpServers: plugin.mcpServers }
}
// src/targets/myprovider.ts
import path from "path"
import { ensureDir, copyDir, writeText } from "../utils/files"
import type { MyProviderBundle } from "../types/myprovider"
export async function writeMyProviderBundle(outputRoot: string, bundle: MyProviderBundle): Promise<void> {
const root = path.join(outputRoot, ".myprovider")
await ensureDir(root)
for (const skill of bundle.skillDirs) {
await copyDir(skill.sourceDir, path.join(root, "skills", skill.name))
}
}
// src/targets/index.ts (add entry)
import { convertClaudeToMyProvider } from "../converters/claude-to-myprovider"
import { writeMyProviderBundle } from "./myprovider"
myprovider: {
name: "myprovider",
implemented: true,
convert: convertClaudeToMyProvider as TargetHandler<MyProviderBundle>["convert"],
write: writeMyProviderBundle as TargetHandler<MyProviderBundle>["write"],
},
Summary
To add a new target provider to the converter architecture in the EveryInc/compound-engineering-plugin repository, you must complete four core tasks:
- Define the bundle type in
src/types/<provider>.tsto establish the data contract between the converter and writer. - Implement the converter in
src/converters/claude-to-<provider>.tsto transformClaudePlugindata into your bundle format. - Create the writer module in
src/targets/<provider>.tsto persist the bundle to disk using utilities likeensureDir,copyDir, andwriteText. - Register the provider in
src/targets/index.tsby adding an entry to thetargetsmap withimplemented: trueand references to your convert and write functions.
Frequently Asked Questions
What is the target registry in compound-engineering-plugin?
The target registry is the central mapping in src/targets/index.ts that declares all available conversion targets. Each entry specifies the provider name, whether it is implemented, and references to the converter and writer functions. The CLI reads this registry to determine which --to options are available.
How do I test a new target provider before registering it?
Create unit tests in tests/<provider>-converter.test.ts and tests/<provider>-writer.test.ts using the sample plugin fixtures in tests/fixtures/sample-plugin. Verify that your converter produces the correct bundle structure and that your writer creates the expected directory tree and files on disk.
Can I extend an existing provider instead of creating a new one?
While you can import and reuse utility functions from existing providers, the architecture expects each target to have its own distinct bundle type, converter, and writer. If you need to modify behavior for an existing target, you should update that target’s specific files rather than creating a parallel implementation.
What file utilities are available for writing provider bundles?
The repository provides several utilities in src/utils/files.ts including ensureDir for creating directories, copyDir for copying skill directories, writeText for writing text files, and backupFile for creating backups before overwriting existing files. These utilities handle path resolution and ensure consistent file system operations across all target providers.
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 →