trace.ai-cli
Version:
A powerful AI-powered CLI tool
713 lines (660 loc) • 27.3 kB
JavaScript
const os = require('os');
const { execSync } = require('child_process');
const si = require('systeminformation');
/**
* Get basic system information
* @returns {Object} System information object
*/
async function getBasicSystemInfo() {
try {
const [osInfo, system] = await Promise.all([
si.osInfo(),
si.system()
]);
return {
platform: os.platform(),
type: os.type(),
distro: osInfo.distro,
release: os.release(),
codename: osInfo.codename,
kernel: osInfo.kernel,
arch: os.arch(),
hostname: os.hostname(),
uptime: os.uptime(),
loadAvg: os.loadavg(),
manufacturer: system.manufacturer,
model: system.model,
serial: system.serial,
uuid: system.uuid,
sku: system.sku,
virtual: system.virtual,
userInfo: os.userInfo(),
tempDir: os.tmpdir()
};
} catch (error) {
console.error('Error getting basic system info:', error);
// Fallback to basic OS information
return {
platform: os.platform(),
type: os.type(),
release: os.release(),
arch: os.arch(),
hostname: os.hostname(),
uptime: os.uptime(),
loadAvg: os.loadavg(),
userInfo: os.userInfo(),
tempDir: os.tmpdir()
};
}
}
/**
* Get detailed CPU information
* @returns {Object} CPU information
*/
async function getCpuInfo() {
try {
const [cpu, cpuCurrentSpeed, cpuTemperature, currentLoad] = await Promise.all([
si.cpu(),
si.cpuCurrentSpeed(),
si.cpuTemperature(),
si.currentLoad()
]);
return {
manufacturer: cpu.manufacturer,
brand: cpu.brand,
vendor: cpu.vendor,
family: cpu.family,
model: cpu.model,
stepping: cpu.stepping,
revision: cpu.revision,
voltage: cpu.voltage,
speed: cpu.speed,
speedMin: cpu.speedMin,
speedMax: cpu.speedMax,
governor: cpu.governor,
cores: cpu.cores,
physicalCores: cpu.physicalCores,
processors: cpu.processors,
socket: cpu.socket,
flags: cpu.flags,
virtualization: cpu.virtualization,
cache: cpu.cache,
currentSpeed: cpuCurrentSpeed,
temperature: cpuTemperature,
load: {
currentLoad: currentLoad.currentLoad,
currentLoadUser: currentLoad.currentLoadUser,
currentLoadSystem: currentLoad.currentLoadSystem,
currentLoadNice: currentLoad.currentLoadNice,
currentLoadIdle: currentLoad.currentLoadIdle,
currentLoadIrq: currentLoad.currentLoadIrq,
rawCurrentLoad: currentLoad.rawCurrentLoad,
rawCurrentLoadUser: currentLoad.rawCurrentLoadUser,
rawCurrentLoadSystem: currentLoad.rawCurrentLoadSystem,
rawCurrentLoadNice: currentLoad.rawCurrentLoadNice,
rawCurrentLoadIdle: currentLoad.rawCurrentLoadIdle,
rawCurrentLoadIrq: currentLoad.rawCurrentLoadIrq
},
loadAvg: os.loadavg()
};
} catch (error) {
console.error('Error getting CPU info:', error);
// Fallback to basic CPU information
const cpus = os.cpus();
return {
model: cpus[0].model,
speed: cpus[0].speed,
cores: cpus.length,
loadAvg: os.loadavg()
};
}
}
/**
* Get memory information in a human-readable format
* @returns {Object} Memory information
*/
async function getMemoryInfo() {
try {
const [mem, memLayout] = await Promise.all([
si.mem(),
si.memLayout()
]);
// Convert to human-readable format
const totalMemGB = (mem.total / (1024 * 1024 * 1024)).toFixed(2);
const freeMemGB = (mem.free / (1024 * 1024 * 1024)).toFixed(2);
const usedMemGB = (mem.used / (1024 * 1024 * 1024)).toFixed(2);
const activeMemGB = (mem.active / (1024 * 1024 * 1024)).toFixed(2);
const availableMemGB = (mem.available / (1024 * 1024 * 1024)).toFixed(2);
const swapTotalGB = (mem.swaptotal / (1024 * 1024 * 1024)).toFixed(2);
const swapUsedGB = (mem.swapused / (1024 * 1024 * 1024)).toFixed(2);
const swapFreeGB = (mem.swapfree / (1024 * 1024 * 1024)).toFixed(2);
return {
total: `${totalMemGB} GB`,
free: `${freeMemGB} GB`,
used: `${usedMemGB} GB`,
active: `${activeMemGB} GB`,
available: `${availableMemGB} GB`,
usagePercent: `${mem.used / mem.total * 100}%`,
swap: {
total: `${swapTotalGB} GB`,
used: `${swapUsedGB} GB`,
free: `${swapFreeGB} GB`,
usagePercent: `${mem.swapused / mem.swaptotal * 100}%`
},
layout: memLayout.map(module => ({
size: `${(module.size / (1024 * 1024 * 1024)).toFixed(2)} GB`,
bank: module.bank,
type: module.type,
clockSpeed: `${module.clockSpeed} MHz`,
formFactor: module.formFactor,
manufacturer: module.manufacturer,
partNum: module.partNum,
serialNum: module.serialNum,
voltageConfigured: module.voltageConfigured,
voltageMin: module.voltageMin,
voltageMax: module.voltageMax
}))
};
} catch (error) {
console.error('Error getting memory info:', error);
// Fallback to basic memory information
const totalMemMB = Math.round(os.totalmem() / (1024 * 1024));
const freeMemMB = Math.round(os.freemem() / (1024 * 1024));
const usedMemMB = totalMemMB - freeMemMB;
const memUsagePercent = Math.round((usedMemMB / totalMemMB) * 100);
return {
total: `${totalMemMB} MB`,
free: `${freeMemMB} MB`,
used: `${usedMemMB} MB`,
usagePercent: `${memUsagePercent}%`
};
}
}
/**
* Get disk space information
* @returns {Object} Disk information
*/
async function getDiskInfo() {
try {
const [fsSize, blockDevices, diskLayout] = await Promise.all([
si.fsSize(),
si.blockDevices(),
si.diskLayout()
]);
// Format file system sizes
const formattedFsSize = fsSize.map(fs => ({
fs: fs.fs,
type: fs.type,
size: `${(fs.size / (1024 * 1024 * 1024)).toFixed(2)} GB`,
used: `${(fs.used / (1024 * 1024 * 1024)).toFixed(2)} GB`,
available: `${(fs.available / (1024 * 1024 * 1024)).toFixed(2)} GB`,
usagePercent: `${fs.use.toFixed(1)}%`,
mount: fs.mount
}));
// Format block devices info
const formattedBlockDevices = blockDevices.map(device => ({
name: device.name,
type: device.type,
fsType: device.fstype,
mount: device.mount,
size: device.size ? `${(device.size / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
physical: device.physical,
uuid: device.uuid,
label: device.label,
model: device.model,
serial: device.serial,
removable: device.removable,
protocol: device.protocol
}));
// Format disk layout info
const formattedDiskLayout = diskLayout.map(disk => ({
device: disk.device,
type: disk.type,
name: disk.name,
vendor: disk.vendor,
size: disk.size ? `${(disk.size / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
bytesPerSector: disk.bytesPerSector,
totalCylinders: disk.totalCylinders,
totalHeads: disk.totalHeads,
totalSectors: disk.totalSectors,
totalTracks: disk.totalTracks,
tracksPerCylinder: disk.tracksPerCylinder,
sectorsPerTrack: disk.sectorsPerTrack,
firmwareRevision: disk.firmwareRevision,
serialNum: disk.serialNum,
interfaceType: disk.interfaceType,
smartStatus: disk.smartStatus
}));
return {
fsSize: formattedFsSize,
blockDevices: formattedBlockDevices,
diskLayout: formattedDiskLayout
};
} catch (error) {
console.error('Error getting disk info:', error);
// Fallback to basic disk information using OS commands
try {
// This will work on macOS and Linux
if (os.platform() === 'darwin' || os.platform() === 'linux') {
const df = execSync('df -h /').toString();
const lines = df.trim().split('\n');
if (lines.length >= 2) {
const parts = lines[1].split(/\s+/);
return {
filesystem: parts[0],
size: parts[1],
used: parts[2],
available: parts[3],
usagePercent: parts[4],
mountedOn: parts[5]
};
}
} else if (os.platform() === 'win32') {
// For Windows, we would need a different approach
// This is a simplified version
const wmic = execSync('wmic logicaldisk get size,freespace,caption').toString();
const lines = wmic.trim().split('\n');
const disks = [];
for (let i = 1; i < lines.length; i++) {
const parts = lines[i].trim().split(/\s+/);
if (parts.length >= 3) {
const caption = parts[0];
const freeSpace = Math.round(parseInt(parts[1]) / (1024 * 1024 * 1024));
const size = Math.round(parseInt(parts[2]) / (1024 * 1024 * 1024));
const used = size - freeSpace;
const usagePercent = Math.round((used / size) * 100);
disks.push({
drive: caption,
size: `${size} GB`,
used: `${used} GB`,
free: `${freeSpace} GB`,
usagePercent: `${usagePercent}%`
});
}
}
return disks;
}
} catch (fallbackError) {
return { error: fallbackError.message };
}
return { error: 'Could not retrieve disk information' };
}
}
/**
* Get network information
* @returns {Object} Network interfaces and connection information
*/
async function getNetworkInfo() {
try {
const [networkInterfaces, networkStats, networkConnections, wifiNetworks, internetSpeed] = await Promise.all([
si.networkInterfaces(),
si.networkStats(),
si.networkConnections(),
si.wifiNetworks().catch(() => []), // This might fail on some systems
si.inetChecksite('https://www.google.com').catch(() => ({ ms: 'N/A' })) // Check internet connectivity
]);
// Format network interfaces
const formattedInterfaces = networkInterfaces.map(iface => ({
interface: iface.iface,
ifaceName: iface.ifaceName,
ip4: iface.ip4,
ip6: iface.ip6,
mac: iface.mac,
internal: iface.internal,
virtual: iface.virtual,
operstate: iface.operstate,
type: iface.type,
duplex: iface.duplex,
speed: iface.speed ? `${iface.speed} Mbps` : 'N/A',
dhcp: iface.dhcp,
dnsSuffix: iface.dnsSuffix,
ieee8021xAuth: iface.ieee8021xAuth,
ieee8021xState: iface.ieee8021xState,
carrierChanges: iface.carrierChanges
}));
// Format network stats
const formattedStats = networkStats.map(stat => ({
interface: stat.iface,
operstate: stat.operstate,
rx_bytes: formatBytes(stat.rx_bytes),
rx_dropped: stat.rx_dropped,
rx_errors: stat.rx_errors,
tx_bytes: formatBytes(stat.tx_bytes),
tx_dropped: stat.tx_dropped,
tx_errors: stat.tx_errors,
rx_sec: formatBytes(stat.rx_sec),
tx_sec: formatBytes(stat.tx_sec),
ms: stat.ms
}));
// Format wifi networks if available
const formattedWifiNetworks = wifiNetworks.map(network => ({
ssid: network.ssid,
bssid: network.bssid,
mode: network.mode,
channel: network.channel,
frequency: network.frequency,
signalLevel: network.signalLevel,
quality: network.quality,
security: network.security,
wpaFlags: network.wpaFlags,
rsnFlags: network.rsnFlags
}));
// Get public IP address if available
let publicIp = 'Not available';
try {
const externalIpResponse = await fetch('https://api.ipify.org?format=json')
.then(res => res.json())
.catch(() => ({ ip: 'Not available' }));
publicIp = externalIpResponse.ip;
} catch (error) {
console.error('Error getting public IP:', error);
}
return {
interfaces: formattedInterfaces,
stats: formattedStats,
connections: networkConnections.length, // Just count connections for privacy
wifiNetworks: formattedWifiNetworks,
internetLatency: `${internetSpeed.ms} ms`,
publicIp
};
} catch (error) {
console.error('Error getting network info:', error);
// Fallback to basic network information
const interfaces = os.networkInterfaces();
const result = [];
for (const [name, netInterface] of Object.entries(interfaces)) {
for (const info of netInterface) {
if (info.family === 'IPv4' || info.family === 4) {
result.push({
interface: name,
address: info.address,
netmask: info.netmask,
mac: info.mac,
internal: info.internal
});
}
}
}
return { interfaces: result };
}
}
/**
* Format bytes to human-readable format
* @param {number} bytes - Bytes to format
* @returns {string} Formatted string
*/
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Get process information
* @returns {Object} Process information
*/
async function getProcessInfo() {
try {
const [processes, currentProcess, services] = await Promise.all([
si.processes(),
si.processLoad(process.pid).catch(() => ({})), // Handle potential errors
si.services('*').catch(() => []) // This might fail on some systems
]);
// Format current process info
const formattedCurrentProcess = {
pid: process.pid,
ppid: process.ppid,
title: process.title,
name: currentProcess && currentProcess.name ? currentProcess.name : 'Unknown',
cpu: currentProcess && currentProcess.cpu !== undefined ? `${currentProcess.cpu.toFixed(1)}%` : 'N/A',
mem: currentProcess && currentProcess.mem !== undefined ? `${currentProcess.mem.toFixed(1)}%` : 'N/A',
arch: process.arch,
platform: process.platform,
version: process.version,
memoryUsage: {
rss: formatBytes(process.memoryUsage().rss),
heapTotal: formatBytes(process.memoryUsage().heapTotal),
heapUsed: formatBytes(process.memoryUsage().heapUsed),
external: formatBytes(process.memoryUsage().external),
arrayBuffers: formatBytes(process.memoryUsage().arrayBuffers || 0)
},
uptime: process.uptime(),
cwd: process.cwd(),
execPath: process.execPath
};
// Format top processes (limited to 10 for privacy and performance)
const topProcesses = processes && processes.list ?
processes.list
.sort((a, b) => (b.cpu || 0) - (a.cpu || 0))
.slice(0, 10)
.map(proc => ({
pid: proc.pid,
name: proc.name || 'Unknown',
cpu: proc.cpu !== undefined ? `${proc.cpu.toFixed(1)}%` : 'N/A',
mem: proc.mem !== undefined ? `${proc.mem.toFixed(1)}%` : 'N/A',
priority: proc.priority,
command: proc.command
})) : [];
// Format system services (limited to 10 for privacy and performance)
const formattedServices = services
.slice(0, 10)
.map(service => ({
name: service.name,
running: service.running,
startmode: service.startmode,
pids: service.pids
}));
return {
currentProcess: formattedCurrentProcess,
summary: {
all: processes.all,
running: processes.running,
blocked: processes.blocked,
sleeping: processes.sleeping
},
topProcesses,
services: formattedServices
};
} catch (error) {
console.error('Error getting process info:', error);
// Fallback to basic process information
return {
pid: process.pid,
ppid: process.ppid,
title: process.title,
arch: process.arch,
platform: process.platform,
version: process.version,
versions: process.versions,
memoryUsage: process.memoryUsage(),
uptime: process.uptime(),
cwd: process.cwd(),
execPath: process.execPath
};
}
}
/**
* Get environment variables and user information (filtered for security)
* @returns {Object} Environment variables and user information
*/
async function getEnvironmentInfo() {
try {
const [users, shellInfo] = await Promise.all([
si.users(),
si.shell()
]);
// Get safe environment variables
const env = process.env;
const safeEnv = {};
// Filter out sensitive information
const safeKeys = [
'PATH', 'LANG', 'TERM', 'SHELL', 'HOME', 'USER', 'LOGNAME',
'TMPDIR', 'TZ', 'EDITOR', 'PAGER', 'LSCOLORS', 'TERM_PROGRAM',
'TERM_PROGRAM_VERSION', 'COLORTERM', 'DISPLAY', 'LC_ALL', 'LC_CTYPE',
'SHLVL', 'PWD', 'XDG_DATA_DIRS', 'XDG_RUNTIME_DIR', 'DESKTOP_SESSION',
'GDMSESSION', 'XDG_CURRENT_DESKTOP', 'XDG_SESSION_TYPE', 'WAYLAND_DISPLAY'
];
for (const key of safeKeys) {
if (env[key]) {
safeEnv[key] = env[key];
}
}
// Format user information (anonymized for privacy)
const formattedUsers = users.map(user => ({
user: user.user,
tty: user.tty,
date: user.date,
time: user.time,
ip: user.ip,
command: user.command
}));
// Format shell history (limited for privacy)
// Check if shellInfo.history exists and is an array before using slice and map
let formattedShellHistory = [];
if (shellInfo && shellInfo.history && Array.isArray(shellInfo.history)) {
formattedShellHistory = shellInfo.history.slice(0, 5).map(cmd => ({
date: cmd.date || '',
time: cmd.time || '',
command: cmd.command || ''
}));
}
return {
variables: safeEnv,
users: formattedUsers,
shell: {
available: shellInfo && shellInfo.available ? shellInfo.available : false,
history: formattedShellHistory
}
};
} catch (error) {
console.error('Error getting environment info:', error);
// Fallback to basic environment information
const env = process.env;
const safeEnv = {};
// Filter out sensitive information
const safeKeys = [
'PATH', 'LANG', 'TERM', 'SHELL', 'HOME', 'USER', 'LOGNAME',
'TMPDIR', 'TZ', 'EDITOR', 'PAGER', 'LSCOLORS', 'TERM_PROGRAM'
];
for (const key of safeKeys) {
if (env[key]) {
safeEnv[key] = env[key];
}
}
return { variables: safeEnv };
}
}
/**
* Get system information based on query
* @param {string} query - The user's query
* @returns {Object} Relevant system information
*/
async function getSystemInfo(query = '') {
const lowerQuery = query.toLowerCase();
let result = {};
// Define common query patterns for natural language processing
const queryPatterns = {
cpu: [
'cpu', 'processor', 'core', 'thread', 'clock', 'speed', 'ghz', 'mhz',
'intel', 'amd', 'arm', 'load', 'usage', 'temperature', 'heat', 'cooling',
'performance', 'benchmark', 'processing', 'compute'
],
memory: [
'memory', 'ram', 'dram', 'dimm', 'stick', 'module', 'bank', 'slot',
'free', 'available', 'used', 'total', 'swap', 'virtual memory', 'page',
'allocation', 'heap', 'stack', 'buffer'
],
disk: [
'disk', 'drive', 'storage', 'hdd', 'ssd', 'nvme', 'partition', 'volume',
'filesystem', 'mount', 'space', 'capacity', 'free space', 'used space',
'read', 'write', 'io', 'block', 'sector', 'format', 'raid'
],
network: [
'network', 'internet', 'wifi', 'ethernet', 'connection', 'ip', 'address',
'mac', 'interface', 'adapter', 'wireless', 'wired', 'lan', 'wan', 'dns',
'gateway', 'router', 'subnet', 'mask', 'ping', 'latency', 'bandwidth',
'speed', 'download', 'upload', 'packet', 'traffic', 'port', 'socket'
],
process: [
'process', 'running', 'task', 'application', 'app', 'program', 'service',
'daemon', 'thread', 'job', 'pid', 'kill', 'terminate', 'start', 'stop',
'restart', 'crash', 'hang', 'freeze', 'responsive', 'background', 'foreground'
],
environment: [
'environment', 'env', 'variable', 'path', 'user', 'shell', 'terminal',
'console', 'profile', 'config', 'configuration', 'setting', 'preference',
'login', 'session', 'account', 'permission', 'access', 'privilege'
],
basic: [
'os', 'system', 'platform', 'version', 'release', 'build', 'kernel',
'distribution', 'distro', 'windows', 'mac', 'linux', 'unix', 'android',
'ios', 'architecture', 'arch', 'bit', '32-bit', '64-bit', 'x86', 'x64',
'arm', 'machine', 'hardware', 'device', 'model', 'manufacturer', 'vendor'
]
};
// Function to check if query matches any pattern in a category
const matchesCategory = (category) => {
return queryPatterns[category].some(pattern => lowerQuery.includes(pattern));
};
// Determine what information to return based on the query
if (lowerQuery.includes('all') || lowerQuery === '') {
// Get all information
const [basic, cpu, memory, disk, network, process, environment] = await Promise.all([
getBasicSystemInfo(),
getCpuInfo(),
getMemoryInfo(),
getDiskInfo(),
getNetworkInfo(),
getProcessInfo(),
getEnvironmentInfo()
]);
result = { basic, cpu, memory, disk, network, process, environment };
} else {
// Process each category based on natural language query
const categoriesToFetch = [];
// Check each category against the query
if (matchesCategory('cpu')) categoriesToFetch.push('cpu');
if (matchesCategory('memory')) categoriesToFetch.push('memory');
if (matchesCategory('disk')) categoriesToFetch.push('disk');
if (matchesCategory('network')) categoriesToFetch.push('network');
if (matchesCategory('process')) categoriesToFetch.push('process');
if (matchesCategory('environment')) categoriesToFetch.push('environment');
if (matchesCategory('basic')) categoriesToFetch.push('basic');
// If no specific category was matched, get basic info
if (categoriesToFetch.length === 0) {
categoriesToFetch.push('basic');
}
// Fetch all requested categories in parallel
const promises = [];
if (categoriesToFetch.includes('basic')) promises.push(getBasicSystemInfo());
if (categoriesToFetch.includes('cpu')) promises.push(getCpuInfo());
if (categoriesToFetch.includes('memory')) promises.push(getMemoryInfo());
if (categoriesToFetch.includes('disk')) promises.push(getDiskInfo());
if (categoriesToFetch.includes('network')) promises.push(getNetworkInfo());
if (categoriesToFetch.includes('process')) promises.push(getProcessInfo());
if (categoriesToFetch.includes('environment')) promises.push(getEnvironmentInfo());
const results = await Promise.all(promises);
// Assign results to the appropriate categories
let index = 0;
if (categoriesToFetch.includes('basic')) result.basic = results[index++];
if (categoriesToFetch.includes('cpu')) result.cpu = results[index++];
if (categoriesToFetch.includes('memory')) result.memory = results[index++];
if (categoriesToFetch.includes('disk')) result.disk = results[index++];
if (categoriesToFetch.includes('network')) result.network = results[index++];
if (categoriesToFetch.includes('process')) result.process = results[index++];
if (categoriesToFetch.includes('environment')) result.environment = results[index++];
}
return result;
}
module.exports = {
getSystemInfo,
getBasicSystemInfo,
getCpuInfo,
getMemoryInfo,
getDiskInfo,
getNetworkInfo,
getProcessInfo,
getEnvironmentInfo,
formatBytes
};