crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
339 lines (303 loc) • 9.79 kB
JavaScript
/**
* CLI command for scaffolding a new Flow.
* Optimized for developer experience with intelligent defaults.
*/
import fs from 'fs';
import path from 'path';
import { Command } from '../Command.js';
export class CreateFlowCommand extends Command {
name = 'create-flow';
description = 'Scaffold a new Flow class file';
syntax = '<flow-name> [options]';
examples = [
'create-flow MyWorkflow',
'create-flow DataProcessingFlow --path ./src/flows',
'create-flow UserOnboardingFlow --with-example'
];
async execute(args) {
const parsedArgs = this.parseArgs(args);
if (!parsedArgs.flowName) {
console.error('Error: Flow name is required');
this.showHelp();
process.exit?.(1);
}
try {
// Ensure the flow name has proper casing
const flowName = this.formatFlowName(parsedArgs.flowName);
// Prepare the output directory
const outputDir = parsedArgs.path || './src/flows';
const outputPath = path.resolve(process.cwd?.() || '', outputDir);
// Create directory if it doesn't exist
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
// Generate the flow file
const flowFilePath = path.join(outputPath, `${flowName}.ts`);
// Check if file already exists to avoid overwriting
if (fs.existsSync(flowFilePath)) {
console.error(`Error: Flow file already exists at ${flowFilePath}`);
process.exit?.(1);
}
// Generate the flow file content
const template = parsedArgs.withExample ?
this.getExampleFlowTemplate(flowName) :
this.getBasicFlowTemplate(flowName);
// Write the file
fs.writeFileSync(flowFilePath, template, 'utf8');
console.log(`\n✅ Successfully created new Flow class!`);
console.log(`File: ${flowFilePath}`);
console.log(`\nYou can now start adding methods to your Flow class.`);
console.log(`To visualize your flow, use: plot-flow ${flowFilePath}`);
}
catch (error) {
console.error('Error creating flow file:', error);
process.exit?.(1);
}
}
/**
* Parse command line arguments for the create-flow command
*/
parseArgs(args) {
if (args.length === 0) {
return {};
}
const result = {
flowName: args[0]
};
// Parse options
for (let i = 1; i < args.length; i++) {
if (args[i] === '--path' && i + 1 < args.length) {
result.path = args[++i];
}
else if (args[i] === '--with-example') {
result.withExample = true;
}
}
return result;
}
/**
* Format the flow name to ensure it follows naming conventions
*/
formatFlowName(name) {
// Remove common suffixes if they exist
let flowName = name;
if (flowName.endsWith('Flow')) {
flowName = flowName;
}
else {
flowName = `${flowName}Flow`;
}
// Ensure PascalCase
return flowName.charAt(0).toUpperCase() + flowName.slice(1);
}
/**
* Get a basic Flow class template with optimized loading
*/
getBasicFlowTemplate(flowName) {
// Use the hardcoded template for now until we add proper flow template handling
// This ensures maximum compatibility with different development environments
return `/**
* ${flowName} - A flow for orchestrating a sequence of operations.
*/
import { Flow, FlowState } from 'crewai-ts/flow';
import { start, listen, router, and_, or_ } from 'crewai-ts/flow/decorators';
import { CONTINUE, STOP } from 'crewai-ts/flow/types';
/**
* State for the ${flowName}
*/
export class ${flowName}State extends FlowState {
// Define your flow state properties here
// For example:
// progress: number = 0;
// data: any = null;
// errors: string[] = [];
}
/**
* ${flowName} - Add your flow description here
*/
export class ${flowName} extends Flow<${flowName}State> {
constructor() {
// Initialize with default state
super({
initialState: new ${flowName}State()
});
}
/**
* Starting point for the flow
*/
async begin() {
console.log('Flow started');
// Add your flow implementation here
return CONTINUE;
}
}
`;
}
/**
* Get an example Flow class template with more methods and patterns
*/
getExampleFlowTemplate(flowName) {
return `/**
* \${flowName} - A detailed example flow showing various patterns.
*/
import { Flow, FlowState } from 'crewai-ts/flow';
import { start, listen, router, and_, or_ } from 'crewai-ts/flow/decorators';
import { CONTINUE, STOP } from 'crewai-ts/flow/types';
/**
* State for the ${flowName}
*/
export class ${flowName}State extends FlowState {
// Flow execution tracking
progress: number = 0;
completed: boolean = false;
hasErrors: boolean = false;
// Data storage
inputData: any = null;
processedData: any = null;
results: any[] = [];
errors: string[] = [];
// Timing information
startTime: number = 0;
endTime: number = 0;
}
/**
* ${flowName} - An example flow demonstrating various flow patterns
* and techniques for effective flow composition.
*/
export class ${flowName} extends Flow<${flowName}State> {
constructor() {
// Initialize with default state
super({
initialState: new ${flowName}State()
});
}
/**
* Starting point for the flow
*/
async begin() {
// Record start time
this.state.startTime = Date.now();
console.log('Flow started');
// Initialize flow execution
this.state.progress = 10;
// Input validation
return CONTINUE;
}
/**
* Validate input data
* This method is triggered after the flow begins
*/
async validateInput(previousResult: any) {
console.log('Validating input data...');
try {
// Simulate input validation
await new Promise(resolve => setTimeout(resolve, 500));
// Update state
this.state.progress = 30;
this.state.inputData = { valid: true, data: 'example data' };
console.log('Input data valid');
return this.state.inputData;
} catch (error) {
// Handle validation errors
this.state.hasErrors = true;
this.state.errors.push('Input validation failed');
return STOP;
}
}
/**
* Process the validated data
* This method is triggered after input validation completes
*/
async processData(inputData: any) {
console.log('Processing data...');
// Simulate data processing
await new Promise(resolve => setTimeout(resolve, 1000));
// Process the data
this.state.processedData = {
...inputData,
processed: true,
timestamp: new Date().toISOString()
};
// Update progress
this.state.progress = 60;
console.log('Data processing complete');
return this.state.processedData;
}
/**
* Generate results from processed data
* This method is triggered after data processing completes
*/
async generateResults(processedData: any) {
console.log('Generating results...');
// Simulate result generation
await new Promise(resolve => setTimeout(resolve, 800));
// Generate results
this.state.results = [
{ id: 1, value: 'Result 1', source: processedData },
{ id: 2, value: 'Result 2', source: processedData }
];
// Update progress
this.state.progress = 90;
console.log(\`Generated \${this.state.results.length} results\`);
return this.state.results;
}
/**
* Complete the flow
* This method is triggered after results are generated
*/
async complete(results: any[]) {
console.log('Completing flow...');
// Record completion time
this.state.endTime = Date.now();
this.state.completed = true;
this.state.progress = 100;
// Calculate execution time
const executionTime = (this.state.endTime - this.state.startTime) / 1000;
console.log(\`Flow completed in \${executionTime.toFixed(2)} seconds\`);
console.log(\`Generated \${results.length} results\`);
// Signal flow completion
return STOP;
}
/**
* Error handler that listens to multiple methods
* This demonstrates using OR condition to trigger on any error
*/
async handleError(error: any) {
console.error('Error in flow execution:', error);
// Update error state
this.state.hasErrors = true;
this.state.errors.push(error.message || 'Unknown error');
// Record completion with error
this.state.endTime = Date.now();
console.log('Flow stopped due to error');
return STOP;
}
/**
* Example of a routing method that determines flow path
* based on conditions in the state
*/
async routeProcessing(inputData: any) {
if (!inputData || !inputData.valid) {
console.log('Input validation failed, stopping flow');
return STOP;
}
if (inputData.priority === 'high') {
console.log('High priority processing route');
// Could trigger a different processing method
} else {
console.log('Standard processing route');
}
return CONTINUE;
}
}
`;
}
}