UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

155 lines (134 loc) 4.8 kB
/** * Search Command - Main entry point for worker search * * Implements `aios workers search <query>` CLI command. * Supports semantic search (OpenAI embeddings) and keyword fallback. * * @module cli/commands/workers/search * @version 1.0.0 * @story 2.7 - Discovery CLI Search */ const { Command } = require('commander'); const path = require('path'); // Local modules const { searchSemantic, isSemanticAvailable } = require('./search-semantic'); const { searchKeyword } = require('./search-keyword'); const { applyFilters } = require('./search-filters'); const { formatOutput } = require('../../utils/output-formatter-cli'); const { calculateScores, sortByScore } = require('../../utils/score-calculator'); /** * Create the search command * @returns {Command} Commander command instance */ function createSearchCommand() { const search = new Command('search'); search .description('Search for workers in the service registry') .argument('<query>', 'Search query (words, phrases, or patterns)') .option('-c, --category <category>', 'Filter by category') .option('-t, --tags <tags>', 'Filter by tags (comma-separated)') .option('-f, --format <format>', 'Output format: table, json, yaml', 'table') .option('-l, --limit <n>', 'Max results', '10') .option('--semantic', 'Force semantic search (requires OPENAI_API_KEY)') .option('--keyword', 'Force keyword search') .option('-v, --verbose', 'Show verbose output') .addHelpText('after', ` Examples: $ aios workers search "json csv" $ aios workers search "data transformation" --category=etl $ aios workers search "validation" --tags=schema,json $ aios workers search "api" --format=json --limit=5 $ aios workers search "transform" --semantic $ aios workers search "convert" --keyword `) .action(executeSearch); return search; } /** * Execute the search command * @param {string} query - Search query * @param {object} options - Command options */ async function executeSearch(query, options) { const startTime = Date.now(); try { // Validate query if (!query || query.trim().length === 0) { console.error('Error: Search query cannot be empty'); process.exit(1); } const trimmedQuery = query.trim(); const limit = parseInt(options.limit, 10) || 10; const tags = options.tags ? options.tags.split(',').map(t => t.trim()) : []; if (options.verbose) { console.log(`Searching for: "${trimmedQuery}"`); if (options.category) console.log(` Category filter: ${options.category}`); if (tags.length > 0) console.log(` Tag filters: ${tags.join(', ')}`); console.log(` Output format: ${options.format}`); console.log(` Limit: ${limit}`); console.log(''); } // Determine search strategy let results; let searchMethod; if (options.keyword) { // Force keyword search searchMethod = 'keyword'; results = await searchKeyword(trimmedQuery); } else if (options.semantic) { // Force semantic search if (!isSemanticAvailable()) { console.error('Error: Semantic search requires OPENAI_API_KEY environment variable'); console.log('Hint: Set OPENAI_API_KEY or use --keyword for fallback search'); process.exit(1); } searchMethod = 'semantic'; results = await searchSemantic(trimmedQuery); } else { // Auto-detect: use semantic if available, otherwise keyword if (isSemanticAvailable()) { searchMethod = 'semantic'; results = await searchSemantic(trimmedQuery); } else { searchMethod = 'keyword'; results = await searchKeyword(trimmedQuery); } } // Apply filters results = applyFilters(results, { category: options.category, tags: tags, }); // Calculate and sort by scores results = calculateScores(results, trimmedQuery); results = sortByScore(results); // Apply limit results = results.slice(0, limit); // Calculate duration const duration = ((Date.now() - startTime) / 1000).toFixed(1); // Format and output results const output = formatOutput(results, { format: options.format, query: trimmedQuery, duration: duration, searchMethod: searchMethod, verbose: options.verbose, }); console.log(output); // Performance check const durationMs = Date.now() - startTime; if (durationMs > 30000) { console.warn(`\nWarning: Search took ${(durationMs / 1000).toFixed(1)}s (target: < 30s)`); } } catch (error) { console.error(`Search error: ${error.message}`); if (options.verbose) { console.error(error.stack); } process.exit(1); } } module.exports = { createSearchCommand, executeSearch, };