erosolar-cli
Version:
Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning
439 lines • 18.2 kB
JavaScript
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { verifiedSuccess, verifiedFailure, } from '../core/resultVerification.js';
/**
* Verify a file was actually written with the expected content
*/
function verifyFileWrite(filePath, expectedContent) {
try {
if (!existsSync(filePath)) {
return {
check: `File exists at ${filePath}`,
passed: false,
details: 'File was not created',
};
}
const stats = statSync(filePath);
if (!stats.isFile()) {
return {
check: `Path is a file`,
passed: false,
details: 'Path exists but is not a file',
};
}
const actualContent = readFileSync(filePath, 'utf-8');
if (actualContent !== expectedContent) {
return {
check: `File content matches`,
passed: false,
details: `Expected ${expectedContent.length} bytes, got ${actualContent.length} bytes`,
};
}
return {
check: `File written and verified`,
passed: true,
details: `${stats.size} bytes at ${filePath}`,
};
}
catch (error) {
return {
check: `File verification`,
passed: false,
details: error instanceof Error ? error.message : String(error),
};
}
}
export function createCodeGenerationTools(workingDir) {
return [
{
name: 'generate_component',
description: 'Generate a React/TypeScript component with proper structure and exports',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Component name (PascalCase)',
},
type: {
type: 'string',
enum: ['functional', 'class'],
description: 'Component type (default: functional)',
},
withProps: {
type: 'boolean',
description: 'Include props interface (default: true)',
},
withStyles: {
type: 'boolean',
description: 'Include CSS module import (default: false)',
},
outputPath: {
type: 'string',
description: 'Output file path (relative to workspace)',
},
},
required: ['name', 'outputPath'],
additionalProperties: false,
},
handler: async (args) => {
const startTime = Date.now();
try {
const componentName = validateComponentName(args['name']);
const componentType = args['type'] === 'class' ? 'class' : 'functional';
const withProps = args['withProps'] !== false;
const withStyles = args['withStyles'] === true;
const outputPath = resolveFilePath(workingDir, args['outputPath']);
const componentCode = generateComponentCode({
name: componentName,
type: componentType,
withProps,
withStyles,
});
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, componentCode, 'utf-8');
// CRITICAL: Verify the file was actually written
const verification = verifyFileWrite(outputPath, componentCode);
const durationMs = Date.now() - startTime;
if (verification.passed) {
return verifiedSuccess(`Component "${componentName}" generated`, `Path: ${outputPath}\nType: ${componentType}\nWith Props: ${withProps}\nWith Styles: ${withStyles}`, [verification], durationMs);
}
else {
return verifiedFailure(`Component "${componentName}" generation failed verification`, `Attempted to write to: ${outputPath}`, ['Check file permissions', 'Verify disk space', 'Ensure path is valid'], [verification], durationMs);
}
}
catch (error) {
return verifiedFailure(`Error generating component`, error instanceof Error ? error.message : String(error), ['Check component name is valid PascalCase', 'Verify output path is writable'], [], Date.now() - startTime);
}
},
},
{
name: 'generate_utility_function',
description: 'Generate a TypeScript utility function with proper typing and documentation',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Function name (camelCase)',
},
description: {
type: 'string',
description: 'Function description for JSDoc',
},
parameters: {
type: 'array',
description: 'Function parameters with name and type',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' },
optional: { type: 'boolean' },
},
required: ['name', 'type'],
},
},
returnType: {
type: 'string',
description: 'Return type (default: void)',
},
outputPath: {
type: 'string',
description: 'Output file path',
},
},
required: ['name', 'description', 'outputPath'],
additionalProperties: false,
},
handler: async (args) => {
const startTime = Date.now();
try {
const functionName = validateFunctionName(args['name']);
const description = typeof args['description'] === 'string' ? args['description'].trim() : '';
const parameters = normalizeFunctionParameters(args['parameters']);
const returnType = typeof args['returnType'] === 'string' ? args['returnType'] : 'void';
const outputPath = resolveFilePath(workingDir, args['outputPath']);
const functionCode = generateUtilityFunctionCode({
name: functionName,
description,
parameters,
returnType,
});
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, functionCode, 'utf-8');
// CRITICAL: Verify the file was actually written
const verification = verifyFileWrite(outputPath, functionCode);
const durationMs = Date.now() - startTime;
if (verification.passed) {
return verifiedSuccess(`Utility function "${functionName}" generated`, `Path: ${outputPath}\nReturn Type: ${returnType}\nParameters: ${parameters.length}`, [verification], durationMs);
}
else {
return verifiedFailure(`Utility function "${functionName}" generation failed verification`, `Attempted to write to: ${outputPath}`, ['Check file permissions', 'Verify disk space', 'Ensure path is valid'], [verification], durationMs);
}
}
catch (error) {
return verifiedFailure(`Error generating utility function`, error instanceof Error ? error.message : String(error), ['Check function name is valid camelCase', 'Verify output path is writable'], [], Date.now() - startTime);
}
},
},
{
name: 'generate_type_definition',
description: 'Generate TypeScript type/interface definitions',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Type/interface name (PascalCase)',
},
type: {
type: 'string',
enum: ['interface', 'type'],
description: 'Definition type (default: interface)',
},
properties: {
type: 'array',
description: 'Properties with name and type',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' },
optional: { type: 'boolean' },
},
required: ['name', 'type'],
},
},
outputPath: {
type: 'string',
description: 'Output file path',
},
},
required: ['name', 'outputPath'],
additionalProperties: false,
},
handler: async (args) => {
const startTime = Date.now();
try {
const typeName = validateTypeName(args['name']);
const definitionType = args['type'] === 'type' ? 'type' : 'interface';
const properties = normalizeTypeProperties(args['properties']);
const outputPath = resolveFilePath(workingDir, args['outputPath']);
const typeCode = generateTypeDefinitionCode({
name: typeName,
type: definitionType,
properties,
});
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, typeCode, 'utf-8');
// CRITICAL: Verify the file was actually written
const verification = verifyFileWrite(outputPath, typeCode);
const durationMs = Date.now() - startTime;
if (verification.passed) {
return verifiedSuccess(`${definitionType} "${typeName}" generated`, `Path: ${outputPath}\nProperties: ${properties.length}`, [verification], durationMs);
}
else {
return verifiedFailure(`${definitionType} "${typeName}" generation failed verification`, `Attempted to write to: ${outputPath}`, ['Check file permissions', 'Verify disk space', 'Ensure path is valid'], [verification], durationMs);
}
}
catch (error) {
return verifiedFailure(`Error generating type definition`, error instanceof Error ? error.message : String(error), ['Check type name is valid PascalCase', 'Verify output path is writable'], [], Date.now() - startTime);
}
},
},
];
}
function normalizeFunctionParameters(value) {
if (!Array.isArray(value)) {
return [];
}
const result = [];
for (const entry of value) {
if (!entry || typeof entry !== 'object') {
continue;
}
const record = entry;
const name = typeof record['name'] === 'string' ? record['name'].trim() : '';
const type = typeof record['type'] === 'string' ? record['type'].trim() : '';
if (!name || !type) {
continue;
}
result.push({
name,
type,
optional: record['optional'] === true,
});
}
return result;
}
function normalizeTypeProperties(value) {
if (!Array.isArray(value)) {
return [];
}
const result = [];
for (const entry of value) {
if (!entry || typeof entry !== 'object') {
continue;
}
const record = entry;
const name = typeof record['name'] === 'string' ? record['name'].trim() : '';
const type = typeof record['type'] === 'string' ? record['type'].trim() : '';
if (!name || !type) {
continue;
}
result.push({
name,
type,
optional: record['optional'] === true,
});
}
return result;
}
function resolveFilePath(workingDir, path) {
if (typeof path !== 'string' || !path.trim()) {
throw new Error('Path must be a non-empty string.');
}
const value = path.trim();
return value.startsWith('/') ? value : join(workingDir, value);
}
function validateComponentName(name) {
if (typeof name !== 'string' || !name.trim()) {
throw new Error('Component name must be a non-empty string.');
}
const value = name.trim();
if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) {
throw new Error('Component name must be PascalCase (start with capital letter, no special characters).');
}
return value;
}
function validateFunctionName(name) {
if (typeof name !== 'string' || !name.trim()) {
throw new Error('Function name must be a non-empty string.');
}
const value = name.trim();
if (!/^[a-z][a-zA-Z0-9]*$/.test(value)) {
throw new Error('Function name must be camelCase (start with lowercase letter, no special characters).');
}
return value;
}
function validateTypeName(name) {
if (typeof name !== 'string' || !name.trim()) {
throw new Error('Type name must be a non-empty string.');
}
const value = name.trim();
if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) {
throw new Error('Type name must be PascalCase (start with capital letter, no special characters).');
}
return value;
}
function ensureDirectoryExists(filePath) {
const directory = dirname(filePath);
if (!existsSync(directory)) {
mkdirSync(directory, { recursive: true });
}
}
function generateComponentCode(options) {
const { name, type, withProps, withStyles } = options;
const lines = [];
lines.push("import React from 'react';");
if (withStyles) {
lines.push(`import styles from './${name}.module.css';`);
}
lines.push('');
if (withProps) {
lines.push(`interface ${name}Props {`);
lines.push(' // Add your props here');
lines.push('}');
lines.push('');
}
if (type === 'class') {
lines.push(`class ${name} extends React.Component${withProps ? `<${name}Props>` : ''} {`);
lines.push(' render() {');
lines.push(' return (');
lines.push(' <div>');
lines.push(` <h1>${name} Component</h1>`);
lines.push(' </div>');
lines.push(' );');
lines.push(' }');
lines.push('}');
}
else {
lines.push(`const ${name}: React.FC${withProps ? `<${name}Props>` : ''} = (${withProps ? 'props' : ''}) => {`);
lines.push(' return (');
lines.push(' <div>');
lines.push(` <h1>${name} Component</h1>`);
lines.push(' </div>');
lines.push(' );');
lines.push('};');
}
lines.push('');
lines.push(`export default ${name};`);
return lines.join('\n');
}
function generateUtilityFunctionCode(options) {
const { name, description, parameters, returnType } = options;
const lines = [];
lines.push('/**');
lines.push(` * ${description}`);
if (parameters.length > 0) {
parameters.forEach(param => {
lines.push(` * @param ${param.name} - ${param.type}${param.optional ? ' (optional)' : ''}`);
});
}
lines.push(` * @returns ${returnType}`);
lines.push(' */');
const paramString = parameters
.map(param => `${param.name}${param.optional ? '?' : ''}: ${param.type}`)
.join(', ');
lines.push(`export function ${name}(${paramString}): ${returnType} {`);
lines.push(' // Implement your function logic here');
if (returnType !== 'void') {
lines.push(` return ${getDefaultReturnValue(returnType)};`);
}
lines.push('}');
return lines.join('\n');
}
function generateTypeDefinitionCode(options) {
const { name, type, properties } = options;
const lines = [];
if (type === 'interface') {
lines.push(`export interface ${name} {`);
}
else {
lines.push(`export type ${name} = {`);
}
if (properties.length === 0) {
lines.push(' // Add properties here');
}
else {
properties.forEach(prop => {
lines.push(` ${prop.name}${prop.optional ? '?' : ''}: ${prop.type};`);
});
}
lines.push('};');
return lines.join('\n');
}
function getDefaultReturnValue(returnType) {
switch (returnType) {
case 'string':
return "''";
case 'number':
return '0';
case 'boolean':
return 'false';
case 'any[]':
case 'Array<any>':
return '[]';
case 'object':
case 'Record<string, any>':
return '{}';
case 'null':
return 'null';
case 'undefined':
return 'undefined';
default:
return 'null';
}
}
//# sourceMappingURL=codeGenerationTools.js.map