yosi.ai
Version:
AI-powered code generation tool using Google's Gemini API
313 lines (277 loc) • 11.7 kB
JavaScript
#!/usr/bin/env node
const { program } = require('commander');
const { generateCode, getAvailableModels } = require('./src/ai-handler');
const { generateAdvancedCode } = require('./src/advanced-ai-handler');
const { saveToFile, displayCode } = require('./src/output-handler');
const { scaffoldProject } = require('./src/scaffold');
const { refactorCode, analyzeCode, convertCode } = require('./src/refactor');
const { generateTests, generateTestSuite } = require('./src/test-generator');
const { version } = require('./package.json');
// Get available models
const MODELS = getAvailableModels();
const MODEL_CHOICES = Object.keys(MODELS);
// Main command for basic code generation
program
.version(version)
.description('yosi.js - AI-powered code generation tool')
.option('-l, --language <language>', 'Programming language for the generated code', 'javascript')
.option('-m, --model <model>', `AI model to use (${MODEL_CHOICES.join(', ')})`, 'gemini-2.5-flash-preview-04-17')
.option('-o, --output <file>', 'Save the generated code to a file')
.option('-v, --verbose', 'Show detailed output')
.argument('<command>', 'Natural language command to generate code')
.action(async (command, options) => {
try {
if (options.verbose) {
console.log('Generating code for:', command);
console.log('Language:', options.language);
console.log('Model:', options.model);
if (options.output) console.log('Output file:', options.output);
}
const generatedCode = await generateCode(command, options.language, options.model);
if (options.output) {
const message = saveToFile(generatedCode, options.output);
console.log(message);
} else {
displayCode(generatedCode, options.language);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Advanced command for more complex code generation
program
.command('advanced')
.description('Generate code with advanced options')
.option('-l, --language <language>', 'Programming language for the generated code', 'javascript')
.option('-f, --framework <framework>', 'Framework to use (e.g., react, express)')
.option('-e, --existing <file>', 'File with existing code to extend or modify')
.option('-c, --comments', 'Include comments in the generated code')
.option('-m, --model <model>', `AI model to use (${MODEL_CHOICES.join(', ')})`, 'gemini-2.5-flash-preview-04-17')
.option('-o, --output <file>', 'Save the generated code to a file') // Add this line
.option('-v, --verbose', 'Show detailed output')
.argument('<command>', 'Natural language command to generate code')
.action(async (command, options) => {
try {
let existingCode = '';
if (options.existing) {
try {
const fs = require('fs');
existingCode = fs.readFileSync(options.existing, 'utf8');
} catch (err) {
console.error(`Error reading existing code file: ${err.message}`);
process.exit(1);
}
}
// Manually parse for the output file flag as a workaround
let outputFile = options.output; // Start with Commander's parsed value (likely undefined)
const outputIndex = process.argv.indexOf('-o');
const outputLongIndex = process.argv.indexOf('--output');
if (outputIndex > -1 && outputIndex + 1 < process.argv.length) {
outputFile = process.argv[outputIndex + 1];
} else if (outputLongIndex > -1 && outputLongIndex + 1 < process.argv.length) {
outputFile = process.argv[outputLongIndex + 1];
}
if (options.verbose) {
console.log('Generating advanced code for:', command);
console.log('Language:', options.language);
console.log('Model:', options.model);
if (options.framework) console.log('Framework:', options.framework);
if (options.existing) console.log('Extending existing code from:', options.existing);
if (options.comments) console.log('Including comments in the generated code');
if (outputFile) console.log('Output file:', outputFile); // Use outputFile here
}
const generatedCode = await generateAdvancedCode({
command,
language: options.language,
framework: options.framework,
existingCode,
includeComments: options.comments,
model: options.model
});
// Always display the code first
displayCode(generatedCode, options.language);
// Use the manually determined outputFile for saving
if (outputFile) {
console.log('\nAttempting to save code to:', outputFile); // Use outputFile here
try {
// Log the first 100 characters of the code for debugging
console.log('Code preview (first 100 chars):', generatedCode.substring(0, 100));
// Check if code contains markdown formatting
if (generatedCode.includes('```')) {
console.log('Warning: Code contains markdown formatting, attempting to clean...');
}
// Force synchronous execution and error handling
try {
const message = saveToFile(generatedCode, outputFile); // Use outputFile here
console.log(message);
// Verify the file was created
const fs = require('fs');
if (fs.existsSync(outputFile)) { // Use outputFile here
console.log('File verified to exist at:', outputFile); // Use outputFile here
console.log('File content length:', fs.readFileSync(outputFile, 'utf8').length); // Use outputFile here
} else {
console.error('ERROR: File was not created despite no errors!');
}
} catch (innerError) {
console.error('Inner error in file saving:', innerError);
console.error('Stack trace:', innerError.stack);
}
} catch (saveError) {
console.error('Error saving file:', saveError.message);
console.error('Error details:', saveError.stack);
}
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Scaffold command for project scaffolding
program
.command('scaffold')
.description('Scaffold a new project')
.option('-n, --name <name>', 'Project name')
.option('-t, --type <type>', 'Project type (react, express, fullstack)', 'react')
.option('-d, --description <description>', 'Project description', 'A new project')
.option('-o, --output <directory>', 'Output directory')
.action(async (options) => {
try {
const projectDir = await scaffoldProject({
projectName: options.name,
projectType: options.type,
description: options.description,
outputDir: options.output
});
console.log(`Project scaffolded successfully at ${projectDir}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Refactor command for code refactoring
program
.command('refactor')
.description('Refactor existing code')
.option('-f, --file <file>', 'File to refactor')
.option('-o, --output <file>', 'Output file (defaults to overwriting the input file)')
.option('-t, --target <section>', 'Target specific section/function/class to refactor')
.option('-p, --preserve-structure', 'Preserve the overall code structure', true)
.option('-v, --verbose', 'Show detailed output')
.argument('<command>', 'Refactoring command (e.g., "optimize", "add comments", "convert to ES6")')
.action(async (command, options) => {
try {
if (!options.file) {
console.error('Error: File path is required');
process.exit(1);
}
if (options.verbose) {
console.log('Refactoring code in:', options.file);
console.log('Refactoring command:', command);
if (options.target) console.log('Target section:', options.target);
console.log('Preserving structure:', options.preserveStructure ? 'Yes' : 'No');
if (options.output) console.log('Output file:', options.output);
}
// Make sure output path is correctly passed
const outputPath = options.output || options.file;
if (options.verbose) {
console.log('Output will be saved to:', outputPath);
console.log('options.output:', options.output);
}
const refactoredCode = await refactorCode({
filePath: options.file,
command,
outputPath,
targetSection: options.target,
preserveStructure: options.preserveStructure
});
console.log(`Code refactored successfully${options.output ? ` and saved to ${options.output}` : ''}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Analyze command for code analysis
program
.command('analyze')
.description('Analyze code and provide suggestions')
.argument('<file>', 'File to analyze')
.action(async (file) => {
try {
const analysis = await analyzeCode(file);
console.log('\nCode Analysis:\n');
console.log(analysis);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Convert command for code conversion
program
.command('convert')
.description('Convert code from one language to another')
.option('-f, --file <file>', 'File to convert')
.option('-t, --to <language>', 'Target language')
.option('-o, --output <file>', 'Output file')
.action(async (options) => {
try {
if (!options.file) {
console.error('Error: File path is required');
process.exit(1);
}
if (!options.to) {
console.error('Error: Target language is required');
process.exit(1);
}
if (!options.output) {
console.error('Error: Output file is required');
process.exit(1);
}
const convertedCode = await convertCode({
filePath: options.file,
targetLanguage: options.to,
outputPath: options.output
});
console.log(`Code converted successfully and saved to ${options.output}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Test command for generating tests
program
.command('test')
.description('Generate tests for a file or directory')
.option('-f, --file <file>', 'File to generate tests for')
.option('-d, --directory <directory>', 'Directory to generate tests for')
.option('-t, --framework <framework>', 'Test framework to use', 'jest')
.option('-o, --output <output>', 'Output file or directory')
.action(async (options) => {
try {
if (options.file) {
// Generate tests for a single file
await generateTests({
filePath: options.file,
testFramework: options.framework,
outputPath: options.output
});
console.log(`Tests generated successfully${options.output ? ` and saved to ${options.output}` : ''}`);
} else if (options.directory) {
// Generate tests for a directory
const testFiles = await generateTestSuite({
directory: options.directory,
testFramework: options.framework,
outputDirectory: options.output,
extensions: ['.js', '.jsx', '.ts', '.tsx']
});
console.log(`Tests generated successfully for ${testFiles.length} files`);
} else {
console.error('Error: Either file or directory is required');
process.exit(1);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program.parse(process.argv);