| name | add-new-agent-support |
| description | Add a new agent (tool) support to agent-command-sync following the registry pattern |
You are adding a new agent support to agent-command-sync. Follow this guide step by step.
Architecture
All conversions use a hub-and-spoke architecture via SemanticIR. No pairwise converters are needed — implementing a single pair of toIR() / fromIR() automatically enables bidirectional conversion with all existing agents.
Source Format → Parser → toIR() → SemanticIR → fromIR() → Target Format
Agent-specific logic is colocated in a single agent class that implements the AgentDefinition interface and is centrally managed via AGENT_REGISTRY: Record<ProductType, AgentDefinition>. Adding a value to PRODUCT_TYPES will trigger a compile error if the registry is missing a corresponding entry.
Step 1: Type Definitions
src/types/intermediate.ts
Add the new agent name to the PRODUCT_TYPES array. ProductType is automatically derived.
export const PRODUCT_TYPES = ["claude", "gemini", "codex", "opencode"] as const;
export const PRODUCT_TYPES = ["claude", "gemini", "codex", "opencode", "newagent"] as const;
This change alone will trigger a compile error if AGENT_REGISTRY is missing an entry for the new agent.
src/types/command.ts
Add an agent-specific command type.
export interface NewAgentCommand {
frontmatter?: Record<string, unknown>;
content: string;
filePath: string;
}
src/types/skill.ts
Add an agent-specific skill type. The standard approach is to extend SkillBase.
export interface NewAgentSkill extends SkillBase {
frontmatter: {
name?: string;
description?: string;
[key: string]: unknown;
};
}
src/types/index.ts uses wildcard exports (export *), so adding types to command.ts / skill.ts automatically re-exports them. No changes needed.
Step 2: Agent Class
src/agents/newagent.ts (new file)
Create a single agent class that implements all interfaces: AgentConfig, BodyParser, CommandParser, CommandConverter, SkillParser, and SkillConverter. Each agent has one file (e.g., claude.ts, gemini.ts, codex.ts, opencode.ts).
import type { BodySegment } from "../types/body-segment.js";
import type { NewAgentCommand, NewAgentSkill } from "../types/index.js";
import type { ConverterOptions, SemanticIR } from "../types/semantic-ir.js";
import { parseBody, serializeBody } from "../utils/body-segment-utils.js";
import type {
AgentDefinition,
BodyParser,
CommandConverter,
CommandParser,
SkillConverter,
SkillParser,
} from "./types.js";
export class NewAgentAgent
implements
AgentDefinition,
BodyParser,
CommandParser<NewAgentCommand>,
CommandConverter<NewAgentCommand>,
SkillParser<NewAgentSkill>,
SkillConverter<NewAgentSkill>
{
readonly displayName = "NewAgent";
readonly dirs = {
commandSubdir: "commands",
skillSubdir: "skills",
projectBase: ".newagent",
userDefault: ".newagent",
};
readonly fileExtension = ".md";
parseBody(body: string): BodySegment[] {
return parseBody(body, );
}
serializeBody(segments: BodySegment[]): string {
return serializeBody(segments, );
}
async parseCommand(filePath: string): Promise<NewAgentCommand> {
}
validateCommand(data: NewAgentCommand): boolean {
}
stringifyCommand(command: NewAgentCommand): string {
}
commandToIR(source: NewAgentCommand, _options?: ConverterOptions): SemanticIR {
}
commandFromIR(ir: SemanticIR, options?: ConverterOptions): NewAgentCommand {
}
async parseSkill(dirPath: string): Promise<NewAgentSkill> {
}
validateSkill(data: NewAgentSkill): boolean {
}
stringifySkill(skill: NewAgentSkill): string {
}
async writeSkillToDirectory(
skill: NewAgentSkill,
sourceDirPath: string,
targetDir: string,
): Promise<void> {
}
skillToIR(source: NewAgentSkill, _options?: ConverterOptions): SemanticIR {
}
skillFromIR(ir: SemanticIR, options?: ConverterOptions): NewAgentSkill {
}
}
export function createNewAgentAgent(): AgentDefinition {
return new NewAgentAgent();
}
Body patterns:
- If the syntax is the same as Claude/Codex/OpenCode: import
CLAUDE_SYNTAX_PATTERNS and CLAUDE_SYNTAX_SERIALIZERS from _claude-syntax-body-patterns.ts
- If the syntax is unique: define custom
PatternDef[] and PlaceholderSerializers in the agent file (see gemini.ts for an example)
CLAUDE_COMMAND_FIELDS: Define the list of fields not supported by the target.
const CLAUDE_COMMAND_FIELDS = ["allowed-tools", "argument-hint"] as const;
const CLAUDE_COMMAND_FIELDS = ["allowed-tools", "argument-hint", "model"] as const;
CLAUDE_SKILL_FIELDS: List of Claude-specific skill fields not supported by the target. These fields are passed through as-is when removeUnsupported=false (default), and removed when removeUnsupported=true. Include disable-model-invocation in this list. Additional considerations:
modelInvocationEnabled: A semantic property. Bidirectionally converted with Claude's disable-model-invocation (inverted) and Codex's allow_implicit_invocation
Skill parser checklist:
- Verify SKILL.md existence with
isSkillDirectory()
- Collect support files with
collectSupportFiles()
- If agent-specific config files exist, parse/write them individually (e.g., Codex's
agents/openai.yaml)
Step 3: Agent Registry Registration
src/agents/registry.ts
Add an entry to the registry.
import { createNewAgentAgent } from "./newagent.js";
export const AGENT_REGISTRY: Record<ProductType, AgentDefinition> = {
claude: createClaudeAgent(),
gemini: createGeminiAgent(),
codex: createCodexAgent(),
opencode: createOpenCodeAgent(),
newagent: createNewAgentAgent(),
};
That's all — no changes to sync.ts / file-utils.ts are needed. The registry lookup automatically integrates into all sync operations.
Step 4: CLI Integration
src/cli/index.ts dynamically generates CLI options (--xxx-dir), description, and customDirs mapping from PRODUCT_TYPES and AGENT_REGISTRY. No changes needed.
Optionally, you can add usage examples for the new agent in the help examples section.
Step 5: Exports
src/agents/index.ts
Add re-export for the new agent.
export * from "./newagent.js";
src/index.ts re-exports src/agents/index.ts via export * from "./agents/index.js", so no additional changes needed there.
Step 6: Tests
Test Fixtures (tests/fixtures/)
- Place sample command files in
tests/fixtures/newagent-commands/
- Place a sample skill in
tests/fixtures/newagent-skills/test-skill/SKILL.md
New Test Files
| File | Test Coverage |
|---|
tests/parsers/newagent-command-parser.test.ts | parseCommand, validateCommand, stringifyCommand |
tests/parsers/newagent-skill-parser.test.ts | parseSkill, validateSkill, stringifySkill, writeSkillToDirectory |
Additions to Existing Tests
| File | Tests to Add |
|---|
tests/utils/body-segment-utils.test.ts | parseBody / serializeBody with new agent's patterns |
tests/converters/command-conversion.test.ts | Other agents ↔ NewAgent conversion |
tests/converters/skill-conversion.test.ts | Other agents ↔ NewAgent skill conversion |
tests/integration/cli.test.ts | End-to-end conversion tests |
tests/utils/file-utils.test.ts and tests/agents/registry.test.ts use AGENT_REGISTRY, so simply adding to the registry will automatically be covered by existing tests.
Step 7: Documentation
CLAUDE.md
Update the following sections:
- Supported formats table (Commands / Skills)
- Placeholder conversion table
- Claude-specific fields section
- CLI option examples
README.md / README_ja.md
Update the following sections:
- Add agent name to the title description
- Features section
- Options table (
--src / --dest description, add --newagent-dir option)
- Default File Locations (Commands / Skills)
- Add new column to Commands Format table
- Add new column to Content Placeholders table
- Skills Format description
- Add new column to Skill Metadata table
- Add link to Official Documents
File Change Checklist
New Files
Source:
Tests:
Modified Files
Source:
Tests:
Documentation:
No Changes Needed (Automatically Handled by Registry Pattern)
src/types/index.ts — Wildcard exports automatically re-export new types
src/index.ts — Re-exports src/agents/index.ts via wildcard
src/cli/index.ts — CLI options dynamically generated from PRODUCT_TYPES / AGENT_REGISTRY
src/cli/sync.ts
src/utils/file-utils.ts
src/cli/options.ts
tests/utils/file-utils.test.ts
tests/agents/registry.test.ts
tests/fixtures/fixtures.test.ts
Verification
pnpm verify && pnpm build