UNPKG

@ace-sdk/cli

Version:

ACE CLI - Command-line tool for intelligent pattern learning and playbook management

354 lines • 17.9 kB
/** * Tune command - Adjust ACE thresholds and settings */ import { createInterface } from 'readline'; import { globalOptions } from '../cli.js'; import { createContext } from '../types/config.js'; import { ACEServerClient } from '../services/server-client.js'; import { Logger } from '../services/logger.js'; import chalk from 'chalk'; /** * Tune command - Interactive or flags mode */ export async function tuneCommand(options = {}) { const logger = new Logger(globalOptions); // Check if any flags were provided (non-interactive mode) const hasFlags = Object.keys(options).length > 1 || (Object.keys(options).length === 1 && !options.scope); if (hasFlags) { // Flags mode (automation-friendly) return await tuneNonInteractive(options, logger); } else { // Interactive mode return await tuneInteractive(options, logger); } } /** * Non-interactive tune mode (flags) */ async function tuneNonInteractive(options, logger) { const spinner = logger.spinner('Updating configuration...'); try { const context = await createContext({ org: globalOptions.org, project: globalOptions.project }); const client = new ACEServerClient(context, logger); // Build settings object from flags using actual server field names const settings = {}; if (options.dedupSimilarityThreshold !== undefined) settings.dedup_similarity_threshold = options.dedupSimilarityThreshold; if (options.dedupEnabled !== undefined) settings.dedup_enabled = options.dedupEnabled; if (options.constitutionThreshold !== undefined) settings.constitution_threshold = options.constitutionThreshold; if (options.pruningThreshold !== undefined) settings.pruning_threshold = options.pruningThreshold; if (options.maxPlaybookTokens !== undefined) settings.max_playbook_tokens = options.maxPlaybookTokens; if (options.tokenBudgetEnforcement !== undefined) settings.token_budget_enforcement = options.tokenBudgetEnforcement; if (options.maxBatchSize !== undefined) settings.max_batch_size = options.maxBatchSize; if (options.autoLearningEnabled !== undefined) settings.auto_learning_enabled = options.autoLearningEnabled; if (options.reflectorEnabled !== undefined) settings.reflector_enabled = options.reflectorEnabled; if (options.curatorEnabled !== undefined) settings.curator_enabled = options.curatorEnabled; if (options.searchTopK !== undefined) settings.search_top_k = options.searchTopK; if (Object.keys(settings).length === 0) { throw new Error('No settings provided. Use --dedup-threshold, --constitution-threshold, etc.'); } const scope = options.scope || 'project'; const result = await client.updateConfig(settings, scope); spinner?.succeed('Configuration updated'); if (logger.isJson()) { logger.output({ success: true, scope, updated: settings, config: result }); } else { logger.info(chalk.green('\nāœ… Configuration updated successfully\n')); logger.info(chalk.bold('Updated Settings:')); for (const [key, value] of Object.entries(settings)) { logger.info(` ${chalk.cyan(key)}: ${value}`); } logger.info(''); } } catch (error) { spinner?.fail('Failed to update configuration'); if (logger.isJson()) { logger.error(error instanceof Error ? error.message : String(error)); } else { logger.error(chalk.red(`\nError: ${error instanceof Error ? error.message : String(error)}\n`)); } process.exit(1); } } /** * Interactive tune mode (wizard) */ async function tuneInteractive(_options, logger) { logger.info(chalk.bold('\nšŸŽ›ļø ACE Configuration Tuner\n')); const rl = createInterface({ input: process.stdin, output: process.stdout }); const question = (prompt) => { return new Promise((resolve) => rl.question(prompt, resolve)); }; try { const context = await createContext({ org: globalOptions.org, project: globalOptions.project }); const client = new ACEServerClient(context, logger); // Fetch current settings logger.info(chalk.dim('Fetching current configuration...')); const serverConfig = await client.getConfig(); const current = serverConfig || {}; logger.info(chalk.green('āœ“ Current configuration loaded\n')); // Display current effective values (actual server fields) logger.info(chalk.bold('Current Settings:')); logger.info(chalk.dim('────────────────────────────────────────────────────────────────')); logger.info(` Constitution Threshold: ${chalk.cyan(current.constitution_threshold ?? 0.7)} (retrieval similarity)`); logger.info(` Search Top K: ${chalk.cyan(current.search_top_k ?? 10)} (max results)`); logger.info(` Dedup Similarity Threshold: ${chalk.cyan(current.dedup_similarity_threshold ?? 0.85)}`); logger.info(` Dedup Enabled: ${chalk.cyan(current.dedup_enabled ?? true)}`); logger.info(` Pruning Threshold: ${chalk.cyan(current.pruning_threshold ?? 0.3)}`); logger.info(` Max Playbook Tokens: ${chalk.cyan(current.max_playbook_tokens ?? 'unlimited')}`); logger.info(` Token Budget Enforcement: ${chalk.cyan(current.token_budget_enforcement ?? false)}`); logger.info(` Max Batch Size: ${chalk.cyan(current.max_batch_size ?? 50)}`); logger.info(` Auto Learning Enabled: ${chalk.cyan(current.auto_learning_enabled ?? true)}`); logger.info(` Reflector Enabled: ${chalk.cyan(current.reflector_enabled ?? true)}`); logger.info(` Curator Enabled: ${chalk.cyan(current.curator_enabled ?? true)}`); logger.info(chalk.dim('────────────────────────────────────────────────────────────────\n')); // Ask which setting to change logger.info(chalk.bold('Which setting would you like to change?\n')); logger.info(' 1. Constitution Threshold (retrieval similarity, 0.0-1.0)'); logger.info(' 2. Search Top K (max search results, 1-100)'); logger.info(' 3. Dedup Similarity Threshold (0.0-1.0)'); logger.info(' 4. Pruning Threshold (min confidence to keep pattern)'); logger.info(' 5. Max Playbook Tokens (token budget limit)'); logger.info(' 6. Auto Learning Enabled (toggle automatic learning)'); logger.info(' 7. Max Batch Size (patterns per batch, 1-100)'); logger.info(' 8. Dedup Enabled (toggle deduplication)'); logger.info(' 9. Token Budget Enforcement (enforce token limits)'); logger.info(' A. Reflector/Curator Enabled (toggle agents)'); logger.info(' R. Reset to defaults'); logger.info(' 0. Cancel\n'); const choice = await question(chalk.cyan('Enter choice: ')); const settings = {}; switch (choice.trim().toLowerCase()) { case '1': { const value = await question(chalk.cyan(`Enter new constitution threshold (0.0-1.0, current: ${current.constitution_threshold ?? 0.7}): `)); const threshold = parseFloat(value); if (isNaN(threshold) || threshold < 0 || threshold > 1) { throw new Error('Invalid threshold. Must be between 0.0 and 1.0'); } settings.constitution_threshold = threshold; break; } case '2': { const value = await question(chalk.cyan(`Enter new search top K (1-100, current: ${current.search_top_k ?? 10}): `)); const topK = parseInt(value, 10); if (isNaN(topK) || topK < 1 || topK > 100) { throw new Error('Invalid top K. Must be between 1 and 100'); } settings.search_top_k = topK; break; } case '3': { const value = await question(chalk.cyan(`Enter new dedup similarity threshold (0.0-1.0, current: ${current.dedup_similarity_threshold ?? 0.85}): `)); const threshold = parseFloat(value); if (isNaN(threshold) || threshold < 0 || threshold > 1) { throw new Error('Invalid threshold. Must be between 0.0 and 1.0'); } settings.dedup_similarity_threshold = threshold; break; } case '4': { const value = await question(chalk.cyan(`Enter new pruning threshold (0.0-1.0, current: ${current.pruning_threshold ?? 0.3}): `)); const threshold = parseFloat(value); if (isNaN(threshold) || threshold < 0 || threshold > 1) { throw new Error('Invalid threshold. Must be between 0.0 and 1.0'); } settings.pruning_threshold = threshold; break; } case '5': { const value = await question(chalk.cyan(`Enter max playbook tokens (number or 'null' for unlimited, current: ${current.max_playbook_tokens ?? 'null'}): `)); if (value.trim().toLowerCase() === 'null') { settings.max_playbook_tokens = null; } else { const tokens = parseInt(value, 10); if (isNaN(tokens) || tokens < 1) { throw new Error('Invalid token count. Must be >= 1 or "null"'); } settings.max_playbook_tokens = tokens; } break; } case '6': { const value = await question(chalk.cyan(`Enable auto learning? (yes/no, current: ${current.auto_learning_enabled ?? true}): `)); settings.auto_learning_enabled = value.trim().toLowerCase() === 'yes'; break; } case '7': { const value = await question(chalk.cyan(`Enter max batch size (1-100, current: ${current.max_batch_size ?? 50}): `)); const size = parseInt(value, 10); if (isNaN(size) || size < 1 || size > 100) { throw new Error('Invalid batch size. Must be between 1 and 100'); } settings.max_batch_size = size; break; } case '8': { const value = await question(chalk.cyan(`Enable deduplication? (yes/no, current: ${current.dedup_enabled ?? true}): `)); settings.dedup_enabled = value.trim().toLowerCase() === 'yes'; break; } case '9': { const value = await question(chalk.cyan(`Enable token budget enforcement? (yes/no, current: ${current.token_budget_enforcement ?? false}): `)); settings.token_budget_enforcement = value.trim().toLowerCase() === 'yes'; break; } case 'a': { logger.info(chalk.bold('\nAgent Settings:\n')); const reflector = await question(chalk.cyan(`Enable Reflector? (yes/no, current: ${current.reflector_enabled ?? true}): `)); const curator = await question(chalk.cyan(`Enable Curator? (yes/no, current: ${current.curator_enabled ?? true}): `)); settings.reflector_enabled = reflector.trim().toLowerCase() === 'yes'; settings.curator_enabled = curator.trim().toLowerCase() === 'yes'; break; } case 'r': { const confirm = await question(chalk.yellow('\nāš ļø Reset ALL settings to defaults? (yes/no): ')); if (confirm.trim().toLowerCase() !== 'yes') { logger.info(chalk.dim('\nCancelled')); rl.close(); return; } logger.info(chalk.dim('\nResetting configuration...')); await client.resetConfig('project'); rl.close(); logger.info(chalk.green('\nāœ… Configuration reset to defaults\n')); return; } case '0': logger.info(chalk.dim('\nCancelled')); rl.close(); return; default: throw new Error('Invalid choice'); } rl.close(); if (Object.keys(settings).length === 0) { logger.info(chalk.dim('\nNo changes made')); return; } // Confirm and save logger.info(chalk.dim('\nSaving configuration...')); await client.updateConfig(settings, 'project'); logger.info(chalk.green('\nāœ… Configuration updated successfully\n')); logger.info(chalk.bold('Updated Settings:')); for (const [key, value] of Object.entries(settings)) { logger.info(` ${chalk.cyan(key)}: ${value}`); } logger.info(''); } catch (error) { rl.close(); if (logger.isJson()) { logger.error(error instanceof Error ? error.message : String(error)); } else { logger.error(chalk.red(`\nāŒ Error: ${error instanceof Error ? error.message : String(error)}\n`)); } process.exit(1); } } /** * Show current effective configuration */ export async function tuneShowCommand() { const logger = new Logger(globalOptions); try { const context = await createContext({ org: globalOptions.org, project: globalOptions.project }); const client = new ACEServerClient(context, logger); const serverConfig = await client.getConfig(); const config = serverConfig || {}; if (logger.isJson()) { logger.output({ project_id: context.projectId, org_id: context.orgId, config: config }); } else { logger.info(chalk.bold('\nšŸŽ›ļø ACE Configuration\n')); logger.info(chalk.dim(`Project: ${context.projectId}`)); logger.info(chalk.dim(`Organization: ${context.orgId}\n`)); logger.info(chalk.bold('Search/Retrieval:')); logger.info(` Constitution Threshold: ${chalk.cyan(config.constitution_threshold ?? 0.7)} (similarity)`); logger.info(` Search Top K: ${chalk.cyan(config.search_top_k ?? 10)} (max results)`); logger.info(chalk.bold('\nPattern Quality:')); logger.info(` Dedup Similarity: ${chalk.cyan(config.dedup_similarity_threshold ?? 0.85)}`); logger.info(` Pruning Threshold: ${chalk.cyan(config.pruning_threshold ?? 0.3)}`); logger.info(chalk.bold('\nToken Management:')); logger.info(` Max Playbook Tokens: ${chalk.cyan(config.max_playbook_tokens ?? 'unlimited')}`); logger.info(` Budget Enforcement: ${chalk.cyan(config.token_budget_enforcement ?? false)}`); logger.info(chalk.bold('\nProcessing:')); logger.info(` Dedup Enabled: ${chalk.cyan(config.dedup_enabled ?? true)}`); logger.info(` Max Batch Size: ${chalk.cyan(config.max_batch_size ?? 50)}`); logger.info(chalk.bold('\nACE Agents:')); logger.info(` Auto Learning: ${chalk.cyan(config.auto_learning_enabled ?? true)}`); logger.info(` Reflector: ${chalk.cyan(config.reflector_enabled ?? true)}`); logger.info(` Curator: ${chalk.cyan(config.curator_enabled ?? true)}`); logger.info(''); } } catch (error) { if (logger.isJson()) { logger.error(error instanceof Error ? error.message : String(error)); } else { logger.error(chalk.red(`\nError: ${error instanceof Error ? error.message : String(error)}\n`)); } process.exit(1); } } /** * Reset configuration to defaults */ export async function tuneResetCommand(options = {}) { const logger = new Logger(globalOptions); const spinner = logger.spinner('Resetting configuration...'); try { const context = await createContext({ org: globalOptions.org, project: globalOptions.project }); const client = new ACEServerClient(context, logger); const scope = options.scope || 'project'; const result = await client.resetConfig(scope); spinner?.succeed('Configuration reset'); if (logger.isJson()) { logger.output(result); } else { logger.info(chalk.green('\nāœ… Configuration reset to defaults\n')); logger.info(chalk.dim(`Scope: ${scope}`)); logger.info(chalk.dim('Run "ce-ace tune show" to see current settings\n')); } } catch (error) { spinner?.fail('Failed to reset configuration'); if (logger.isJson()) { logger.error(error instanceof Error ? error.message : String(error)); } else { logger.error(chalk.red(`\nError: ${error instanceof Error ? error.message : String(error)}\n`)); } process.exit(1); } } //# sourceMappingURL=tune.js.map