tasq/node_modules/@claude-flow/codex/dist/cli.js

652 lines
27 KiB
JavaScript

#!/usr/bin/env node
/**
* @claude-flow/codex - CLI
*
* Command-line interface for Codex integration
* Part of the coflow rebranding initiative
*/
import { Command } from 'commander';
import chalk from 'chalk';
import { CodexInitializer } from './initializer.js';
import { validateAgentsMd, validateSkillMd, validateConfigToml } from './validators/index.js';
import { migrateFromClaudeCode, analyzeClaudeMd, generateMigrationReport } from './migrations/index.js';
import { listTemplates, BUILT_IN_SKILLS } from './templates/index.js';
import { generateSkillMd } from './generators/skill-md.js';
import { VERSION, PACKAGE_INFO } from './index.js';
import fs from 'fs-extra';
import path from 'path';
const program = new Command();
// Custom error handler for better output
function handleError(error, message) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(chalk.red.bold('\nError:'), chalk.red(message ?? errorMessage));
if (error instanceof Error && error.stack && process.env.DEBUG) {
console.error(chalk.gray('\nStack trace:'));
console.error(chalk.gray(error.stack));
}
process.exit(1);
}
// Validate project path exists and is accessible
async function validatePath(projectPath) {
const resolvedPath = path.resolve(projectPath);
try {
const stats = await fs.stat(resolvedPath);
if (!stats.isDirectory()) {
throw new Error(`Path is not a directory: ${resolvedPath}`);
}
return resolvedPath;
}
catch (error) {
if (error.code === 'ENOENT') {
// Directory doesn't exist, try to create it
console.log(chalk.yellow(`Creating directory: ${resolvedPath}`));
await fs.ensureDir(resolvedPath);
return resolvedPath;
}
throw error;
}
}
// Validate skill name format
function validateSkillName(name) {
const validPattern = /^[a-z][a-z0-9-]*$/;
return validPattern.test(name);
}
// Print banner
function printBanner() {
console.log(chalk.cyan.bold('\n Claude Flow Codex'));
console.log(chalk.gray(' OpenAI Codex integration for Claude Flow'));
console.log(chalk.gray(' ----------------------------------------\n'));
}
program
.name('claude-flow-codex')
.description('OpenAI Codex integration for Claude Flow - Part of the coflow ecosystem')
.version(VERSION, '-v, --version', 'Display version number')
.option('--debug', 'Enable debug mode', false)
.hook('preAction', (thisCommand) => {
if (thisCommand.opts().debug) {
process.env.DEBUG = 'true';
}
});
// Init command
program
.command('init')
.description('Initialize a new Codex project with AGENTS.md and skills')
.option('-t, --template <template>', 'Template to use (minimal, default, full, enterprise)', 'default')
.option('-s, --skills <skills>', 'Comma-separated list of skills to include')
.option('-f, --force', 'Overwrite existing files', false)
.option('--dual', 'Generate both Codex and Claude Code configurations', false)
.option('-p, --path <path>', 'Project path', process.cwd())
.option('-q, --quiet', 'Suppress verbose output', false)
.action(async (options) => {
try {
if (!options.quiet) {
printBanner();
}
// Validate template
const validTemplates = ['minimal', 'default', 'full', 'enterprise'];
if (!validTemplates.includes(options.template)) {
console.error(chalk.red(`Invalid template: ${options.template}`));
console.log(chalk.gray(`Valid templates: ${validTemplates.join(', ')}`));
process.exit(1);
}
const projectPath = await validatePath(options.path);
console.log(chalk.blue('Initializing Codex project...'));
console.log(chalk.gray(` Path: ${projectPath}`));
console.log(chalk.gray(` Template: ${options.template}`));
if (options.skills) {
console.log(chalk.gray(` Skills: ${options.skills}`));
}
if (options.force) {
console.log(chalk.yellow(' Force: enabled (will overwrite existing files)'));
}
if (options.dual) {
console.log(chalk.gray(' Mode: dual (Codex + Claude Code)'));
}
const initializer = new CodexInitializer();
const skills = options.skills?.split(',').map((s) => s.trim()).filter(Boolean);
// Validate skill names if provided
if (skills) {
for (const skill of skills) {
if (!validateSkillName(skill)) {
console.error(chalk.red(`Invalid skill name: ${skill}`));
console.log(chalk.gray('Skill names must be kebab-case (lowercase letters, numbers, hyphens)'));
process.exit(1);
}
}
}
const result = await initializer.initialize({
projectPath,
template: options.template,
skills,
force: options.force,
dual: options.dual,
});
if (result.success) {
console.log(chalk.green.bold('\n Project initialized successfully!'));
if (result.filesCreated.length > 0) {
console.log(chalk.white('\n Files created:'));
for (const file of result.filesCreated) {
console.log(chalk.gray(` ${chalk.green('+')} ${file}`));
}
}
if (result.skillsGenerated.length > 0) {
console.log(chalk.white('\n Skills generated:'));
for (const skill of result.skillsGenerated) {
console.log(chalk.gray(` ${chalk.cyan('$')}${skill}`));
}
}
if (result.warnings && result.warnings.length > 0) {
console.log(chalk.yellow('\n Warnings:'));
for (const warning of result.warnings) {
console.log(chalk.yellow(` ! ${warning}`));
}
}
console.log(chalk.blue.bold('\n Next steps:'));
console.log(chalk.gray(' 1. Review AGENTS.md and customize for your project'));
console.log(chalk.gray(' 2. Review .agents/config.toml settings'));
console.log(chalk.gray(' 3. Start using skills with $skill-name syntax'));
console.log();
}
else {
console.log(chalk.red.bold('\n Initialization failed'));
if (result.errors) {
for (const error of result.errors) {
console.log(chalk.red(` - ${error}`));
}
}
process.exit(1);
}
}
catch (error) {
handleError(error, 'Failed to initialize project');
}
});
// Generate skill command
program
.command('generate-skill')
.alias('gs')
.description('Generate a new SKILL.md file')
.requiredOption('-n, --name <name>', 'Skill name (kebab-case)')
.option('-d, --description <description>', 'Skill description')
.option('-t, --triggers <triggers>', 'Comma-separated trigger conditions')
.option('-s, --skip <skip>', 'Comma-separated skip conditions')
.option('-p, --path <path>', 'Output path', process.cwd())
.option('--dry-run', 'Show what would be generated without writing', false)
.action(async (options) => {
try {
printBanner();
// Validate skill name
if (!validateSkillName(options.name)) {
console.error(chalk.red(`Invalid skill name: ${options.name}`));
console.log(chalk.gray('Skill names must be kebab-case (lowercase letters, numbers, hyphens)'));
console.log(chalk.gray('Examples: my-skill, code-analyzer, data-processor'));
process.exit(1);
}
const projectPath = await validatePath(options.path);
console.log(chalk.blue(`Generating skill: ${chalk.white(options.name)}`));
const triggers = options.triggers?.split(',').map((s) => s.trim()).filter(Boolean);
const skipWhen = options.skip?.split(',').map((s) => s.trim()).filter(Boolean);
const skillMd = await generateSkillMd({
name: options.name,
description: options.description ?? `Custom skill: ${options.name}`,
triggers: triggers ?? ['Define when to trigger this skill'],
skipWhen: skipWhen ?? ['Define when to skip this skill'],
});
if (options.dryRun) {
console.log(chalk.yellow('\nDry run - would generate:'));
console.log(chalk.gray('---'));
console.log(skillMd);
console.log(chalk.gray('---'));
return;
}
const skillDir = path.join(projectPath, '.agents', 'skills', options.name);
await fs.ensureDir(skillDir);
const skillPath = path.join(skillDir, 'SKILL.md');
// Check if skill already exists
if (await fs.pathExists(skillPath)) {
console.log(chalk.yellow(`Skill already exists: ${skillPath}`));
console.log(chalk.gray('Use --force to overwrite (not yet implemented)'));
process.exit(1);
}
await fs.writeFile(skillPath, skillMd);
console.log(chalk.green.bold(`\n Skill created successfully!`));
console.log(chalk.gray(` Path: ${skillPath}`));
console.log(chalk.gray(` Use: ${chalk.cyan('$' + options.name)}`));
console.log();
}
catch (error) {
handleError(error, 'Failed to generate skill');
}
});
// Validate command
program
.command('validate')
.alias('check')
.description('Validate AGENTS.md, SKILL.md, or config.toml files')
.option('-f, --file <file>', 'File to validate')
.option('-p, --path <path>', 'Project path to validate all files', process.cwd())
.option('--fix', 'Attempt to fix issues (not yet implemented)', false)
.option('--strict', 'Treat warnings as errors', false)
.action(async (options) => {
try {
printBanner();
const projectPath = path.resolve(options.path);
const filesToValidate = [];
if (options.file) {
// Validate specific file
const filePath = path.resolve(options.file);
if (!await fs.pathExists(filePath)) {
console.error(chalk.red(`File not found: ${filePath}`));
process.exit(1);
}
const fileName = path.basename(filePath).toLowerCase();
if (fileName === 'agents.md') {
filesToValidate.push({ path: filePath, type: 'agents' });
}
else if (fileName === 'skill.md') {
filesToValidate.push({ path: filePath, type: 'skill' });
}
else if (fileName === 'config.toml') {
filesToValidate.push({ path: filePath, type: 'config' });
}
else {
console.error(chalk.red(`Unknown file type: ${fileName}`));
console.log(chalk.gray('Supported files: AGENTS.md, SKILL.md, config.toml'));
process.exit(1);
}
}
else {
// Validate all files in project
console.log(chalk.blue(`Scanning project: ${projectPath}`));
const agentsMd = path.join(projectPath, 'AGENTS.md');
const configToml = path.join(projectPath, '.agents', 'config.toml');
if (await fs.pathExists(agentsMd)) {
filesToValidate.push({ path: agentsMd, type: 'agents' });
}
if (await fs.pathExists(configToml)) {
filesToValidate.push({ path: configToml, type: 'config' });
}
// Find skill files
const skillsDir = path.join(projectPath, '.agents', 'skills');
if (await fs.pathExists(skillsDir)) {
try {
const skills = await fs.readdir(skillsDir);
for (const skill of skills) {
const skillPath = path.join(skillsDir, skill);
const skillStats = await fs.stat(skillPath);
if (skillStats.isDirectory()) {
const skillMd = path.join(skillPath, 'SKILL.md');
if (await fs.pathExists(skillMd)) {
filesToValidate.push({ path: skillMd, type: 'skill' });
}
}
}
}
catch {
// Ignore errors reading skills directory
}
}
}
if (filesToValidate.length === 0) {
console.log(chalk.yellow('No files found to validate'));
console.log(chalk.gray('Run `claude-flow-codex init` to create a project'));
return;
}
console.log(chalk.blue(`\nValidating ${filesToValidate.length} file(s)...\n`));
let hasErrors = false;
let hasWarnings = false;
let totalErrors = 0;
let totalWarnings = 0;
for (const file of filesToValidate) {
let content;
try {
content = await fs.readFile(file.path, 'utf-8');
}
catch (error) {
console.log(chalk.red(` ! Cannot read: ${file.path}`));
hasErrors = true;
continue;
}
let result;
switch (file.type) {
case 'agents':
result = await validateAgentsMd(content);
break;
case 'skill':
result = await validateSkillMd(content);
break;
case 'config':
result = await validateConfigToml(content);
break;
}
const relativePath = path.relative(projectPath, file.path);
if (result.valid && result.warnings.length === 0) {
console.log(chalk.green(` ${chalk.green.bold('PASS')} ${relativePath}`));
}
else if (result.valid) {
console.log(chalk.yellow(` ${chalk.yellow.bold('WARN')} ${relativePath}`));
hasWarnings = true;
}
else {
console.log(chalk.red(` ${chalk.red.bold('FAIL')} ${relativePath}`));
hasErrors = true;
}
for (const error of result.errors) {
totalErrors++;
const lineInfo = error.line ? chalk.gray(` (line ${error.line})`) : '';
console.log(chalk.red(` ${chalk.red('x')} ${error.message}${lineInfo}`));
}
for (const warning of result.warnings) {
totalWarnings++;
console.log(chalk.yellow(` ${chalk.yellow('!')} ${warning.message}`));
if (warning.suggestion) {
console.log(chalk.gray(` ${warning.suggestion}`));
}
}
}
// Summary
console.log();
if (hasErrors) {
console.log(chalk.red.bold(` ${totalErrors} error(s), ${totalWarnings} warning(s)`));
process.exit(1);
}
else if (hasWarnings && options.strict) {
console.log(chalk.yellow.bold(` ${totalWarnings} warning(s) (strict mode)`));
process.exit(1);
}
else if (hasWarnings) {
console.log(chalk.yellow(` All files valid with ${totalWarnings} warning(s)`));
}
else {
console.log(chalk.green.bold(' All files valid!'));
}
}
catch (error) {
handleError(error, 'Validation failed');
}
});
// Migrate command
program
.command('migrate')
.description('Migrate from Claude Code (CLAUDE.md) to Codex (AGENTS.md)')
.option('-f, --from <file>', 'Source CLAUDE.md file', 'CLAUDE.md')
.option('-o, --output <path>', 'Output directory', process.cwd())
.option('--analyze-only', 'Only analyze, do not generate files', false)
.option('--generate-skills', 'Generate skill files from detected patterns', true)
.option('--preserve-comments', 'Preserve comments from original file', true)
.action(async (options) => {
try {
printBanner();
const sourcePath = path.resolve(options.from);
if (!await fs.pathExists(sourcePath)) {
console.error(chalk.red(`Source file not found: ${sourcePath}`));
console.log(chalk.gray('\nLooking for CLAUDE.md in the current directory.'));
console.log(chalk.gray('Use --from <path> to specify a different source file.'));
process.exit(1);
}
let content;
try {
content = await fs.readFile(sourcePath, 'utf-8');
}
catch (error) {
handleError(error, `Cannot read source file: ${sourcePath}`);
}
if (options.analyzeOnly) {
console.log(chalk.blue('Analyzing CLAUDE.md...'));
console.log(chalk.gray(`Source: ${sourcePath}\n`));
const analysis = await analyzeClaudeMd(content);
console.log(chalk.white.bold('Sections found:'));
if (analysis.sections.length > 0) {
for (const section of analysis.sections) {
console.log(chalk.gray(` - ${section}`));
}
}
else {
console.log(chalk.gray(' (none)'));
}
console.log(chalk.white.bold('\nSkills detected:'));
if (analysis.skills.length > 0) {
for (const skill of analysis.skills) {
console.log(chalk.gray(` - /${skill} ${chalk.cyan('->')} $${skill}`));
}
}
else {
console.log(chalk.gray(' (none)'));
}
console.log(chalk.white.bold('\nHooks used:'));
if (analysis.hooks.length > 0) {
for (const hook of analysis.hooks) {
console.log(chalk.gray(` - ${hook}`));
}
}
else {
console.log(chalk.gray(' (none)'));
}
console.log(chalk.white.bold('\nCustom instructions:'));
if (analysis.customInstructions.length > 0) {
for (const instruction of analysis.customInstructions.slice(0, 5)) {
console.log(chalk.gray(` - ${instruction.substring(0, 60)}...`));
}
if (analysis.customInstructions.length > 5) {
console.log(chalk.gray(` ... and ${analysis.customInstructions.length - 5} more`));
}
}
else {
console.log(chalk.gray(' (none)'));
}
if (analysis.warnings.length > 0) {
console.log(chalk.yellow.bold('\nMigration warnings:'));
for (const warning of analysis.warnings) {
console.log(chalk.yellow(` ! ${warning}`));
}
}
console.log();
}
else {
console.log(chalk.blue('Migrating to Codex...'));
console.log(chalk.gray(`Source: ${sourcePath}`));
console.log(chalk.gray(`Output: ${path.resolve(options.output)}\n`));
const result = await migrateFromClaudeCode({
sourcePath,
targetPath: options.output,
generateSkills: options.generateSkills,
preserveComments: options.preserveComments,
});
const report = generateMigrationReport(result);
console.log(report);
if (result.success) {
console.log(chalk.green.bold('\n Migration completed successfully!'));
console.log(chalk.gray('\n Next steps:'));
console.log(chalk.gray(' 1. Review the generated AGENTS.md'));
console.log(chalk.gray(' 2. Check skill invocation syntax (/ -> $)'));
console.log(chalk.gray(' 3. Run `claude-flow-codex validate` to verify'));
console.log();
}
else {
console.log(chalk.red.bold('\n Migration failed'));
process.exit(1);
}
}
}
catch (error) {
handleError(error, 'Migration failed');
}
});
// Templates command
program
.command('templates')
.alias('list-templates')
.description('List available templates')
.option('--json', 'Output as JSON', false)
.action((options) => {
try {
const templates = listTemplates();
if (options.json) {
console.log(JSON.stringify(templates, null, 2));
return;
}
printBanner();
console.log(chalk.white.bold('Available templates:\n'));
for (const template of templates) {
console.log(chalk.cyan.bold(` ${template.name}`));
console.log(chalk.gray(` ${template.description}`));
console.log(chalk.gray(` Skills: ${template.skillCount} included`));
console.log();
}
console.log(chalk.gray('Use: claude-flow-codex init --template <name>'));
console.log();
}
catch (error) {
handleError(error, 'Failed to list templates');
}
});
// Skills command
program
.command('skills')
.alias('list-skills')
.description('List available built-in skills')
.option('--json', 'Output as JSON', false)
.action((options) => {
try {
if (options.json) {
console.log(JSON.stringify(BUILT_IN_SKILLS, null, 2));
return;
}
printBanner();
console.log(chalk.white.bold('Built-in skills:\n'));
for (const [name, info] of Object.entries(BUILT_IN_SKILLS)) {
console.log(chalk.cyan(` $${name}`));
console.log(chalk.gray(` ${info.description}`));
console.log(chalk.gray(` Category: ${info.category}`));
console.log();
}
console.log(chalk.gray('Use: claude-flow-codex generate-skill -n <name> to create a custom skill'));
console.log();
}
catch (error) {
handleError(error, 'Failed to list skills');
}
});
// Info command
program
.command('info')
.description('Show package information')
.option('--json', 'Output as JSON', false)
.action((options) => {
try {
if (options.json) {
console.log(JSON.stringify(PACKAGE_INFO, null, 2));
return;
}
console.log(chalk.cyan.bold('\n @claude-flow/codex'));
console.log(chalk.gray(' ' + '='.repeat(40)));
console.log(chalk.white(` Version: ${PACKAGE_INFO.version}`));
console.log(chalk.white(` Description: ${PACKAGE_INFO.description}`));
console.log(chalk.white(` Future: ${PACKAGE_INFO.futureUmbrella} (umbrella package)`));
console.log(chalk.white(` Repository: ${PACKAGE_INFO.repository}`));
console.log(chalk.gray(' ' + '='.repeat(40)));
console.log(chalk.gray('\n Part of the coflow rebranding initiative'));
console.log();
}
catch (error) {
handleError(error, 'Failed to show info');
}
});
// Doctor command - check system health
program
.command('doctor')
.description('Check system health and dependencies')
.action(async () => {
try {
printBanner();
console.log(chalk.blue('Running health checks...\n'));
const checks = [];
// Check Node.js version
const nodeVersion = process.version;
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0] ?? '0', 10);
if (nodeMajor >= 18) {
checks.push({ name: 'Node.js', status: 'pass', message: `${nodeVersion} (>= 18 required)` });
}
else {
checks.push({ name: 'Node.js', status: 'fail', message: `${nodeVersion} (>= 18 required)` });
}
// Check for AGENTS.md in current directory
const agentsMdExists = await fs.pathExists(path.join(process.cwd(), 'AGENTS.md'));
if (agentsMdExists) {
checks.push({ name: 'AGENTS.md', status: 'pass', message: 'Found in current directory' });
}
else {
checks.push({ name: 'AGENTS.md', status: 'warn', message: 'Not found - run init to create' });
}
// Check for .agents directory
const agentsDir = await fs.pathExists(path.join(process.cwd(), '.agents'));
if (agentsDir) {
checks.push({ name: '.agents/', status: 'pass', message: 'Directory exists' });
}
else {
checks.push({ name: '.agents/', status: 'warn', message: 'Not found - run init to create' });
}
// Check for config.toml
const configExists = await fs.pathExists(path.join(process.cwd(), '.agents', 'config.toml'));
if (configExists) {
checks.push({ name: 'config.toml', status: 'pass', message: 'Found in .agents/' });
}
else {
checks.push({ name: 'config.toml', status: 'warn', message: 'Not found' });
}
// Check for git
try {
const gitExists = await fs.pathExists(path.join(process.cwd(), '.git'));
if (gitExists) {
checks.push({ name: 'Git', status: 'pass', message: 'Repository detected' });
}
else {
checks.push({ name: 'Git', status: 'warn', message: 'Not a git repository' });
}
}
catch {
checks.push({ name: 'Git', status: 'warn', message: 'Cannot check' });
}
// Print results
let hasFailures = false;
for (const check of checks) {
const icon = check.status === 'pass' ? chalk.green('PASS')
: check.status === 'warn' ? chalk.yellow('WARN')
: chalk.red('FAIL');
console.log(` ${icon} ${chalk.white(check.name)}`);
console.log(chalk.gray(` ${check.message}`));
if (check.status === 'fail') {
hasFailures = true;
}
}
console.log();
if (hasFailures) {
console.log(chalk.red.bold(' Some checks failed'));
process.exit(1);
}
else {
console.log(chalk.green.bold(' All checks passed!'));
}
console.log();
}
catch (error) {
handleError(error, 'Health check failed');
}
});
// Dual-mode command - collaborative Claude Code + Codex execution
import { createDualModeCommand } from './dual-mode/index.js';
program.addCommand(createDualModeCommand());
// Error handling for unknown commands
program.on('command:*', () => {
console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));
console.log(chalk.gray(`Run ${chalk.white('claude-flow-codex --help')} for available commands.`));
process.exit(1);
});
// Parse and run
program.parse();
// If no command provided, show help
if (!process.argv.slice(2).length) {
printBanner();
program.outputHelp();
}
//# sourceMappingURL=cli.js.map