UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

599 lines (535 loc) • 20.9 kB
#!/usr/bin/env node const { Command } = require('commander'); const path = require('path'); const { execSync } = require('child_process'); const { version } = require('../package.json'); const program = new Command(); program .name('sf-agent') .description('SF-Agent Framework CLI for building and managing agents') .version(version); program .command('install') .description('Install SF-Agent Framework agents and tools interactively') .option('-f, --full', 'Install complete SF-Agent Framework') .option('-d, --directory <path>', 'Installation directory') .option( '-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, cline, gemini, github-copilot, other)' ) .action(async (options) => { try { // Use child_process to run the installer - this is more reliable for npm packages const installerPath = path.join(__dirname, 'installer', 'bin', 'sf-agent.js'); // Build the command let cmd = `node "${installerPath}" install`; if (options.full) cmd += ' --full'; if (options.directory) cmd += ` --directory "${options.directory}"`; if (options.ide && options.ide.length > 0) { cmd += ` --ide ${options.ide.join(' ')}`; } // Execute the installer execSync(cmd, { stdio: 'inherit' }); } catch (error) { console.error('Installation failed:', error.message); process.exit(1); } }); // Story-based context engineering commands program .command('shard <document>') .description('Shard planning document into story-sized pieces') .option('-o, --output <dir>', 'Output directory', 'docs/sharded') .option('-v, --verbose', 'Verbose output') .action(async (document, options) => { try { const sharderPath = path.join(__dirname, 'document-sharder.js'); const cmd = `node "${sharderPath}" "${document}" -o "${options.output}"${options.verbose ? ' -v' : ''}`; execSync(cmd, { stdio: 'inherit' }); } catch (error) { console.error('Sharding failed:', error.message); process.exit(1); } }); program .command('story <action> [storyId]') .description('Manage story queue and context') .option('-d, --dir <directory>', 'Story directory', 'docs/stories') .action(async (action, storyId, options) => { try { const queuePath = path.join(__dirname, 'story-queue-manager.js'); let cmd = `node "${queuePath}" ${action}`; if (storyId) cmd += ` ${storyId}`; if (options.dir) cmd += ` -d "${options.dir}"`; execSync(cmd, { stdio: 'inherit' }); } catch (error) { console.error('Story management failed:', error.message); process.exit(1); } }); program .command('validate-story <storyId>') .description('Validate story has complete context for development') .option('--fix', 'Apply auto-fix suggestions') .option('--block', 'Block story if invalid') .action(async (storyId, options) => { console.log(`Validating story ${storyId}...`); // Story validation logic would go here console.log('Story validation complete'); }); program .command('phase <phase>') .description('Switch between planning and development phases') .action(async (phase) => { if (!['planning', 'development'].includes(phase)) { console.error('Phase must be either "planning" or "development"'); process.exit(1); } console.log(`Switching to ${phase} phase...`); console.log(`Context limit: ${phase === 'planning' ? '128k' : '32k'} tokens`); console.log(`Agent mode: ${phase === 'planning' ? 'Rich' : 'Lean'}`); }); program .command('workflow [workflowId]') .description('Start an interactive workflow session') .option('-d, --dir <directory>', 'Workflow directory', 'sf-core/workflows') .option( '-o, --output <directory>', 'Output directory for session files', 'docs/workflow-sessions' ) .option('-v, --verbose', 'Verbose output') .action(async (workflowId, options) => { try { const InteractiveWorkflowPlanner = require('./lib/interactive-workflow-planner'); const planner = new InteractiveWorkflowPlanner(process.cwd()); if (!workflowId) { // List available workflows console.log('\nšŸ“‹ Available Workflows:\n'); const workflows = await planner.listWorkflows(); if (workflows.length === 0) { console.log('No workflows found. Creating default interactive workflow...'); workflowId = 'interactive-project-setup'; } else { workflows.forEach((w) => console.log(` - ${w}`)); console.log('\nRun: sf-agent workflow <workflow-name> to start\n'); return; } } // Start the interactive workflow const result = await planner.startInteractiveWorkflow(workflowId); // Display summary console.log('\nāœ… Workflow Completed!\n'); console.log('Summary:', JSON.stringify(result, null, 2)); // Save session results if output directory specified if (options.output) { const fs = require('fs').promises; const outputPath = path.join(options.output, `${workflowId}-${Date.now()}.json`); await fs.mkdir(options.output, { recursive: true }); await fs.writeFile(outputPath, JSON.stringify(result, null, 2)); console.log(`\nSession saved to: ${outputPath}`); } } catch (error) { console.error('Workflow execution failed:', error.message); process.exit(1); } }); program .command('handoff <action> [handoffId]') .description('Manage agent handoffs (create, accept, complete, status)') .option('--from <agent>', 'Source agent (for create)') .option('--to <agent>', 'Target agent (for create)') .option('--agent <agent>', 'Agent ID (for accept)') .option('--artifacts <paths...>', 'Artifact paths') .option('--notes <text>', 'Notes') .option('--next <agent>', 'Next agent in chain (for complete)') .action(async (action, handoffId, options) => { try { const AgentCollaborationProtocol = require('./lib/agent-collaboration-protocol'); const protocol = new AgentCollaborationProtocol(process.cwd()); await protocol.initialize(); switch (action) { case 'create': if (!options.from || !options.to) { console.error('Error: --from and --to are required for create'); process.exit(1); } const handoff = await protocol.createHandoff({ from: options.from, to: options.to, artifacts: options.artifacts || [], context: { notes: options.notes }, }); console.log(`\nāœ… Handoff created: ${handoff.id}`); console.log(`From: ${handoff.from} → To: ${handoff.to}`); break; case 'accept': if (!handoffId || !options.agent) { console.error('Error: handoffId and --agent are required for accept'); process.exit(1); } const accepted = await protocol.acceptHandoff(handoffId, options.agent); console.log(`\nāœ… Handoff ${handoffId} accepted by ${options.agent}`); console.log(`Artifacts loaded: ${accepted.artifacts.length}`); break; case 'complete': if (!handoffId || !options.agent) { console.error('Error: handoffId and --agent are required for complete'); process.exit(1); } const deliverables = options.artifacts?.map((a) => ({ name: a, content: `Deliverable: ${a}` })) || []; const completed = await protocol.completeHandoff(handoffId, options.agent, deliverables); console.log(`\nāœ… Handoff ${handoffId} completed`); console.log(`Deliverables: ${deliverables.length}`); break; case 'status': if (handoffId) { const status = protocol.getHandoffStatus(handoffId); console.log(`\nHandoff ${handoffId}:`); console.log(JSON.stringify(status, null, 2)); } else { const handoffs = protocol.listActiveHandoffs(options.agent); console.log(`\nActive Handoffs${options.agent ? ` for ${options.agent}` : ''}:`); handoffs.forEach((h) => { console.log(` - ${h.id}: ${h.from} → ${h.to} (${h.status})`); }); } break; case 'metrics': const metrics = protocol.getCollaborationMetrics(); console.log('\nšŸ“Š Collaboration Metrics:'); console.log(JSON.stringify(metrics, null, 2)); break; default: console.error(`Unknown action: ${action}`); console.log('Available actions: create, accept, complete, status, metrics'); process.exit(1); } } catch (error) { console.error('Handoff operation failed:', error.message); process.exit(1); } }); program .command('update') .description('Update existing SF-Agent installation') .option('--force', 'Force update, overwriting modified files') .option('--dry-run', 'Show what would be updated without making changes') .action(async (options) => { try { // Use child_process to run the installer - this is more reliable for npm packages const installerPath = path.join(__dirname, 'installer', 'bin', 'sf-agent.js'); // Build the command let cmd = `node "${installerPath}" update`; if (options.force) cmd += ' --force'; if (options.dryRun) cmd += ' --dry-run'; // Execute the installer execSync(cmd, { stdio: 'inherit' }); } catch (error) { console.error('Update failed:', error.message); process.exit(1); } }); program .command('build') .description('Build web bundles for agents and teams') .option('-a, --agents-only', 'Build only agent bundles') .option('-t, --teams-only', 'Build only team bundles') .option('--no-clean', 'Skip cleaning output directories') .action(async (options) => { const WebBuilder = require('./builders/web-builder'); const builder = new WebBuilder({ rootDir: process.cwd(), }); try { if (options.clean) { console.log('Cleaning output directories...'); await builder.cleanOutputDirs(); } if (!options.teamsOnly) { console.log('Building agent bundles...'); await builder.buildAgents(); } if (!options.agentsOnly) { console.log('Building team bundles...'); await builder.buildTeams(); } console.log('Build completed successfully!'); } catch (error) { console.error('Build failed:', error.message); process.exit(1); } }); program .command('list:agents') .description('List all available agents') .action(async () => { const WebBuilder = require('./builders/web-builder'); const builder = new WebBuilder({ rootDir: process.cwd() }); const agents = await builder.resolver.listAgents(); console.log('Available agents:'); agents.forEach((agent) => console.log(` - ${agent}`)); }); program .command('validate') .description('Validate agent and team configurations') .action(async () => { const WebBuilder = require('./builders/web-builder'); const builder = new WebBuilder({ rootDir: process.cwd() }); try { // Validate by attempting to build all agents and teams const agents = await builder.resolver.listAgents(); const teams = await builder.resolver.listTeams(); console.log('Validating agents...'); for (const agent of agents) { await builder.resolver.resolveAgentDependencies(agent); console.log(` āœ“ ${agent}`); } console.log('\nValidating teams...'); for (const team of teams) { await builder.resolver.resolveTeamDependencies(team); console.log(` āœ“ ${team}`); } console.log('\nAll configurations are valid!'); } catch (error) { console.error('Validation failed:', error.message); process.exit(1); } }); program .command('upgrade') .description('Upgrade a SF-Agent-Method V3 project to V4') .option('-p, --project <path>', 'Path to V3 project (defaults to current directory)') .option('--dry-run', 'Show what would be changed without making changes') .option('--no-backup', 'Skip creating backup (not recommended)') .action(async (options) => { const V3ToV4Upgrader = require('./upgraders/v3-to-v4-upgrader'); const upgrader = new V3ToV4Upgrader(); await upgrader.upgrade({ projectPath: options.project, dryRun: options.dryRun, backup: options.backup, }); }); program .command('mcp:setup') .description('Set up MCP (Model Context Protocol) for your IDE') .option( '-i, --ide <ide>', 'Specific IDE to configure (cursor, claude-code, windsurf, github-copilot, vscode)' ) .option('-a, --all', 'Configure MCP for all supported IDEs') .option('-u, --universal', 'Create universal MCP configuration file') .action(async (options) => { const IdeSetup = require('./installer/lib/ide-setup'); const projectDir = process.cwd(); try { if (options.universal || options.all) { console.log('Creating universal MCP configuration...'); await IdeSetup.createUniversalMCPConfig(projectDir); } if (options.all) { console.log('\nConfiguring MCP for all supported IDEs...'); const ides = ['cursor', 'claude-code', 'windsurf', 'github-copilot', 'vscode']; for (const ide of ides) { console.log(`\nConfiguring ${ide}...`); switch (ide) { case 'cursor': await IdeSetup.setupCursorMCP(projectDir); break; case 'claude-code': await IdeSetup.setupClaudeCodeMCP(projectDir); break; case 'windsurf': await IdeSetup.setupWindsurfMCP(projectDir); break; case 'github-copilot': await IdeSetup.setupGitHubCopilotMCP(projectDir); break; case 'vscode': await IdeSetup.setupVSCodeMCP(projectDir); break; } } } else if (options.ide) { console.log(`Configuring MCP for ${options.ide}...`); switch (options.ide) { case 'cursor': await IdeSetup.setupCursorMCP(projectDir); break; case 'claude-code': await IdeSetup.setupClaudeCodeMCP(projectDir); break; case 'windsurf': await IdeSetup.setupWindsurfMCP(projectDir); break; case 'github-copilot': await IdeSetup.setupGitHubCopilotMCP(projectDir); break; case 'vscode': await IdeSetup.setupVSCodeMCP(projectDir); break; default: console.error(`Unsupported IDE: ${options.ide}`); console.log('Supported IDEs: cursor, claude-code, windsurf, github-copilot, vscode'); process.exit(1); } } else { console.log('Please specify an IDE with --ide or use --all for all IDEs'); console.log('Example: npm run mcp:setup -- --ide cursor'); console.log('Example: npm run mcp:setup -- --all'); } console.log('\nāœ“ MCP setup complete!'); console.log('See sf-core/mcp/MCP-IDE-GUIDE.md for usage instructions'); } catch (error) { console.error('MCP setup failed:', error.message); process.exit(1); } }); program .command('mcp:list') .description('List all available MCP servers in the project') .action(async () => { const IdeSetup = require('./installer/lib/ide-setup'); const projectDir = process.cwd(); const servers = await IdeSetup.discoverMCPServers(projectDir); if (servers.length === 0) { console.log('No MCP servers found in the project.'); console.log('To add MCP servers, create .js files in:'); console.log(' - .sf-core/mcp/servers/'); return; } console.log('Available MCP servers:'); servers.forEach((server) => { console.log(`\n ${server.packName}/${server.serverName}`); console.log(` Path: ${server.relativePath}`); }); console.log(`\nTotal: ${servers.length} MCP servers found`); }); program .command('metrics:record <eventName> [data]') .description('Record a metric event') .action((eventName, data) => { const { recordEvent } = require('./metrics-tracker'); let parsedData = null; if (data) { try { parsedData = JSON.parse(data); } catch (error) { console.error('Invalid JSON data:', error.message); process.exit(1); } } recordEvent(eventName, parsedData); }); program .command('metrics:report') .description('Generate a metrics report') .action(() => { const { generateReport } = require('./metrics-tracker'); generateReport(); }); program .command('visualize:workflow <file>') .description('Generate a Mermaid diagram for a workflow YAML file') .action((file) => { const visualizeWorkflow = require('./workflow-visualizer'); visualizeWorkflow(file); }); program .command('create-agent') .description('Create a new agent interactively') .action(() => { try { execSync('node tools/agent-creator.js', { stdio: 'inherit' }); } catch (error) { console.error('Agent creation failed:', error.message); process.exit(1); } }); program .command('flatten') .description('Flatten the codebase into a single XML file') .action(() => { try { console.log('Flattening codebase...'); execSync('node tools/codebase-flattener.js', { stdio: 'inherit' }); console.log('Codebase flattened successfully!'); } catch (error) { console.error('Codebase flattening failed:', error.message); process.exit(1); } }); // Memory system commands program .command('memory <action>') .description('Manage context memory system (init, store, retrieve, stats, patterns)') .option('--query <text>', 'Query for retrieval') .option('--type <type>', 'Context type') .option('--agent <agent>', 'Agent name') .option('--content <text>', 'Content to store') .option('--limit <number>', 'Limit results', '10') .action(async (action, options) => { try { const ContextMemorySystem = require('./lib/context-memory-system'); const memory = new ContextMemorySystem(process.cwd()); switch (action) { case 'init': await memory.initialize(); console.log('āœ… Memory system initialized'); console.log(`Session ID: ${memory.currentSession.id}`); break; case 'store': await memory.initialize(); const contextId = await memory.storeContext({ type: options.type || 'general', agent: options.agent || 'user', content: options.content || 'Test context', metadata: {}, }); console.log(`āœ… Context stored: ${contextId}`); break; case 'retrieve': await memory.initialize(); const contexts = await memory.retrieveContext(options.query || '', { limit: parseInt(options.limit), }); console.log(`\nšŸ“š Retrieved ${contexts.length} contexts:`); contexts.forEach((c, i) => { console.log(`\n${i + 1}. ${c.id} (${c.type})`); console.log(` Agent: ${c.agent}`); console.log(` Importance: ${c.importance}`); }); break; case 'stats': await memory.initialize(); const stats = memory.getMemoryStats(); console.log('\nšŸ“Š Memory Statistics:'); console.log(JSON.stringify(stats, null, 2)); break; case 'patterns': await memory.initialize(); const patterns = memory.getLearnedPatterns(options.type); console.log(`\nšŸ” Learned Patterns${options.type ? ` (${options.type})` : ''}:`); patterns.forEach((p, i) => { console.log(`\n${i + 1}. ${p.pattern.type}`); console.log(` Frequency: ${p.frequency}`); console.log(` First seen: ${p.first_seen}`); }); break; case 'end': await memory.initialize(); const session = await memory.endSession(); console.log(`āœ… Session ended: ${session.id}`); break; default: console.error(`Unknown action: ${action}`); console.log('Available actions: init, store, retrieve, stats, patterns, end'); } } catch (error) { console.error('Memory operation failed:', error.message); process.exit(1); } }); program.parse(process.argv);