@ace-sdk/cli
Version:
ACE CLI - Command-line tool for intelligent pattern learning and playbook management
354 lines ⢠17.9 kB
JavaScript
/**
* 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