Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
487
npm/packages/agentic-synth/bin/cli.js
Executable file
487
npm/packages/agentic-synth/bin/cli.js
Executable file
@@ -0,0 +1,487 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Agentic Synth CLI
|
||||
* Production-ready CLI for synthetic data generation
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { AgenticSynth } from '../dist/index.js';
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
// Helper to load JSON config file
|
||||
function loadConfig(configPath) {
|
||||
try {
|
||||
if (!existsSync(configPath)) {
|
||||
throw new Error(`Config file not found: ${configPath}`);
|
||||
}
|
||||
const content = readFileSync(configPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
if (error.message.includes('not found')) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Invalid JSON in config file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to load schema file
|
||||
function loadSchema(schemaPath) {
|
||||
try {
|
||||
if (!existsSync(schemaPath)) {
|
||||
throw new Error(`Schema file not found: ${schemaPath}`);
|
||||
}
|
||||
const content = readFileSync(schemaPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
if (error.message.includes('not found')) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Invalid JSON in schema file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
program
|
||||
.name('agentic-synth')
|
||||
.description('AI-powered synthetic data generation for agentic systems')
|
||||
.version('0.1.6')
|
||||
.addHelpText('after', `
|
||||
Examples:
|
||||
$ agentic-synth generate --count 100 --schema schema.json
|
||||
$ agentic-synth init --provider gemini
|
||||
$ agentic-synth doctor --verbose
|
||||
|
||||
Advanced Examples (via @ruvector/agentic-synth-examples):
|
||||
$ npx @ruvector/agentic-synth-examples dspy train --models gemini,claude
|
||||
$ npx @ruvector/agentic-synth-examples self-learn --task code-generation
|
||||
$ npx @ruvector/agentic-synth-examples list
|
||||
|
||||
Learn more:
|
||||
https://www.npmjs.com/package/@ruvector/agentic-synth-examples
|
||||
https://github.com/ruvnet/ruvector/tree/main/packages/agentic-synth
|
||||
`);
|
||||
|
||||
program
|
||||
.command('generate')
|
||||
.description('Generate synthetic structured data')
|
||||
.option('-c, --count <number>', 'Number of records to generate', '10')
|
||||
.option('-s, --schema <path>', 'Path to JSON schema file')
|
||||
.option('-o, --output <path>', 'Output file path (JSON format)')
|
||||
.option('--seed <value>', 'Random seed for reproducibility')
|
||||
.option('-p, --provider <provider>', 'Model provider (gemini, openrouter)', 'gemini')
|
||||
.option('-m, --model <model>', 'Model name to use')
|
||||
.option('--format <format>', 'Output format (json, csv, array)', 'json')
|
||||
.option('--config <path>', 'Path to config file with provider settings')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Load configuration
|
||||
let config = {
|
||||
provider: options.provider,
|
||||
model: options.model
|
||||
};
|
||||
|
||||
// Load config file if provided
|
||||
if (options.config) {
|
||||
const fileConfig = loadConfig(resolve(options.config));
|
||||
config = { ...config, ...fileConfig };
|
||||
}
|
||||
|
||||
// Ensure API key is set
|
||||
if (!config.apiKey && !process.env.GEMINI_API_KEY && !process.env.OPENROUTER_API_KEY) {
|
||||
console.error('Error: API key not found. Set GEMINI_API_KEY or OPENROUTER_API_KEY environment variable, or provide --config file.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize AgenticSynth
|
||||
const synth = new AgenticSynth(config);
|
||||
|
||||
// Load schema if provided
|
||||
let schema = undefined;
|
||||
if (options.schema) {
|
||||
schema = loadSchema(resolve(options.schema));
|
||||
}
|
||||
|
||||
// Parse count
|
||||
const count = parseInt(options.count, 10);
|
||||
if (isNaN(count) || count < 1) {
|
||||
throw new Error('Count must be a positive integer');
|
||||
}
|
||||
|
||||
// Parse seed
|
||||
let seed = options.seed;
|
||||
if (seed) {
|
||||
const seedNum = parseInt(seed, 10);
|
||||
seed = isNaN(seedNum) ? seed : seedNum;
|
||||
}
|
||||
|
||||
console.log(`Generating ${count} records...`);
|
||||
const startTime = Date.now();
|
||||
|
||||
// Generate data using AgenticSynth
|
||||
const result = await synth.generateStructured({
|
||||
count,
|
||||
schema,
|
||||
seed,
|
||||
format: options.format
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// Output results
|
||||
if (options.output) {
|
||||
const outputPath = resolve(options.output);
|
||||
writeFileSync(outputPath, JSON.stringify(result.data, null, 2));
|
||||
console.log(`✓ Generated ${result.metadata.count} records to ${outputPath}`);
|
||||
} else {
|
||||
console.log(JSON.stringify(result.data, null, 2));
|
||||
}
|
||||
|
||||
// Display metadata
|
||||
console.error(`\nMetadata:`);
|
||||
console.error(` Provider: ${result.metadata.provider}`);
|
||||
console.error(` Model: ${result.metadata.model}`);
|
||||
console.error(` Cached: ${result.metadata.cached}`);
|
||||
console.error(` Duration: ${duration}ms`);
|
||||
console.error(` Generated: ${result.metadata.generatedAt}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error('\nStack trace:');
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('config')
|
||||
.description('Display or test configuration')
|
||||
.option('-f, --file <path>', 'Config file path to load')
|
||||
.option('-t, --test', 'Test configuration by initializing AgenticSynth')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
let config = {};
|
||||
|
||||
// Load config file if provided
|
||||
if (options.file) {
|
||||
config = loadConfig(resolve(options.file));
|
||||
}
|
||||
|
||||
// Create instance to validate config
|
||||
const synth = new AgenticSynth(config);
|
||||
const currentConfig = synth.getConfig();
|
||||
|
||||
console.log('Current Configuration:');
|
||||
console.log(JSON.stringify(currentConfig, null, 2));
|
||||
|
||||
if (options.test) {
|
||||
console.log('\n✓ Configuration is valid and AgenticSynth initialized successfully');
|
||||
}
|
||||
|
||||
// Check for API keys
|
||||
console.log('\nEnvironment Variables:');
|
||||
console.log(` GEMINI_API_KEY: ${process.env.GEMINI_API_KEY ? '✓ Set' : '✗ Not set'}`);
|
||||
console.log(` OPENROUTER_API_KEY: ${process.env.OPENROUTER_API_KEY ? '✓ Set' : '✗ Not set'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Configuration error:', error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error('\nStack trace:');
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('validate')
|
||||
.description('Validate configuration and dependencies')
|
||||
.option('-f, --file <path>', 'Config file path to validate')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
let config = {};
|
||||
|
||||
// Load config file if provided
|
||||
if (options.file) {
|
||||
config = loadConfig(resolve(options.file));
|
||||
console.log('✓ Config file is valid JSON');
|
||||
}
|
||||
|
||||
// Validate by creating instance
|
||||
const synth = new AgenticSynth(config);
|
||||
console.log('✓ Configuration schema is valid');
|
||||
|
||||
// Check provider settings
|
||||
const currentConfig = synth.getConfig();
|
||||
console.log(`✓ Provider: ${currentConfig.provider}`);
|
||||
console.log(`✓ Model: ${currentConfig.model || 'default'}`);
|
||||
console.log(`✓ Cache strategy: ${currentConfig.cacheStrategy}`);
|
||||
console.log(`✓ Max retries: ${currentConfig.maxRetries}`);
|
||||
console.log(`✓ Timeout: ${currentConfig.timeout}ms`);
|
||||
|
||||
// Validate API key
|
||||
if (!currentConfig.apiKey && !process.env.GEMINI_API_KEY && !process.env.OPENROUTER_API_KEY) {
|
||||
console.warn('⚠ Warning: No API key found. Set GEMINI_API_KEY or OPENROUTER_API_KEY environment variable.');
|
||||
} else {
|
||||
console.log('✓ API key is configured');
|
||||
}
|
||||
|
||||
console.log('\n✓ All validations passed');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error('\nStack trace:');
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize a new agentic-synth configuration file')
|
||||
.option('-f, --force', 'Overwrite existing config file')
|
||||
.option('-p, --provider <provider>', 'Model provider (gemini, openrouter)', 'gemini')
|
||||
.option('-o, --output <path>', 'Output config file path', '.agentic-synth.json')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
const configPath = resolve(options.output);
|
||||
|
||||
// Check if file exists
|
||||
if (existsSync(configPath) && !options.force) {
|
||||
console.error(`Error: Config file already exists at ${configPath}`);
|
||||
console.error('Use --force to overwrite');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create default configuration
|
||||
const defaultConfig = {
|
||||
provider: options.provider,
|
||||
model: options.provider === 'gemini' ? 'gemini-2.0-flash-exp' : 'anthropic/claude-3-opus',
|
||||
cacheStrategy: 'memory',
|
||||
maxRetries: 3,
|
||||
timeout: 30000,
|
||||
debug: false
|
||||
};
|
||||
|
||||
// Write config file
|
||||
writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
||||
console.log(`✓ Created configuration file: ${configPath}`);
|
||||
console.log('\nNext steps:');
|
||||
console.log('1. Set your API key:');
|
||||
if (options.provider === 'gemini') {
|
||||
console.log(' export GEMINI_API_KEY="your-api-key"');
|
||||
} else {
|
||||
console.log(' export OPENROUTER_API_KEY="your-api-key"');
|
||||
}
|
||||
console.log('2. Edit the config file to customize settings');
|
||||
console.log('3. Run: agentic-synth doctor');
|
||||
console.log('4. Generate data: agentic-synth generate --config .agentic-synth.json');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating config:', error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error('\nStack trace:');
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('doctor')
|
||||
.description('Run comprehensive diagnostics on environment and configuration')
|
||||
.option('-f, --file <path>', 'Config file path to check')
|
||||
.option('-v, --verbose', 'Show detailed diagnostic information')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
console.log('🔍 Running diagnostics...\n');
|
||||
|
||||
let errorCount = 0;
|
||||
let warningCount = 0;
|
||||
|
||||
// Check 1: Node.js version
|
||||
console.log('1. Node.js Environment:');
|
||||
const nodeVersion = process.version;
|
||||
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
|
||||
if (majorVersion >= 18) {
|
||||
console.log(` ✓ Node.js ${nodeVersion} (compatible)`);
|
||||
} else {
|
||||
console.log(` ✗ Node.js ${nodeVersion} (requires >= 18.0.0)`);
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
// Check 2: Environment variables
|
||||
console.log('\n2. API Keys:');
|
||||
const hasGeminiKey = !!process.env.GEMINI_API_KEY;
|
||||
const hasOpenRouterKey = !!process.env.OPENROUTER_API_KEY;
|
||||
|
||||
if (hasGeminiKey) {
|
||||
console.log(' ✓ GEMINI_API_KEY is set');
|
||||
if (options.verbose) {
|
||||
console.log(` Value: ${process.env.GEMINI_API_KEY.substring(0, 10)}...`);
|
||||
}
|
||||
} else {
|
||||
console.log(' ✗ GEMINI_API_KEY not set');
|
||||
warningCount++;
|
||||
}
|
||||
|
||||
if (hasOpenRouterKey) {
|
||||
console.log(' ✓ OPENROUTER_API_KEY is set');
|
||||
if (options.verbose) {
|
||||
console.log(` Value: ${process.env.OPENROUTER_API_KEY.substring(0, 10)}...`);
|
||||
}
|
||||
} else {
|
||||
console.log(' ✗ OPENROUTER_API_KEY not set');
|
||||
warningCount++;
|
||||
}
|
||||
|
||||
if (!hasGeminiKey && !hasOpenRouterKey) {
|
||||
console.log(' ⚠ Warning: No API keys configured. At least one is required.');
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
// Check 3: Configuration file
|
||||
console.log('\n3. Configuration:');
|
||||
let config = {};
|
||||
if (options.file) {
|
||||
try {
|
||||
config = loadConfig(resolve(options.file));
|
||||
console.log(` ✓ Config file loaded: ${options.file}`);
|
||||
if (options.verbose) {
|
||||
console.log(` Content: ${JSON.stringify(config, null, 6)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ✗ Failed to load config: ${error.message}`);
|
||||
errorCount++;
|
||||
}
|
||||
} else {
|
||||
const defaultPaths = ['.agentic-synth.json', 'agentic-synth.json', 'config.json'];
|
||||
let found = false;
|
||||
for (const path of defaultPaths) {
|
||||
if (existsSync(path)) {
|
||||
config = loadConfig(path);
|
||||
console.log(` ✓ Auto-detected config: ${path}`);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
console.log(' ⚠ No config file found (using defaults)');
|
||||
warningCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check 4: AgenticSynth initialization
|
||||
console.log('\n4. Package Initialization:');
|
||||
try {
|
||||
const synth = new AgenticSynth(config);
|
||||
const currentConfig = synth.getConfig();
|
||||
console.log(' ✓ AgenticSynth initialized successfully');
|
||||
console.log(` ✓ Provider: ${currentConfig.provider}`);
|
||||
console.log(` ✓ Model: ${currentConfig.model || 'default'}`);
|
||||
console.log(` ✓ Cache: ${currentConfig.cacheStrategy}`);
|
||||
console.log(` ✓ Max retries: ${currentConfig.maxRetries}`);
|
||||
console.log(` ✓ Timeout: ${currentConfig.timeout}ms`);
|
||||
} catch (error) {
|
||||
console.log(` ✗ Failed to initialize: ${error.message}`);
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
// Check 5: Dependencies
|
||||
console.log('\n5. Dependencies:');
|
||||
try {
|
||||
// Check if required packages are available
|
||||
const packages = [
|
||||
'@google/generative-ai',
|
||||
'commander',
|
||||
'dotenv',
|
||||
'zod'
|
||||
];
|
||||
|
||||
for (const pkg of packages) {
|
||||
try {
|
||||
await import(pkg);
|
||||
console.log(` ✓ ${pkg}`);
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${pkg} not found`);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ✗ Dependency check failed: ${error.message}`);
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
// Check 6: File system permissions
|
||||
console.log('\n6. File System:');
|
||||
try {
|
||||
const testPath = resolve('.agentic-synth-test.tmp');
|
||||
writeFileSync(testPath, 'test');
|
||||
readFileSync(testPath);
|
||||
// Clean up
|
||||
import('fs').then(fs => fs.unlinkSync(testPath));
|
||||
console.log(' ✓ Read/write permissions OK');
|
||||
} catch (error) {
|
||||
console.log(' ✗ File system permissions issue');
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(50));
|
||||
if (errorCount === 0 && warningCount === 0) {
|
||||
console.log('✓ All checks passed! Your environment is ready.');
|
||||
} else {
|
||||
if (errorCount > 0) {
|
||||
console.log(`✗ Found ${errorCount} error(s)`);
|
||||
}
|
||||
if (warningCount > 0) {
|
||||
console.log(`⚠ Found ${warningCount} warning(s)`);
|
||||
}
|
||||
console.log('\nRecommendations:');
|
||||
if (!hasGeminiKey && !hasOpenRouterKey) {
|
||||
console.log('- Set at least one API key (GEMINI_API_KEY or OPENROUTER_API_KEY)');
|
||||
}
|
||||
if (errorCount > 0) {
|
||||
console.log('- Fix errors above before using agentic-synth');
|
||||
}
|
||||
if (!options.file && warningCount > 0) {
|
||||
console.log('- Run: agentic-synth init');
|
||||
console.log('- Then: agentic-synth doctor --file .agentic-synth.json');
|
||||
}
|
||||
}
|
||||
console.log('='.repeat(50));
|
||||
|
||||
process.exit(errorCount > 0 ? 1 : 0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Doctor command error:', error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error('\nStack trace:');
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Error handler for unknown commands
|
||||
program.on('command:*', function () {
|
||||
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Show help if no command provided
|
||||
if (process.argv.length === 2) {
|
||||
program.help();
|
||||
}
|
||||
|
||||
program.parse();
|
||||
Reference in New Issue
Block a user