@xec-sh/cli
Version:
Xec: The Universal Shell for TypeScript
1,236 lines (1,235 loc) ⢠59.2 kB
JavaScript
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'),