856 lines
28 KiB
JavaScript
856 lines
28 KiB
JavaScript
/**
|
|
* @claude-flow/codex - Migrations
|
|
*
|
|
* Migration utilities for converting Claude Code configurations to Codex format
|
|
* Supports full CLAUDE.md parsing with section extraction, skill conversion,
|
|
* and proper AGENTS.md/config.toml generation.
|
|
*/
|
|
/**
|
|
* Feature mappings between Claude Code and Codex
|
|
*/
|
|
export const FEATURE_MAPPINGS = [
|
|
{
|
|
claudeCode: 'CLAUDE.md',
|
|
codex: 'AGENTS.md',
|
|
status: 'mapped',
|
|
notes: 'Main instruction file - content is portable',
|
|
},
|
|
{
|
|
claudeCode: 'CLAUDE.local.md',
|
|
codex: '.codex/AGENTS.override.md',
|
|
status: 'mapped',
|
|
notes: 'Local overrides - gitignored in both',
|
|
},
|
|
{
|
|
claudeCode: 'settings.json',
|
|
codex: 'config.toml',
|
|
status: 'mapped',
|
|
notes: 'Format conversion required (JSON to TOML)',
|
|
},
|
|
{
|
|
claudeCode: '/skill-name',
|
|
codex: '$skill-name',
|
|
status: 'mapped',
|
|
notes: 'Skill invocation syntax - search and replace',
|
|
},
|
|
{
|
|
claudeCode: 'TodoWrite',
|
|
codex: 'Task tracking',
|
|
status: 'mapped',
|
|
notes: 'Similar functionality with different API',
|
|
},
|
|
{
|
|
claudeCode: 'Task tool agents',
|
|
codex: 'Sub-agent collaboration',
|
|
status: 'partial',
|
|
notes: 'Codex sub-agents via CODEX_HANDOFF_TARGET env var',
|
|
},
|
|
{
|
|
claudeCode: 'MCP servers',
|
|
codex: '[mcp_servers]',
|
|
status: 'mapped',
|
|
notes: 'Configuration format differs but same functionality',
|
|
},
|
|
{
|
|
claudeCode: 'hooks system',
|
|
codex: 'Automations',
|
|
status: 'partial',
|
|
notes: 'Codex automations are scheduled, not event-driven',
|
|
},
|
|
{
|
|
claudeCode: 'EnterPlanMode',
|
|
codex: 'No direct equivalent',
|
|
status: 'unsupported',
|
|
notes: 'Codex uses different planning paradigm',
|
|
},
|
|
{
|
|
claudeCode: 'Permission modes',
|
|
codex: 'approval_policy + sandbox_mode',
|
|
status: 'mapped',
|
|
notes: 'Codex provides more granular control',
|
|
},
|
|
];
|
|
/**
|
|
* Hook keywords recognized in CLAUDE.md
|
|
*/
|
|
const HOOK_KEYWORDS = [
|
|
'pre-task',
|
|
'post-task',
|
|
'pre-edit',
|
|
'post-edit',
|
|
'pre-command',
|
|
'post-command',
|
|
'session-start',
|
|
'session-end',
|
|
'session-restore',
|
|
'route',
|
|
'explain',
|
|
'pretrain',
|
|
'notify',
|
|
];
|
|
/**
|
|
* Patterns that need migration warnings
|
|
*/
|
|
const WARNING_PATTERNS = [
|
|
{ pattern: 'EnterPlanMode', message: 'EnterPlanMode has no direct Codex equivalent - review planning workflow' },
|
|
{ pattern: 'claude -p', message: 'claude -p headless mode - use Codex sub-agent patterns instead' },
|
|
{ pattern: 'TodoWrite', message: 'TodoWrite - Codex uses different task tracking approach' },
|
|
{ pattern: /--dangerously-skip-permissions/g, message: 'Dangerous permission skip detected - use Codex approval_policy instead' },
|
|
{ pattern: /mcp__claude-flow__/g, message: 'MCP tool calls need migration to Codex MCP configuration' },
|
|
{ pattern: /mcp__ruv-swarm__/g, message: 'Swarm MCP calls - ensure ruv-swarm MCP server is configured in config.toml' },
|
|
];
|
|
/**
|
|
* Parse a CLAUDE.md file completely
|
|
*/
|
|
export async function parseClaudeMd(content) {
|
|
const lines = content.split('\n');
|
|
const result = {
|
|
title: '',
|
|
sections: [],
|
|
skills: [],
|
|
hooks: [],
|
|
customInstructions: [],
|
|
codeBlocks: [],
|
|
mcpServers: [],
|
|
settings: {},
|
|
warnings: [],
|
|
};
|
|
// Extract title (first H1)
|
|
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
if (titleMatch && titleMatch[1]) {
|
|
result.title = titleMatch[1].trim();
|
|
result.settings.projectName = result.title;
|
|
}
|
|
// Parse sections
|
|
result.sections = parseSections(content, lines);
|
|
// Extract skills (both /skill-name and $skill-name syntax)
|
|
result.skills = extractSkills(content, lines);
|
|
// Extract hooks
|
|
result.hooks = extractHooks(content);
|
|
// Extract code blocks
|
|
result.codeBlocks = extractCodeBlocks(content, lines);
|
|
// Extract MCP server configurations from code blocks
|
|
result.mcpServers = extractMcpServers(result.codeBlocks);
|
|
// Extract settings from content
|
|
result.settings = {
|
|
...result.settings,
|
|
...extractSettings(content, result.sections),
|
|
};
|
|
// Extract custom instructions (behavioral rules)
|
|
result.customInstructions = extractBehavioralRules(content);
|
|
// Check for patterns that need warnings
|
|
for (const { pattern, message } of WARNING_PATTERNS) {
|
|
if (typeof pattern === 'string') {
|
|
if (content.includes(pattern)) {
|
|
result.warnings.push(message);
|
|
}
|
|
}
|
|
else {
|
|
if (pattern.test(content)) {
|
|
result.warnings.push(message);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Parse sections from markdown content
|
|
*/
|
|
function parseSections(content, lines) {
|
|
const sections = [];
|
|
const sectionRegex = /^(#{1,6})\s+(.+)$/;
|
|
let currentSection = null;
|
|
let contentLines = [];
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
const match = sectionRegex.exec(line);
|
|
if (match && match[1] && match[2]) {
|
|
// Save previous section
|
|
if (currentSection) {
|
|
currentSection.content = contentLines.join('\n').trim();
|
|
currentSection.endLine = i;
|
|
sections.push(currentSection);
|
|
}
|
|
// Start new section
|
|
currentSection = {
|
|
level: match[1].length,
|
|
title: match[2].trim(),
|
|
content: '',
|
|
startLine: i + 1,
|
|
endLine: i + 1,
|
|
};
|
|
contentLines = [];
|
|
}
|
|
else if (currentSection) {
|
|
contentLines.push(line);
|
|
}
|
|
}
|
|
// Save last section
|
|
if (currentSection) {
|
|
currentSection.content = contentLines.join('\n').trim();
|
|
currentSection.endLine = lines.length;
|
|
sections.push(currentSection);
|
|
}
|
|
return sections;
|
|
}
|
|
/**
|
|
* Extract skill references from content
|
|
*/
|
|
function extractSkills(content, lines) {
|
|
const skills = [];
|
|
const seenSkills = new Set();
|
|
// Slash syntax: /skill-name
|
|
const slashRegex = /\/([a-z][a-z0-9-]*)/g;
|
|
let match;
|
|
while ((match = slashRegex.exec(content)) !== null) {
|
|
const name = match[1];
|
|
// Skip common false positives
|
|
if (['src', 'dist', 'docs', 'tests', 'config', 'scripts', 'examples', 'node_modules', 'workspaces'].includes(name)) {
|
|
continue;
|
|
}
|
|
if (!seenSkills.has(`slash:${name}`)) {
|
|
seenSkills.add(`slash:${name}`);
|
|
const lineNum = findLineNumber(content, match.index);
|
|
skills.push({
|
|
name,
|
|
syntax: 'slash',
|
|
context: getContextAroundMatch(lines, lineNum),
|
|
line: lineNum,
|
|
});
|
|
}
|
|
}
|
|
// Dollar syntax: $skill-name
|
|
const dollarRegex = /\$([a-z][a-z0-9-]+)/g;
|
|
while ((match = dollarRegex.exec(content)) !== null) {
|
|
const name = match[1];
|
|
if (!seenSkills.has(`dollar:${name}`)) {
|
|
seenSkills.add(`dollar:${name}`);
|
|
const lineNum = findLineNumber(content, match.index);
|
|
skills.push({
|
|
name,
|
|
syntax: 'dollar',
|
|
context: getContextAroundMatch(lines, lineNum),
|
|
line: lineNum,
|
|
});
|
|
}
|
|
}
|
|
return skills;
|
|
}
|
|
/**
|
|
* Extract hooks referenced in content
|
|
*/
|
|
function extractHooks(content) {
|
|
const hooks = [];
|
|
const lowerContent = content.toLowerCase();
|
|
for (const hook of HOOK_KEYWORDS) {
|
|
if (lowerContent.includes(hook)) {
|
|
hooks.push(hook);
|
|
}
|
|
}
|
|
return hooks;
|
|
}
|
|
/**
|
|
* Extract code blocks from markdown
|
|
*/
|
|
function extractCodeBlocks(content, lines) {
|
|
const blocks = [];
|
|
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
|
|
let match;
|
|
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
blocks.push({
|
|
language: match[1] || 'text',
|
|
content: match[2].trim(),
|
|
line: findLineNumber(content, match.index),
|
|
});
|
|
}
|
|
return blocks;
|
|
}
|
|
/**
|
|
* Extract MCP server configurations from code blocks
|
|
*/
|
|
function extractMcpServers(codeBlocks) {
|
|
const servers = [];
|
|
for (const block of codeBlocks) {
|
|
// Look for MCP server configurations in bash/shell blocks
|
|
if (['bash', 'shell', 'sh', 'zsh'].includes(block.language)) {
|
|
// Pattern: claude mcp add <name> <command> [args...]
|
|
const mcpAddRegex = /claude\s+mcp\s+add\s+(\S+)\s+(.+)/g;
|
|
let match;
|
|
while ((match = mcpAddRegex.exec(block.content)) !== null) {
|
|
const name = match[1];
|
|
const parts = match[2].trim().split(/\s+/);
|
|
servers.push({
|
|
name,
|
|
command: parts[0] || 'npx',
|
|
args: parts.slice(1),
|
|
enabled: true,
|
|
});
|
|
}
|
|
}
|
|
// Look for JSON MCP configurations
|
|
if (['json', 'jsonc'].includes(block.language)) {
|
|
try {
|
|
const parsed = JSON.parse(block.content);
|
|
if (parsed.mcpServers) {
|
|
for (const [name, config] of Object.entries(parsed.mcpServers)) {
|
|
const mcpConfig = config;
|
|
servers.push({
|
|
name,
|
|
command: mcpConfig.command || 'npx',
|
|
args: mcpConfig.args || [],
|
|
enabled: true,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// Not valid JSON, skip
|
|
}
|
|
}
|
|
// Look for JavaScript/TypeScript MCP tool calls
|
|
if (['javascript', 'typescript', 'js', 'ts'].includes(block.language)) {
|
|
// Pattern: mcp__<server>__<tool>
|
|
const mcpCallRegex = /mcp__([a-z-]+)__/g;
|
|
const seenServers = new Set();
|
|
let match;
|
|
while ((match = mcpCallRegex.exec(block.content)) !== null) {
|
|
const serverName = match[1].replace(/-/g, '_');
|
|
if (!seenServers.has(serverName)) {
|
|
seenServers.add(serverName);
|
|
// Don't add as full config, just note it exists
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Deduplicate
|
|
const seen = new Set();
|
|
return servers.filter((s) => {
|
|
if (seen.has(s.name))
|
|
return false;
|
|
seen.add(s.name);
|
|
return true;
|
|
});
|
|
}
|
|
/**
|
|
* Extract settings from content and sections
|
|
*/
|
|
function extractSettings(content, sections) {
|
|
const settings = {};
|
|
// Look for tech stack
|
|
const techMatch = content.match(/(?:Tech\s*Stack|Technology|Stack)[:\s]+([^\n]+)/i);
|
|
if (techMatch && techMatch[1]) {
|
|
settings.techStack = techMatch[1].replace(/\*\*/g, '').trim();
|
|
}
|
|
// Look for build command
|
|
const buildMatch = content.match(/(?:Build|Compile)[:\s]*\n?```(?:bash|sh)?\n([^\n]+)/i);
|
|
if (buildMatch && buildMatch[1]) {
|
|
settings.buildCommand = buildMatch[1].trim();
|
|
}
|
|
else {
|
|
// Check for npm run build pattern
|
|
if (content.includes('npm run build')) {
|
|
settings.buildCommand = 'npm run build';
|
|
}
|
|
}
|
|
// Look for test command
|
|
const testMatch = content.match(/(?:Test)[:\s]*\n?```(?:bash|sh)?\n([^\n]+)/i);
|
|
if (testMatch && testMatch[1]) {
|
|
settings.testCommand = testMatch[1].trim();
|
|
}
|
|
else {
|
|
if (content.includes('npm test')) {
|
|
settings.testCommand = 'npm test';
|
|
}
|
|
}
|
|
// Look for dev command
|
|
if (content.includes('npm run dev')) {
|
|
settings.devCommand = 'npm run dev';
|
|
}
|
|
// Look for approval/permission settings
|
|
if (content.includes('auto-approve') || content.includes('autoApprove')) {
|
|
settings.approvalPolicy = 'never';
|
|
}
|
|
else if (content.includes('read-only')) {
|
|
settings.sandboxMode = 'read-only';
|
|
}
|
|
// Look for model specification
|
|
const modelMatch = content.match(/model[:\s]+["']?([^"'\n]+)["']?/i);
|
|
if (modelMatch && modelMatch[1]) {
|
|
settings.model = modelMatch[1].trim();
|
|
}
|
|
return settings;
|
|
}
|
|
/**
|
|
* Extract behavioral rules from content
|
|
*/
|
|
function extractBehavioralRules(content) {
|
|
const rules = [];
|
|
// Look for Behavioral Rules section
|
|
const behavioralMatch = content.match(/##\s*Behavioral\s*Rules[^\n]*\n([\s\S]*?)(?=\n##|\n#\s|$)/i);
|
|
if (behavioralMatch && behavioralMatch[1]) {
|
|
const ruleLines = behavioralMatch[1].split('\n');
|
|
for (const line of ruleLines) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith('- ')) {
|
|
rules.push(trimmed.substring(2));
|
|
}
|
|
else if (trimmed.startsWith('* ')) {
|
|
rules.push(trimmed.substring(2));
|
|
}
|
|
}
|
|
}
|
|
// Also look for Security Rules
|
|
const securityMatch = content.match(/##\s*Security\s*Rules?[^\n]*\n([\s\S]*?)(?=\n##|\n#\s|$)/i);
|
|
if (securityMatch && securityMatch[1]) {
|
|
const securityLines = securityMatch[1].split('\n');
|
|
for (const line of securityLines) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith('- ')) {
|
|
rules.push(trimmed.substring(2));
|
|
}
|
|
else if (trimmed.startsWith('* ')) {
|
|
rules.push(trimmed.substring(2));
|
|
}
|
|
}
|
|
}
|
|
return rules;
|
|
}
|
|
/**
|
|
* Find line number for a character index
|
|
*/
|
|
function findLineNumber(content, index) {
|
|
return content.substring(0, index).split('\n').length;
|
|
}
|
|
/**
|
|
* Get context around a match
|
|
*/
|
|
function getContextAroundMatch(lines, lineNum) {
|
|
const start = Math.max(0, lineNum - 2);
|
|
const end = Math.min(lines.length, lineNum + 1);
|
|
return lines.slice(start, end).join('\n');
|
|
}
|
|
/**
|
|
* Analyze a CLAUDE.md file for migration (simplified interface)
|
|
*/
|
|
export async function analyzeClaudeMd(content) {
|
|
const parsed = await parseClaudeMd(content);
|
|
return {
|
|
sections: parsed.sections.map((s) => s.title),
|
|
skills: [...new Set(parsed.skills.map((s) => s.name))],
|
|
hooks: parsed.hooks,
|
|
customInstructions: parsed.customInstructions,
|
|
warnings: parsed.warnings,
|
|
};
|
|
}
|
|
/**
|
|
* Convert skill invocation syntax from slash to dollar
|
|
*/
|
|
export function convertSkillSyntax(content) {
|
|
// Convert /skill-name to $skill-name, but avoid path-like patterns
|
|
return content.replace(/(?<![a-zA-Z0-9_./])\/([a-z][a-z0-9-]*)(?![a-zA-Z0-9_./])/g, (match, skillName) => {
|
|
// Skip common directory names
|
|
const skipPatterns = ['src', 'dist', 'docs', 'tests', 'config', 'scripts', 'examples', 'node_modules', 'workspaces', 'data', 'logs', 'tmp', 'var', 'etc', 'usr', 'bin', 'lib'];
|
|
if (skipPatterns.includes(skillName)) {
|
|
return match;
|
|
}
|
|
return `$${skillName}`;
|
|
});
|
|
}
|
|
/**
|
|
* Generate AGENTS.md content from parsed CLAUDE.md
|
|
*/
|
|
export function generateAgentsMdFromParsed(parsed) {
|
|
const lines = [];
|
|
// Title
|
|
lines.push(`# ${parsed.title || 'Project Agent Guide'}`);
|
|
lines.push('');
|
|
lines.push('> Migrated from CLAUDE.md by @claude-flow/codex');
|
|
lines.push('');
|
|
// Project Overview
|
|
lines.push('## Project Overview');
|
|
lines.push('');
|
|
if (parsed.settings.techStack) {
|
|
lines.push(`**Tech Stack**: ${parsed.settings.techStack}`);
|
|
}
|
|
lines.push('');
|
|
// Quick Start
|
|
lines.push('## Setup');
|
|
lines.push('');
|
|
if (parsed.settings.buildCommand) {
|
|
lines.push('### Build');
|
|
lines.push('```bash');
|
|
lines.push(parsed.settings.buildCommand);
|
|
lines.push('```');
|
|
lines.push('');
|
|
}
|
|
if (parsed.settings.testCommand) {
|
|
lines.push('### Test');
|
|
lines.push('```bash');
|
|
lines.push(parsed.settings.testCommand);
|
|
lines.push('```');
|
|
lines.push('');
|
|
}
|
|
if (parsed.settings.devCommand) {
|
|
lines.push('### Development');
|
|
lines.push('```bash');
|
|
lines.push(parsed.settings.devCommand);
|
|
lines.push('```');
|
|
lines.push('');
|
|
}
|
|
// Code Standards
|
|
lines.push('## Code Standards');
|
|
lines.push('');
|
|
lines.push('- Files under 500 lines');
|
|
lines.push('- No hardcoded secrets');
|
|
lines.push('- Input validation at boundaries');
|
|
lines.push('- Typed interfaces for public APIs');
|
|
lines.push('');
|
|
// Skills
|
|
if (parsed.skills.length > 0) {
|
|
lines.push('## Skills');
|
|
lines.push('');
|
|
lines.push('| Skill | Original Syntax |');
|
|
lines.push('|-------|-----------------|');
|
|
for (const skill of parsed.skills) {
|
|
const codexSyntax = `$${skill.name}`;
|
|
const originalSyntax = skill.syntax === 'slash' ? `/${skill.name}` : `$${skill.name}`;
|
|
lines.push(`| \`${codexSyntax}\` | \`${originalSyntax}\` |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
// Security
|
|
lines.push('## Security');
|
|
lines.push('');
|
|
if (parsed.customInstructions.length > 0) {
|
|
for (const rule of parsed.customInstructions.slice(0, 10)) {
|
|
lines.push(`- ${rule}`);
|
|
}
|
|
}
|
|
else {
|
|
lines.push('- NEVER commit secrets or credentials');
|
|
lines.push('- Validate all user inputs');
|
|
lines.push('- Prevent directory traversal attacks');
|
|
}
|
|
lines.push('');
|
|
// Hooks (as reference)
|
|
if (parsed.hooks.length > 0) {
|
|
lines.push('## Automation Hooks');
|
|
lines.push('');
|
|
lines.push('The following hooks were detected in the original configuration:');
|
|
lines.push('');
|
|
for (const hook of parsed.hooks) {
|
|
lines.push(`- \`${hook}\``);
|
|
}
|
|
lines.push('');
|
|
lines.push('> Note: Codex uses scheduled Automations instead of event-driven hooks.');
|
|
lines.push('');
|
|
}
|
|
// MCP Servers
|
|
if (parsed.mcpServers.length > 0) {
|
|
lines.push('## MCP Servers');
|
|
lines.push('');
|
|
lines.push('Configure in config.toml:');
|
|
lines.push('');
|
|
for (const server of parsed.mcpServers) {
|
|
lines.push(`- **${server.name}**: \`${server.command} ${(server.args || []).join(' ')}\``);
|
|
}
|
|
lines.push('');
|
|
}
|
|
// Migration notes
|
|
if (parsed.warnings.length > 0) {
|
|
lines.push('## Migration Notes');
|
|
lines.push('');
|
|
for (const warning of parsed.warnings) {
|
|
lines.push(`- ${warning}`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
/**
|
|
* Convert settings.json to config.toml format
|
|
*/
|
|
export function convertSettingsToToml(settings) {
|
|
const lines = [];
|
|
lines.push('# Migrated from settings.json');
|
|
lines.push('# Generated by @claude-flow/codex');
|
|
lines.push('');
|
|
// Model
|
|
if (settings.model) {
|
|
lines.push(`model = "${settings.model}"`);
|
|
}
|
|
else {
|
|
lines.push('model = "gpt-5.3-codex"');
|
|
}
|
|
lines.push('');
|
|
// Permissions mapping
|
|
if (settings.permissions) {
|
|
const perms = settings.permissions;
|
|
if (perms.autoApprove === true) {
|
|
lines.push('approval_policy = "never"');
|
|
lines.push('sandbox_mode = "danger-full-access"');
|
|
}
|
|
else if (perms.autoApprove === 'read-only') {
|
|
lines.push('approval_policy = "on-request"');
|
|
lines.push('sandbox_mode = "read-only"');
|
|
}
|
|
else {
|
|
lines.push('approval_policy = "on-request"');
|
|
lines.push('sandbox_mode = "workspace-write"');
|
|
}
|
|
}
|
|
else {
|
|
lines.push('approval_policy = "on-request"');
|
|
lines.push('sandbox_mode = "workspace-write"');
|
|
}
|
|
lines.push('');
|
|
// Web search
|
|
if (settings.webSearch !== undefined) {
|
|
const mode = settings.webSearch === true ? 'live' : settings.webSearch === false ? 'disabled' : 'cached';
|
|
lines.push(`web_search = "${mode}"`);
|
|
}
|
|
else {
|
|
lines.push('web_search = "cached"');
|
|
}
|
|
lines.push('');
|
|
// Features
|
|
lines.push('[features]');
|
|
lines.push('child_agents_md = true');
|
|
lines.push('shell_snapshot = true');
|
|
lines.push('request_rule = true');
|
|
lines.push('');
|
|
// MCP servers
|
|
if (settings.mcpServers && typeof settings.mcpServers === 'object') {
|
|
for (const [name, config] of Object.entries(settings.mcpServers)) {
|
|
const mcpConfig = config;
|
|
lines.push(`[mcp_servers.${name}]`);
|
|
if (mcpConfig.command) {
|
|
lines.push(`command = "${mcpConfig.command}"`);
|
|
}
|
|
if (mcpConfig.args && mcpConfig.args.length > 0) {
|
|
const argsStr = mcpConfig.args.map((a) => `"${a}"`).join(', ');
|
|
lines.push(`args = [${argsStr}]`);
|
|
}
|
|
lines.push('enabled = true');
|
|
if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
|
|
lines.push('');
|
|
lines.push(`[mcp_servers.${name}.env]`);
|
|
for (const [key, value] of Object.entries(mcpConfig.env)) {
|
|
lines.push(`${key} = "${value}"`);
|
|
}
|
|
}
|
|
lines.push('');
|
|
}
|
|
}
|
|
else {
|
|
// Add default claude-flow server
|
|
lines.push('[mcp_servers.claude-flow]');
|
|
lines.push('command = "npx"');
|
|
lines.push('args = ["-y", "@claude-flow/cli@latest"]');
|
|
lines.push('enabled = true');
|
|
lines.push('');
|
|
}
|
|
// History
|
|
lines.push('[history]');
|
|
lines.push('persistence = "save-all"');
|
|
lines.push('');
|
|
// Profiles
|
|
lines.push('# Development profile');
|
|
lines.push('[profiles.dev]');
|
|
lines.push('approval_policy = "never"');
|
|
lines.push('sandbox_mode = "danger-full-access"');
|
|
lines.push('');
|
|
lines.push('# Safe profile');
|
|
lines.push('[profiles.safe]');
|
|
lines.push('approval_policy = "untrusted"');
|
|
lines.push('sandbox_mode = "read-only"');
|
|
lines.push('');
|
|
return lines.join('\n');
|
|
}
|
|
/**
|
|
* Generate config.toml from parsed CLAUDE.md
|
|
*/
|
|
export function generateConfigTomlFromParsed(parsed) {
|
|
const lines = [];
|
|
lines.push('# Migrated from CLAUDE.md');
|
|
lines.push('# Generated by @claude-flow/codex');
|
|
lines.push('');
|
|
// Model
|
|
lines.push(`model = "${parsed.settings.model || 'gpt-5.3-codex'}"`);
|
|
lines.push('');
|
|
// Approval policy
|
|
const approvalPolicy = parsed.settings.approvalPolicy || 'on-request';
|
|
lines.push(`approval_policy = "${approvalPolicy}"`);
|
|
lines.push('');
|
|
// Sandbox mode
|
|
const sandboxMode = parsed.settings.sandboxMode || 'workspace-write';
|
|
lines.push(`sandbox_mode = "${sandboxMode}"`);
|
|
lines.push('');
|
|
// Web search
|
|
lines.push('web_search = "cached"');
|
|
lines.push('');
|
|
// Features
|
|
lines.push('[features]');
|
|
lines.push('child_agents_md = true');
|
|
lines.push('shell_snapshot = true');
|
|
lines.push('request_rule = true');
|
|
lines.push('');
|
|
// MCP servers
|
|
if (parsed.mcpServers.length > 0) {
|
|
for (const server of parsed.mcpServers) {
|
|
lines.push(`[mcp_servers.${server.name.replace(/-/g, '_')}]`);
|
|
lines.push(`command = "${server.command}"`);
|
|
if (server.args && server.args.length > 0) {
|
|
const argsStr = server.args.map((a) => `"${a}"`).join(', ');
|
|
lines.push(`args = [${argsStr}]`);
|
|
}
|
|
lines.push(`enabled = ${server.enabled ?? true}`);
|
|
lines.push('');
|
|
}
|
|
}
|
|
else {
|
|
// Default claude-flow server
|
|
lines.push('[mcp_servers.claude_flow]');
|
|
lines.push('command = "npx"');
|
|
lines.push('args = ["-y", "@claude-flow/cli@latest"]');
|
|
lines.push('enabled = true');
|
|
lines.push('');
|
|
}
|
|
// Skills
|
|
if (parsed.skills.length > 0) {
|
|
lines.push('# Skills detected in original configuration');
|
|
for (const skill of parsed.skills) {
|
|
lines.push(`# - ${skill.name} (${skill.syntax} syntax)`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
// History
|
|
lines.push('[history]');
|
|
lines.push('persistence = "save-all"');
|
|
lines.push('');
|
|
// Profiles
|
|
lines.push('[profiles.dev]');
|
|
lines.push('approval_policy = "never"');
|
|
lines.push('sandbox_mode = "danger-full-access"');
|
|
lines.push('');
|
|
lines.push('[profiles.safe]');
|
|
lines.push('approval_policy = "untrusted"');
|
|
lines.push('sandbox_mode = "read-only"');
|
|
lines.push('');
|
|
return lines.join('\n');
|
|
}
|
|
/**
|
|
* Migrate from Claude Code (CLAUDE.md) to Codex (AGENTS.md)
|
|
*/
|
|
export async function migrateFromClaudeCode(options) {
|
|
const { sourcePath, targetPath, preserveComments = true, generateSkills = true } = options;
|
|
try {
|
|
// In actual implementation, this would read the file
|
|
// For now, we provide the structure for the migration
|
|
const result = {
|
|
success: true,
|
|
agentsMdPath: `${targetPath}/AGENTS.md`,
|
|
skillsCreated: generateSkills
|
|
? ['swarm-orchestration', 'memory-management', 'security-audit']
|
|
: [],
|
|
configTomlPath: `${targetPath}/.agents/config.toml`,
|
|
mappings: FEATURE_MAPPINGS,
|
|
warnings: [
|
|
'Review skill invocation syntax (changed from / to $)',
|
|
'Check hook configurations for Automation compatibility',
|
|
'Verify MCP server configurations in config.toml',
|
|
],
|
|
};
|
|
return result;
|
|
}
|
|
catch (error) {
|
|
return {
|
|
success: false,
|
|
warnings: [`Migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`],
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Perform full migration with content
|
|
*/
|
|
export async function performFullMigration(claudeMdContent, settingsJson) {
|
|
// Parse CLAUDE.md
|
|
const parsed = await parseClaudeMd(claudeMdContent);
|
|
// Generate AGENTS.md
|
|
let agentsMd = generateAgentsMdFromParsed(parsed);
|
|
// Convert skill syntax in the generated content
|
|
agentsMd = convertSkillSyntax(agentsMd);
|
|
// Generate config.toml
|
|
let configToml;
|
|
if (settingsJson) {
|
|
configToml = convertSettingsToToml(settingsJson);
|
|
}
|
|
else {
|
|
configToml = generateConfigTomlFromParsed(parsed);
|
|
}
|
|
// Collect skills to create
|
|
const skillsToCreate = [...new Set(parsed.skills.map((s) => s.name))];
|
|
return {
|
|
agentsMd,
|
|
configToml,
|
|
warnings: parsed.warnings,
|
|
skillsToCreate,
|
|
};
|
|
}
|
|
/**
|
|
* Generate migration report
|
|
*/
|
|
export function generateMigrationReport(result) {
|
|
const lines = [];
|
|
lines.push('# Migration Report');
|
|
lines.push('');
|
|
lines.push(`**Status**: ${result.success ? 'Success' : 'Failed'}`);
|
|
lines.push(`**Generated**: ${new Date().toISOString()}`);
|
|
lines.push('');
|
|
if (result.agentsMdPath) {
|
|
lines.push('## Generated Files');
|
|
lines.push('');
|
|
lines.push(`- AGENTS.md: \`${result.agentsMdPath}\``);
|
|
if (result.configTomlPath) {
|
|
lines.push(`- config.toml: \`${result.configTomlPath}\``);
|
|
}
|
|
lines.push('');
|
|
}
|
|
if (result.skillsCreated && result.skillsCreated.length > 0) {
|
|
lines.push('## Skills Created');
|
|
lines.push('');
|
|
for (const skill of result.skillsCreated) {
|
|
lines.push(`- \`$${skill}\``);
|
|
}
|
|
lines.push('');
|
|
}
|
|
if (result.mappings) {
|
|
lines.push('## Feature Mappings');
|
|
lines.push('');
|
|
lines.push('| Claude Code | Codex | Status | Notes |');
|
|
lines.push('|-------------|-------|--------|-------|');
|
|
for (const mapping of result.mappings) {
|
|
const notes = mapping.notes || '';
|
|
lines.push(`| \`${mapping.claudeCode}\` | \`${mapping.codex}\` | ${mapping.status} | ${notes} |`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
if (result.warnings && result.warnings.length > 0) {
|
|
lines.push('## Warnings');
|
|
lines.push('');
|
|
for (const warning of result.warnings) {
|
|
lines.push(`- ${warning}`);
|
|
}
|
|
lines.push('');
|
|
}
|
|
lines.push('## Next Steps');
|
|
lines.push('');
|
|
lines.push('1. Review generated AGENTS.md for accuracy');
|
|
lines.push('2. Update skill references from `/skill-name` to `$skill-name`');
|
|
lines.push('3. Configure MCP servers in config.toml');
|
|
lines.push('4. Create skill definitions in `.agents/skills/`');
|
|
lines.push('5. Test with `codex` CLI');
|
|
lines.push('');
|
|
return lines.join('\n');
|
|
}
|
|
//# sourceMappingURL=index.js.map
|