UNPKG

@xec-sh/cli

Version:

Xec: The Universal Shell for TypeScript

1,236 lines (1,235 loc) • 59.2 kB
import os from 'os'; import path from 'path'; import chalk from 'chalk'; import fs from 'fs-extra'; import { glob } from 'glob'; import { table } from 'table'; import { promisify } from 'util'; import { $ } from '@xec-sh/core'; import { fileURLToPath } from 'url'; import { exec } from 'child_process'; import { createRequire } from 'module'; import { select, confirm } from '@clack/prompts'; import { formatBytes } from '../utils/formatters.js'; import { TaskManager } from '../config/task-manager.js'; import { getModuleLoader } from '../utils/module-loader.js'; import { TargetResolver } from '../config/target-resolver.js'; import { discoverAllCommands } from '../utils/cli-command-manager.js'; import { BaseCommand } from '../utils/command-base.js'; import { ConfigurationManager } from '../config/configuration-manager.js'; import { VariableInterpolator } from '../config/variable-interpolator.js'; export class InspectCommand extends BaseCommand { constructor() { super({ name: 'inspect', aliases: ['i'], description: 'Inspect and analyze xec project configuration, tasks, and resources', arguments: '[type] [name]', options: [ { flags: '-f, --filter <pattern>', description: 'Filter results by pattern' }, { flags: '--format <format>', description: 'Output format (table, json, yaml, tree)', defaultValue: 'table' }, { flags: '-r, --resolve', description: 'Resolve and show interpolated values' }, { flags: '--validate', description: 'Validate configuration and connectivity' }, { flags: '-e, --explain', description: 'Show execution plan and details' }, { flags: '-p, --profile <name>', description: 'Use specific profile' }, ], examples: [ { command: 'xec inspect', description: 'Interactive mode to browse all resources' }, { command: 'xec inspect tasks', description: 'List all available tasks' }, { command: 'xec inspect targets', description: 'List all configured targets' }, { command: 'xec i system', description: 'Display system information' }, { command: 'xec inspect tasks deploy --explain', description: 'Show execution plan for deploy task' } ] }); } async execute(args) { const lastArg = args[args.length - 1]; const isCommand = lastArg && typeof lastArg === 'object' && lastArg.constructor && lastArg.constructor.name === 'Command'; const optionsArg = isCommand ? args[args.length - 2] : lastArg; const positionalArgs = isCommand ? args.slice(0, -2) : args.slice(0, -1); const [type, name] = positionalArgs; const options = { filter: optionsArg?.filter, format: optionsArg?.format || 'table', resolve: optionsArg?.resolve, validate: optionsArg?.validate, explain: optionsArg?.explain, profile: optionsArg?.profile, verbose: this.options.verbose, quiet: this.options.quiet, }; try { const inspector = new ProjectInspector(options); await inspector.initialize(); if (!type && !name) { await inspector.runInteractive(); } else { await inspector.inspect(type, name); } } catch (error) { console.error(chalk.red('Error:'), error.message); if (options?.verbose) { console.error(chalk.gray(error.stack)); } throw error; } } } class ProjectInspector { constructor(options = {}) { this.options = options; } async initialize() { this.configManager = new ConfigurationManager({ projectRoot: process.cwd(), profile: this.options.profile, }); await this.configManager.load(); const config = this.configManager.getConfig(); this.taskManager = new TaskManager({ configManager: this.configManager, debug: this.options.verbose, }); this.targetResolver = new TargetResolver(config, { autoDetect: true, }); this.variableInterpolator = new VariableInterpolator(this.configManager.getSecretManager()); await this.taskManager.load(); } async inspect(type, name) { const results = []; const inspectType = type || 'all'; if (inspectType === 'all' || inspectType === 'tasks') { results.push(...await this.inspectTasks(name)); } if (inspectType === 'all' || inspectType === 'targets') { results.push(...await this.inspectTargets(name)); } if (inspectType === 'all' || inspectType === 'vars') { results.push(...await this.inspectVariables(name)); } if (inspectType === 'all' || inspectType === 'scripts') { results.push(...await this.inspectScripts(name)); } if (inspectType === 'all' || inspectType === 'commands') { results.push(...await this.inspectCommands(name)); } if (inspectType === 'config') { results.push(...await this.inspectConfig(name)); } if (inspectType === 'all' || inspectType === 'system') { results.push(...await this.inspectSystem(name)); } if (inspectType === 'cache') { results.push(...await this.inspectCache(name)); } const filtered = this.applyFilter(results); this.displayResults(filtered, inspectType); } async runInteractive() { console.log(chalk.bold('\nšŸ” Xec Project Inspector\n')); while (true) { const choice = await select({ message: 'What would you like to inspect?', options: [ { value: 'tasks', label: 'šŸ“‹ Tasks - Executable tasks and workflows' }, { value: 'targets', label: 'šŸŽÆ Targets - Hosts, containers, and pods' }, { value: 'vars', label: 'šŸ“ Variables - Configuration variables' }, { value: 'scripts', label: 'šŸ“œ Scripts - JavaScript/TypeScript files' }, { value: 'commands', label: '⌘ Commands - Dynamic and built-in CLI commands' }, { value: 'config', label: 'āš™ļø Configuration - Full configuration tree' }, { value: 'system', label: 'šŸ’» System - Version and environment information' }, { value: 'cache', label: 'šŸ“¦ Cache - Module cache information' }, { value: 'validate', label: 'āœ… Validate - Check configuration and connectivity' }, { value: 'exit', label: 'āŒ Exit' }, ], }); if (choice === 'exit') break; if (choice === 'validate') { await this.runValidation(); continue; } await this.inspectInteractive(choice); } } async inspectTasks(name) { const tasks = await this.taskManager.list(); const results = []; for (const task of tasks) { if (name && task.name !== name) continue; const taskConfig = this.configManager.get(`tasks.${task.name}`); const result = { type: 'task', name: task.name, data: task, metadata: { source: 'config', hasSteps: task.hasSteps, hasScript: task.hasScript, isPrivate: task.isPrivate, }, }; if (this.options.explain && name) { const explanation = await this.taskManager.explain(task.name, {}); result.metadata['explanation'] = explanation; } if (this.options.resolve) { result.metadata['resolved'] = await this.resolveTaskVariables(taskConfig); } results.push(result); } return results; } async inspectTargets(name) { const results = []; const targets = this.configManager.get('targets') || {}; for (const [targetType, targetList] of Object.entries(targets)) { if (targetType === 'defaults') continue; for (const [targetName, targetConfig] of Object.entries(targetList)) { const fullName = `${targetType}.${targetName}`; if (name && fullName !== name && targetName !== name) continue; const result = { type: 'target', name: fullName, data: { type: targetType, name: targetName, config: targetConfig, }, metadata: { targetType, hasDefaults: !!targets.defaults, }, }; if (this.options.validate && name) { result.metadata['validation'] = await this.validateTarget(fullName); } results.push(result); } } return results; } async inspectVariables(name) { const vars = this.configManager.get('vars') || {}; const results = []; for (const [varName, varValue] of Object.entries(vars)) { if (name && varName !== name) continue; const result = { type: 'variable', name: varName, data: varValue, metadata: { type: typeof varValue, hasInterpolation: this.hasInterpolation(varValue), }, }; if (this.options.resolve) { try { result.metadata['resolved'] = await this.variableInterpolator.interpolateAsync(String(varValue), {}); } catch (error) { result.metadata['resolveError'] = error.message; } } results.push(result); } return results; } async inspectScripts(name) { const scriptsDir = path.join(process.cwd(), '.xec', 'scripts'); const results = []; if (!await fs.pathExists(scriptsDir)) { return results; } const pattern = name ? `**/${name}*` : '**/*'; const files = await glob(pattern, { cwd: scriptsDir, nodir: true, ignore: ['**/*.test.*', '**/*.spec.*'], }); for (const file of files) { if (!['.js', '.ts', '.mjs'].some(ext => file.endsWith(ext))) continue; const fullPath = path.join(scriptsDir, file); const result = { type: 'script', name: file, data: { path: fullPath, relativePath: file, }, metadata: { extension: path.extname(file), size: (await fs.stat(fullPath)).size, }, }; if (this.options.verbose) { const content = await fs.readFile(fullPath, 'utf-8'); const description = this.extractScriptDescription(content); if (description) { result.metadata['description'] = description; } } results.push(result); } return results; } async inspectCommands(name) { const results = []; const allCommands = await discoverAllCommands(); for (const cmd of allCommands) { if (name && cmd.name !== name) continue; results.push({ type: 'command', name: cmd.name, data: { type: cmd.type, description: cmd.description, path: cmd.path, loaded: cmd.loaded, error: cmd.error, }, metadata: { category: cmd.type === 'built-in' ? 'built-in' : 'custom', }, }); } return results; } async inspectConfig(path) { const config = this.configManager.getConfig(); if (path) { const value = this.configManager.get(path); return [{ type: 'config', name: path, data: value, metadata: { path, exists: value !== undefined, }, }]; } return [{ type: 'config', name: 'Full Configuration', data: config, metadata: { profile: this.configManager.getCurrentProfile(), }, }]; } async inspectCache(action) { const results = []; const loader = getModuleLoader({ verbose: false }); if (!action || action === 'stats') { const stats = await loader.getCacheStats(); results.push({ type: 'cache', name: 'stats', data: { memoryEntries: stats.memoryEntries, fileEntries: stats.fileEntries, totalSize: stats.totalSize, formattedSize: formatBytes(stats.totalSize), }, metadata: { category: 'statistics', cacheDir: path.join(os.homedir(), '.xec', 'module-cache'), }, }); } if (action === 'clear') { await loader.clearCache(); results.push({ type: 'cache', name: 'clear', data: { message: 'Module cache cleared successfully', }, metadata: { category: 'operation', timestamp: new Date().toISOString(), }, }); } return results; } async inspectSystem(category) { const results = []; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const require = createRequire(import.meta.url); if (!category || category === 'version') { const versionData = { xec: {}, node: {}, system: {}, }; try { const { readFileSync } = await import('fs'); const { join } = await import('path'); const cliPkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8')); versionData.xec.cli = cliPkg.version; versionData.xec.name = cliPkg.name; versionData.xec.description = cliPkg.description; } catch (e) { versionData.xec.cli = 'unknown'; } try { const corePath = path.dirname(require.resolve('@xec-sh/core')); const corePkgPath = path.join(corePath, '../package.json'); const corePkg = JSON.parse(await fs.readFile(corePkgPath, 'utf-8')); versionData.xec.core = corePkg.version; } catch { versionData.xec.core = 'not installed'; } versionData.node.version = process.version; versionData.node.modules = process.versions.modules; versionData.node.v8 = process.versions.v8; versionData.node.openssl = process.versions.openssl; results.push({ type: 'system', name: 'version', data: versionData, metadata: { category: 'version', }, }); } if (!category || category === 'os') { const osData = { platform: process.platform, release: os.release(), type: os.type(), arch: os.arch(), version: typeof os.version === 'function' ? os.version() : 'N/A', hostname: os.hostname(), uptime: os.uptime(), loadavg: os.loadavg(), }; const isTestEnv = process.env['NODE_ENV'] === 'test' || process.env['JEST_WORKER_ID'] !== undefined; if (!isTestEnv) { if (process.platform === 'darwin') { try { const result = await $ `sw_vers`; const lines = result.lines(); lines.forEach(line => { const [key, value] = line.split(':').map(s => s.trim()); if (key === 'ProductName') osData.productName = value; if (key === 'ProductVersion') osData.productVersion = value; if (key === 'BuildVersion') osData.buildVersion = value; }); } catch (error) { } } else if (process.platform === 'linux') { try { const osRelease = await fs.readFile('/etc/os-release', 'utf-8'); const lines = osRelease.split('\n'); lines.forEach(line => { const [key, value] = line.split('='); if (key === 'PRETTY_NAME') osData.distro = value?.replace(/"/g, ''); if (key === 'VERSION_ID') osData.distroVersion = value?.replace(/"/g, ''); }); } catch (error) { } } else if (process.platform === 'win32') { try { const result = await $ `wmic os get Caption,Version /value`; const lines = result.lines(); lines.forEach(line => { const [key, value] = line.split('='); if (key === 'Caption') osData.caption = value?.trim(); if (key === 'Version') osData.winVersion = value?.trim(); }); } catch (error) { } } } results.push({ type: 'system', name: 'os', data: osData, metadata: { category: 'operating-system', }, }); } if (!category || category === 'hardware') { const memoryInfo = await this.getMemoryInfo(); const hardwareData = { cpus: os.cpus().map(cpu => ({ model: cpu.model, speed: cpu.speed, })), cpuCount: os.cpus().length, memory: memoryInfo, endianness: os.endianness(), }; results.push({ type: 'system', name: 'hardware', data: hardwareData, metadata: { category: 'hardware', }, }); } if (!category || category === 'environment') { const envData = { user: os.userInfo(), shell: process.env['SHELL'] || 'unknown', home: os.homedir(), tmpdir: os.tmpdir(), path: process.env['PATH']?.split(path.delimiter).slice(0, 10), nodeEnv: process.env['NODE_ENV'] || 'development', xecEnv: { XEC_HOME: process.env['XEC_HOME'], XEC_DEBUG: process.env['XEC_DEBUG'], XEC_PROFILE: process.env['XEC_PROFILE'], }, }; results.push({ type: 'system', name: 'environment', data: envData, metadata: { category: 'environment', }, }); } if (!category || category === 'network') { const networkData = { interfaces: {}, }; const nets = os.networkInterfaces(); for (const name of Object.keys(nets)) { const netInfo = nets[name]; if (netInfo) { networkData.interfaces[name] = netInfo .filter(net => !net.internal) .map(net => ({ address: net.address, family: net.family, mac: net.mac, })); } } results.push({ type: 'system', name: 'network', data: networkData, metadata: { category: 'network', }, }); } if (!category || category === 'tools') { const toolsData = { installed: {}, versions: {}, }; const isTestEnv = process.env['NODE_ENV'] === 'test' || process.env['JEST_WORKER_ID'] !== undefined; if (!isTestEnv) { const tools = [ { name: 'node', cmd: 'node --version' }, { name: 'bun', cmd: 'bun --version' }, { name: 'deno', cmd: 'deno --version' }, { name: 'git', cmd: 'git --version' }, { name: 'docker', cmd: 'docker --version' }, { name: 'kubectl', cmd: 'kubectl version --client' }, { name: 'npm', cmd: 'npm --version' }, { name: 'yarn', cmd: 'yarn --version' }, { name: 'pnpm', cmd: 'pnpm --version' }, { name: 'python', cmd: 'python3 --version' }, { name: 'go', cmd: 'go version' }, { name: 'rust', cmd: 'rustc --version' }, { name: 'java', cmd: 'java -version 2>&1' }, ]; for (const tool of tools) { try { const result = await $.cd(os.homedir()).raw `${tool.cmd}`; toolsData.versions[tool.name] = result.lines().join('; '); toolsData.installed[tool.name] = true; } catch (error) { toolsData.installed[tool.name] = false; } } } else { toolsData.installed.node = true; toolsData.versions.node = process.version; toolsData.installed.npm = true; toolsData.versions.npm = '10.0.0'; } results.push({ type: 'system', name: 'tools', data: toolsData, metadata: { category: 'development-tools', }, }); } if (!category || category === 'project') { const projectData = { workingDirectory: process.cwd(), projectRoot: process.cwd(), configFiles: {}, }; const configFiles = [ '.xec/config.yaml', '.xec/config.json', 'package.json', 'tsconfig.json', 'Dockerfile', 'docker-compose.yml', '.gitignore', ]; for (const file of configFiles) { const filePath = path.join(process.cwd(), file); projectData.configFiles[file] = await fs.pathExists(filePath); } try { const pkgPath = path.join(process.cwd(), 'package.json'); if (await fs.pathExists(pkgPath)) { const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8')); projectData.package = { name: pkg.name, version: pkg.version, description: pkg.description, scripts: Object.keys(pkg.scripts || {}), }; } } catch { } results.push({ type: 'system', name: 'project', data: projectData, metadata: { category: 'project', }, }); } return results; } async inspectInteractive(type) { const results = await this.getInspectionResults(type); if (results.length === 0) { console.log(chalk.yellow('\nNo items found.\n')); return; } const choices = results.map(r => ({ value: r, label: this.formatItemLabel(r), })); const selected = await select({ message: `Select ${type} to inspect:`, options: choices, }); if (selected && typeof selected !== 'symbol') { this.displayDetailedResult(selected); if (this.options.explain && selected.type === 'task') { const explain = await confirm({ message: 'Show execution plan?', }); if (explain) { await this.showTaskExplanation(selected.name); } } } } async getInspectionResults(type) { switch (type) { case 'tasks': return this.inspectTasks(); case 'targets': return this.inspectTargets(); case 'vars': return this.inspectVariables(); case 'scripts': return this.inspectScripts(); case 'commands': return this.inspectCommands(); case 'config': return this.inspectConfig(); case 'system': return this.inspectSystem(); case 'cache': return this.inspectCache(); default: return []; } } displayResults(results, type) { if (results.length === 0) { console.log(chalk.yellow('No items found.')); return; } if (type === 'system' && this.options.explain) { for (const result of results) { this.displayDetailedResult(result); } return; } switch (this.options.format) { case 'json': console.log(JSON.stringify(results, null, 2)); break; case 'yaml': for (const result of results) { console.log(`${result.name}:`); this.printYaml(result.data, 2); if (result.metadata && this.options.verbose) { console.log(' metadata:'); this.printYaml(result.metadata, 4); } console.log(); } break; case 'tree': this.displayTree(results); break; default: this.displayTable(results, type); } } displayTable(results, type) { const headers = this.getTableHeaders(type); const data = [headers]; for (const result of results) { data.push(this.formatTableRow(result, type)); } const config = { border: { topBody: `─`, topJoin: `┬`, topLeft: `ā”Œ`, topRight: `┐`, bottomBody: `─`, bottomJoin: `┓`, bottomLeft: `ā””`, bottomRight: `ā”˜`, bodyLeft: `│`, bodyRight: `│`, bodyJoin: `│`, joinBody: `─`, joinLeft: `ā”œ`, joinRight: `┤`, joinJoin: `┼` } }; console.log(table(data, config)); if (this.options.verbose) { console.log(chalk.dim(`\nTotal: ${results.length} ${type === 'all' ? 'items' : type}`)); } } displayTree(results) { const grouped = this.groupByType(results); for (const [type, items] of Object.entries(grouped)) { console.log(chalk.bold.cyan(`\n${type}:`)); for (const item of items) { console.log(` ā”œā”€ ${this.formatTreeItem(item)}`); if (this.options.verbose && item.metadata) { for (const [key, value] of Object.entries(item.metadata)) { console.log(` │ └─ ${chalk.dim(key)}: ${chalk.gray(this.formatValue(value))}`); } } } } } displayDetailedResult(result) { console.log(chalk.bold(`\n${result.type.toUpperCase()}: ${result.name}\n`)); if (result.type === 'system') { this.displaySystemDetails(result); return; } if (result.type === 'cache') { this.displayCacheDetails(result); return; } if (typeof result.data === 'object') { for (const [key, value] of Object.entries(result.data)) { console.log(`${chalk.cyan(key)}: ${this.formatValue(value)}`); } } else { console.log(this.formatValue(result.data)); } if (result.metadata && Object.keys(result.metadata).length > 0) { console.log(chalk.bold('\nMetadata:')); for (const [key, value] of Object.entries(result.metadata)) { console.log(`${chalk.cyan(key)}: ${this.formatValue(value)}`); } } console.log(); } displaySystemDetails(result) { const data = result.data; switch (result.name) { case 'version': console.log(chalk.bold('Xec:')); console.log(` ${chalk.cyan('CLI:')} ${data.xec.cli}`); console.log(` ${chalk.cyan('Core:')} ${data.xec.core}`); console.log(` ${chalk.cyan('Name:')} ${data.xec.name}`); console.log(` ${chalk.cyan('Description:')} ${data.xec.description}`); console.log(chalk.bold('\nNode.js:')); console.log(` ${chalk.cyan('Version:')} ${data.node.version}`); console.log(` ${chalk.cyan('V8:')} ${data.node.v8}`); console.log(` ${chalk.cyan('OpenSSL:')} ${data.node.openssl}`); console.log(` ${chalk.cyan('Modules:')} ${data.node.modules}`); break; case 'os': console.log(`${chalk.cyan('Platform:')} ${data.platform}`); console.log(`${chalk.cyan('Type:')} ${data.type}`); console.log(`${chalk.cyan('Release:')} ${data.release}`); console.log(`${chalk.cyan('Architecture:')} ${data.arch}`); console.log(`${chalk.cyan('Hostname:')} ${data.hostname}`); console.log(`${chalk.cyan('Uptime:')} ${this.formatUptime(data.uptime)}`); console.log(`${chalk.cyan('Load Average:')} ${data.loadavg.map((n) => n.toFixed(2)).join(', ')}`); if (data.productName) { console.log(chalk.bold('\nmacOS:')); console.log(` ${chalk.cyan('Product:')} ${data.productName}`); console.log(` ${chalk.cyan('Version:')} ${data.productVersion}`); console.log(` ${chalk.cyan('Build:')} ${data.buildVersion}`); } else if (data.distro) { console.log(chalk.bold('\nLinux:')); console.log(` ${chalk.cyan('Distribution:')} ${data.distro}`); console.log(` ${chalk.cyan('Version:')} ${data.distroVersion || 'N/A'}`); } else if (data.caption) { console.log(chalk.bold('\nWindows:')); console.log(` ${chalk.cyan('Caption:')} ${data.caption}`); console.log(` ${chalk.cyan('Version:')} ${data.winVersion}`); } break; case 'hardware': { console.log(chalk.bold('CPUs:')); const cpusByModel = data.cpus.reduce((acc, cpu) => { if (!acc[cpu.model]) acc[cpu.model] = 0; acc[cpu.model]++; return acc; }, {}); for (const [model, count] of Object.entries(cpusByModel)) { console.log(` ${chalk.cyan(count + 'x')} ${model}`); } console.log(chalk.bold('\nMemory:')); console.log(` ${chalk.cyan('Total:')} ${this.formatFileSize(data.memory.total)}`); console.log(` ${chalk.cyan('Available:')} ${this.formatFileSize(data.memory.available)} (${data.memory.availablePercent}%)`); console.log(` ${chalk.cyan('Used:')} ${this.formatFileSize(data.memory.used)} (${data.memory.usagePercent}%)`); if (data.memory.wired) { console.log(` ${chalk.cyan('Wired:')} ${this.formatFileSize(data.memory.wired)}`); console.log(` ${chalk.cyan('Active:')} ${this.formatFileSize(data.memory.active)}`); console.log(` ${chalk.cyan('Inactive:')} ${this.formatFileSize(data.memory.inactive)}`); console.log(` ${chalk.cyan('Compressed:')} ${this.formatFileSize(data.memory.compressed)}`); } console.log(` ${chalk.cyan('Endianness:')} ${data.endianness}`); break; } case 'environment': console.log(chalk.bold('User:')); console.log(` ${chalk.cyan('Username:')} ${data.user.username}`); console.log(` ${chalk.cyan('UID:')} ${data.user.uid}`); console.log(` ${chalk.cyan('GID:')} ${data.user.gid}`); console.log(` ${chalk.cyan('Home:')} ${data.user.homedir}`); console.log(` ${chalk.cyan('Shell:')} ${data.user.shell}`); console.log(chalk.bold('\nPaths:')); console.log(` ${chalk.cyan('Home:')} ${data.home}`); console.log(` ${chalk.cyan('Temp:')} ${data.tmpdir}`); console.log(` ${chalk.cyan('Shell:')} ${data.shell}`); console.log(chalk.bold('\nEnvironment:')); console.log(` ${chalk.cyan('NODE_ENV:')} ${data.nodeEnv}`); if (data.xecEnv.XEC_HOME) console.log(` ${chalk.cyan('XEC_HOME:')} ${data.xecEnv.XEC_HOME}`); if (data.xecEnv.XEC_DEBUG) console.log(` ${chalk.cyan('XEC_DEBUG:')} ${data.xecEnv.XEC_DEBUG}`); if (data.xecEnv.XEC_PROFILE) console.log(` ${chalk.cyan('XEC_PROFILE:')} ${data.xecEnv.XEC_PROFILE}`); console.log(chalk.bold('\nPATH (first 10):')); data.path.forEach((p, i) => { console.log(` ${i + 1}. ${p}`); }); break; case 'network': for (const [name, interfaces] of Object.entries(data.interfaces)) { if (interfaces.length > 0) { console.log(chalk.bold(`${name}:`)); interfaces.forEach(iface => { console.log(` ${chalk.cyan(iface.family)}: ${iface.address}`); if (iface.mac !== '00:00:00:00:00:00') { console.log(` ${chalk.cyan('MAC')}: ${iface.mac}`); } }); console.log(); } } break; case 'tools': for (const [tool, installed] of Object.entries(data.installed)) { if (installed) { console.log(` ${chalk.green('āœ“')} ${chalk.cyan(tool)}: ${data.versions[tool]}`); } else { console.log(` ${chalk.red('āœ—')} ${chalk.dim(tool)}`); } } break; case 'project': console.log(`${chalk.cyan('Working Directory:')} ${data.workingDirectory}`); console.log(`${chalk.cyan('Project Root:')} ${data.projectRoot}`); if (data.package) { console.log(chalk.bold('\nPackage:')); console.log(` ${chalk.cyan('Name:')} ${data.package.name}`); console.log(` ${chalk.cyan('Version:')} ${data.package.version}`); if (data.package.description) { console.log(` ${chalk.cyan('Description:')} ${data.package.description}`); } if (data.package.scripts.length > 0) { console.log(` ${chalk.cyan('Scripts:')} ${data.package.scripts.join(', ')}`); } } console.log(chalk.bold('\nConfiguration Files:')); for (const [file, exists] of Object.entries(data.configFiles)) { console.log(` ${exists ? chalk.green('āœ“') : chalk.red('āœ—')} ${file}`); } break; default: for (const [key, value] of Object.entries(data)) { console.log(`${chalk.cyan(key)}: ${this.formatValue(value)}`); } } console.log(); } displayCacheDetails(result) { const data = result.data; switch (result.name) { case 'stats': console.log(chalk.bold('Module Cache Statistics:')); console.log(` ${chalk.cyan('Memory Entries:')} ${data.memoryEntries}`); console.log(` ${chalk.cyan('File Entries:')} ${data.fileEntries}`); console.log(` ${chalk.cyan('Total Size:')} ${data.formattedSize}`); console.log(); console.log(` ${chalk.dim('Cache Directory:')} ${result.metadata?.['cacheDir']}`); break; case 'clear': console.log(chalk.green('āœ” ') + data.message); console.log(` ${chalk.dim('Timestamp:')} ${result.metadata?.['timestamp']}`); break; default: for (const [key, value] of Object.entries(data)) { console.log(`${chalk.cyan(key)}: ${this.formatValue(value)}`); } } } formatUptime(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); const parts = []; if (days > 0) parts.push(`${days}d`); if (hours > 0) parts.push(`${hours}h`); if (minutes > 0) parts.push(`${minutes}m`); return parts.length > 0 ? parts.join(' ') : '< 1m'; } async showTaskExplanation(taskName) { const explanation = await this.taskManager.explain(taskName, {}); console.log(chalk.bold('\nTask Execution Plan:\n')); for (const line of explanation) { if (line === '') { console.log(); } else if (line.startsWith('Task:') || line.startsWith('Parameters:') || line.startsWith('Execution plan:') || line.startsWith('Target')) { console.log(chalk.bold.cyan(line)); } else if (line.match(/^\s*\d+\./)) { console.log(chalk.green(line)); } else if (line.match(/^\s{2,}/)) { console.log(chalk.gray(line)); } else { console.log(line); } } } async runValidation() { console.log(chalk.bold('\nšŸ” Running Configuration Validation...\n')); console.log(chalk.cyan('Configuration Syntax:')); const configValid = await this.validateConfiguration(); console.log(configValid ? chalk.green(' āœ“ Valid') : chalk.red(' āœ— Invalid')); console.log(chalk.cyan('\nTarget Connectivity:')); const targets = await this.inspectTargets(); for (const target of targets) { const validation = await this.validateTarget(target.name); const status = validation.valid ? chalk.green('āœ“') : chalk.red('āœ—'); console.log(` ${status} ${target.name} - ${validation.message}`); } console.log(chalk.cyan('\nVariable Resolution:')); const vars = await this.inspectVariables(); let varErrors = 0; for (const variable of vars) { if (variable.metadata?.['hasInterpolation']) { try { await this.variableInterpolator.interpolateAsync(String(variable.data), {}); } catch (error) { varErrors++; console.log(` ${chalk.red('āœ—')} ${variable.name} - ${error instanceof Error ? error.message : String(error)}`); } } } if (varErrors === 0) { console.log(chalk.green(' āœ“ All variables resolve correctly')); } console.log(chalk.cyan('\nTask Definitions:')); const tasks = await this.inspectTasks(); let taskErrors = 0; for (const task of tasks) { const validation = await this.validateTask(task.name); if (!validation.valid) { taskErrors++; console.log(` ${chalk.red('āœ—')} ${task.name} - ${validation.message}`); } } if (taskErrors === 0) { console.log(chalk.green(' āœ“ All tasks are valid')); } console.log(); } applyFilter(results) { if (!this.options.filter) return results; const pattern = new RegExp(this.options.filter, 'i'); return results.filter(r => pattern.test(r.name) || pattern.test(JSON.stringify(r.data))); } hasInterpolation(value) { if (typeof value !== 'string') return false; return /\$\{[^}]+\}/.test(value); } async resolveTaskVariables(task) { if (!task) return task; try { return await this.variableInterpolator.interpolateAsync(JSON.stringify(task), {}); } catch (error) { return { error: error instanceof Error ? error.message : String(error) }; } } async validateTarget(targetName) { try { const target = await this.targetResolver.resolve(targetName); return { valid: true, message: `Type: ${target.type}, configured correctly` }; } catch (error) { return { valid: false, message: error.message }; } } async validateConfiguration() { try { const config = this.configManager.getConfig(); return !!config; } catch { return false; } } async validateTask(taskName) { try { const task = this.configManager.get(`tasks.${taskName}`); if (!task) { return { valid: false, message: 'Task not found' }; } if (typeof task === 'object' && task.target) { await this.targetResolver.resolve(task.target); } return { valid: true, message: 'Valid' }; } catch (error) { return { valid: false, message: error.message }; } } extractScriptDescription(content) { const lines = content.split('\n').slice(0, 20); for (const line of lines) { const match = line.match(/^\s*(\/\/|\/\*\*?|#)\s*@?[Dd]escription:?\s*(.+)$/); if (match && match[2]) { return match[2].trim(); } } return null; } getTableHeaders(type) { switch (type) { case 'tasks': return ['Name', 'Type', 'Description', 'Parameters']; case 'targets': return ['Name', 'Type', 'Details']; case 'vars': return ['Name', 'Value', 'Type']; case 'scripts': return ['Name', 'Path', 'Size']; case 'commands': return ['Name', 'Type', 'Description']; case 'system': return ['Category', 'Component', 'Value']; case 'cache': return ['Type', 'Metric', 'Value']; default: return ['Type', 'Name', 'Details']; } } formatTableRow(result, tableType) { if (tableType === 'all') { return [ result.type, chalk.cyan(result.name), this.formatValue(result.data, 50), ]; } switch (result.type) { case 'task': return [ chalk.cyan(result.name), result.data?.hasSteps ? 'Pipeline' : result.data?.hasScript ? 'Script' : 'Command', result.data?.description || chalk.dim('No description'), result.data?.params?.length > 0 ? result.data.params.map((p) => p.name).join(', ') : chalk.dim('None'), ]; case 'target': return [ chalk.cyan(result.name), result.data?.type || 'unknown', this.formatTargetDetails(result.data), ]; case 'variable': return [ chalk.cyan(result.name), this.formatValue(result.data, 50), result.metadata?.['type'] || 'unknown', ]; case 'script': return [ chalk.cyan(result.name), chalk.dim(result.data?.relativePath || ''), this.formatFileSize(result.metadata?.['size'] || 0), ]; case 'command': return [ chalk.cyan(result.name), result.data?.type || 'unknown', result.data?.description || chalk.dim('No description'), ]; case 'system': return this.formatSystemTableRow(result); case 'cache': return this.formatCacheTableRow(result); default: return [ result.type, chalk.cyan(result.name), this.formatValue(result.data, 50), ]; } } formatCacheTableRow(result) { const data = result.data; switch (result.name) { case 'stats': return [ chalk.cyan('Statistics'), 'Module Cache', `${data.fileEntries} files (${data.formattedSize})`, ]; case 'clear': return [ chalk.cyan('Operation'), 'Clear Cache', data.message, ]; default: return [ chalk.cyan('Cache'), result.name, this.formatValue(data, 50), ]; } } formatSystemTableRow(result) { const category = result.metadata?.['category'] || result.name; const data = result.data; switch (result.name) { case 'version': return [ chalk.cyan('Version'), 'Xec CLI', `${data.xec.cli} (core: ${data.xec.core})`, ]; case 'os': { const osDesc = data.productName || data.distro || data.caption || data.type; return [ chalk.cyan('OS'),