UNPKG

trace.ai-cli

Version:

A powerful AI-powered CLI tool

713 lines (660 loc) 27.3 kB
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 };