about-system
Version:
A Node.js script to display key system information with emojis. Cross-platform support for Windows, macOS, and Linux with customizable output and caching.
1,522 lines (1,331 loc) • 53 kB
JavaScript
#!/usr/bin/env node
/**
* =========================================================
* System Info with Emojis - Node.js Version
* =========================================================
* Node.js script to display key system information with emojis
* The output and order are fully customizable: edit DISPLAY_ORDER
* to control which info blocks are shown and in what order.
* Supported blocks: user, hostname, ip, iplocal, city, domain, isp, os,
* cpu, gpu, disk_used, ram_used, top_process, device, kernel, uptime,
* shell, pacman, ports, containers, memory_available, swap_used,
* load_average, users_logged_in, network_interfaces, mount_points,
* services_running, temperature, battery, screen_resolution.
* Network info (ip, city, domain, isp) is fetched from ipinfo.io
* only if needed.
*
* @author: vtempest
* @license: MIT
*/
import os from 'os';
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import https from 'https';
// Cache configuration
const CACHE_FILE = path.join(os.tmpdir(), 'systeminfo-cache.json');
const SETTINGS_FILE = path.join(os.homedir(), '.config', 'systeminfo-settings.json');
const CACHE_DURATION = {
// Cache durations in milliseconds
ip: 5 * 60 * 1000, // 5 minutes for IP info
cpu: 24 * 60 * 60 * 1000, // 24 hours for CPU info
gpu: 24 * 60 * 60 * 1000, // 24 hours for GPU info
os: 24 * 60 * 60 * 1000, // 24 hours for OS info
device: 24 * 60 * 60 * 1000, // 24 hours for device info
kernel: 60 * 60 * 1000, // 1 hour for kernel (can change on updates)
pacman: 10 * 60 * 1000, // 10 minutes for installed packages
ports: 30 * 1000, // 30 seconds for ports (changes frequently)
containers: 30 * 1000, // 30 seconds for containers
top_process: 5 * 1000, // 5 seconds for top process
disk_used: 60 * 1000, // 1 minute for disk usage
ram_used: 10 * 1000, // 10 seconds for RAM usage
services_running: 30 * 1000, // 30 seconds for services
temperature: 30 * 1000, // 30 seconds for temperature
battery: 60 * 1000, // 1 minute for battery
network_interfaces: 5 * 60 * 1000, // 5 minutes for network interfaces
mount_points: 10 * 60 * 1000 // 10 minutes for mount points
};
// Default settings
const DEFAULT_SETTINGS = {
display_order: [
['user', 'hostname', 'os', 'device', 'kernel', 'cpu', 'gpu', ],
['disk_used', 'ram_used', 'top_process', 'uptime', 'temperature', 'battery', 'load_average'],
['ip', 'iplocal', 'city', 'domain', 'isp'],
['shell', 'pacman', 'ports', 'services_running', 'containers']
],
colors: {
user: "red",
hostname: "orange",
disk_used: "purple",
ram_used: "yellow",
top_process: "magenta",
uptime: "cyan",
ip: "green",
iplocal: "yellow",
city: "green",
domain: "gray",
isp: "lightblue",
os: "gray",
cpu: "orange",
gpu: "yellow",
device: "yellow",
kernel: "green",
shell: "orange",
pacman: "multicolor",
ports: "multicolor",
containers: "green",
memory_available: "blue",
swap_used: "purple",
load_average: "red",
users_logged_in: "cyan",
network_interfaces: "yellow",
mount_points: "gray",
services_running: "green",
temperature: "red",
battery: "green",
screen_resolution: "blue"
},
cache: {
enabled: true,
custom_durations: {}
},
network: {
timeout: 5000,
ipinfo_token: "da2d6cc4baa5d1",
show_offline_message: true
},
display: {
show_emojis: true,
compact_mode: false,
separator: "\n",
multiline: true,
group_similar: true,
single_line: false,
line_wrap_length: process?.stdout?.columns || 100, // fallback to 80 if tput fails
},
advanced: {
debug: false,
performance_logging: false,
fallback_commands: true
}
};
// Color codes for terminal output
const colors = {
reset: '\x1b[0m',
red: '\x1b[38;5;196m',
orange: '\x1b[38;5;208m',
yellow: '\x1b[38;5;226m',
green: '\x1b[38;5;46m',
blue: '\x1b[38;5;39m',
cyan: '\x1b[38;5;51m',
purple: '\x1b[38;5;171m',
magenta: '\x1b[38;5;213m',
gray: '\x1b[38;5;250m',
lightblue: '\x1b[38;5;220m'
};
// Platform detection constants
const IS_WINDOWS = os.platform() === 'win32';
const IS_MAC = os.platform() === 'darwin';
const IS_LINUX = os.platform() === 'linux';
// Cache management functions
function loadCache() {
try {
if (fs.existsSync(CACHE_FILE)) {
const cacheData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
return cacheData;
}
} catch (error) {
// If cache is corrupted, ignore it
}
return {};
}
function saveCache(cache) {
try {
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
} catch (error) {
// Silently fail if can't write cache
}
}
function isCacheValid(cacheEntry, key) {
if (!cacheEntry || !cacheEntry.timestamp) return false;
const age = Date.now() - cacheEntry.timestamp;
const maxAge = CACHE_DURATION[key] || 60000; // Default 1 minute
return age < maxAge;
}
// Cache helper functions
function getCachedValue(cache, key, settings = null) {
if (!cache[key]) return null;
const cacheEntry = cache[key];
if (!isCacheValid(cacheEntry, key)) {
delete cache[key];
return null;
}
return cacheEntry.value;
}
function setCachedValue(cache, key, value) {
cache[key] = {
value: value,
timestamp: Date.now()
};
}
// Settings save function
function saveSettings(settings) {
try {
const configDir = path.dirname(SETTINGS_FILE);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
return true;
} catch (error) {
return false;
}
}
function loadSettings() {
let settings;
try {
if (fs.existsSync(SETTINGS_FILE)) {
settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
// Merge with defaults to ensure all properties exist
settings = { ...DEFAULT_SETTINGS, ...settings };
} else {
settings = DEFAULT_SETTINGS;
}
} catch {
settings = DEFAULT_SETTINGS;
}
return settings;
}
// Helper function to execute shell commands safely
function execCommand(command, options = {}) {
try {
const cmd = IS_WINDOWS ? `cmd /c ${command}` : command;
return execSync(cmd, {
encoding: 'utf8',
timeout: 10000,
stdio: ['pipe', 'pipe', 'ignore'],
...options
}).toString().trim();
} catch (error) {
return '';
}
}
// Helper function to check if command exists
function commandExists(command) {
try {
if (IS_WINDOWS) {
execSync(`where ${command}`, { stdio: 'ignore' });
} else {
execSync(`which ${command}`, { stdio: 'ignore' });
}
return true;
} catch {
return false;
}
}
// Helper function to fetch IP info from ipinfo.io
async function fetchIPInfo(settings) {
return new Promise((resolve) => {
const token = settings.network.ipinfo_token;
const timeout = settings.network.timeout;
const url = `https://ipinfo.io/json${token ? `?token=${token}` : ''}`;
const req = https.get(url, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch {
resolve({});
}
});
});
req.on('error', () => resolve({}));
req.setTimeout(timeout, () => {
req.destroy();
resolve({});
});
});
}
// System info functions
const infoFunctions = {
user(settings) {
const color = colors[settings.colors.user] || colors.red;
const emoji = settings.display.show_emojis ? '👤 ' : '';
return `${color}${emoji}${os.userInfo().username}`;
},
hostname(settings) {
const color = colors[settings.colors.hostname] || colors.orange;
const emoji = settings.display.show_emojis ? '🏠 ' : '';
return `${color}${emoji}${os.hostname()}`;
},
async ip(settings) {
const cached = getCachedValue(this.cache, 'ip', settings);
if (cached) return cached;
if (!this.ipInfo) {
const emoji = settings.display.show_emojis ? '🌎 ' : '';
const result = settings.network.show_offline_message ?
`${colors.gray}${emoji}No Network` : '';
setCachedValue(this.cache, 'ip', result);
return result;
}
const ip = this.ipInfo.ip || 'No IP';
const color = colors[settings.colors.ip] || colors.green;
const emoji = settings.display.show_emojis ? '🌎 ' : '';
const result = `${color}${emoji}${ip}`;
setCachedValue(this.cache, 'ip', result);
return result;
},
iplocal(settings) {
const color = colors[settings.colors.iplocal] || colors.yellow;
const emoji = settings.display.show_emojis ? '🌐 ' : '';
if (IS_LINUX) {
// Try ifconfig first (like bash script)
try {
const ifconfig = execCommand('ifconfig 2>/dev/null');
const wlanMatch = ifconfig.match(/wlan0[\s\S]*?inet (\d+\.\d+\.\d+\.\d+)/);
if (wlanMatch) {
return `${color}${emoji}${wlanMatch[1]}`;
}
} catch {}
// Fallback to ip command (like bash script)
try {
const ipAddr = execCommand('ip addr show 2>/dev/null');
const addresses = [];
const matches = ipAddr.matchAll(/inet (\d+\.\d+\.\d+\.\d+)\/\d+/g);
for (const match of matches) {
if (!match[1].startsWith('127.')) {
addresses.push(match[1]);
}
}
if (addresses.length > 0) {
return `${color}${emoji}${addresses.join(' ')}`;
}
} catch {}
}
// Fallback to Node.js method
const interfaces = os.networkInterfaces();
const addresses = [];
for (const name of Object.keys(interfaces)) {
for (const device of interfaces[name]) {
if (device.family === 'IPv4' && !device.internal) {
addresses.push(device.address);
}
}
}
if (addresses.length === 0) return '';
return `${color}${emoji}${addresses.join(' ')}`;
},
async city(settings) {
const cached = getCachedValue(this.cache, 'city', settings);
if (cached) return cached;
if (!this.ipInfo || !this.ipInfo.city) {
setCachedValue(this.cache, 'city', '');
return '';
}
const color = colors[settings.colors.city] || colors.green;
const emoji = settings.display.show_emojis ? '📍 ' : '';
const result = `${color}${emoji}${this.ipInfo.city}`;
setCachedValue(this.cache, 'city', result);
return result;
},
async domain(settings) {
const cached = getCachedValue(this.cache, 'domain', settings);
if (cached) return cached;
if (!this.ipInfo || !this.ipInfo.hostname) {
setCachedValue(this.cache, 'domain', '');
return '';
}
const color = colors[settings.colors.domain] || colors.gray;
const emoji = settings.display.show_emojis ? '🔗 ' : '';
const result = `${color}${emoji}http://${this.ipInfo.hostname}`;
setCachedValue(this.cache, 'domain', result);
return result;
},
async isp(settings) {
const cached = getCachedValue(this.cache, 'isp', settings);
if (cached) return cached;
if (!this.ipInfo || !this.ipInfo.org) {
setCachedValue(this.cache, 'isp', '');
return '';
}
const isp = this.ipInfo.org.split(' ').slice(1).join(' '); // Remove AS number
const color = colors[settings.colors.isp] || colors.lightblue;
const emoji = settings.display.show_emojis ? '👮 ' : '';
const result = `${color}${emoji}${isp}`;
setCachedValue(this.cache, 'isp', result);
return result;
},
os(settings) {
const cached = getCachedValue(this.cache, 'os', settings);
if (cached) return cached;
const platform = os.platform();
const release = os.release();
let osName = '';
if (IS_WINDOWS) {
try {
const version = execCommand('ver');
const match = version.match(/Microsoft Windows \[Version ([^\]]+)\]/);
osName = match ? `Windows ${match[1]}` : `Windows ${release}`;
} catch {
osName = `Windows ${release}`;
}
} else if (IS_MAC) {
osName = `macOS ${release}`;
} else if (IS_LINUX) {
try {
const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
const nameMatch = osRelease.match(/^NAME="([^"]+)"/m);
const versionMatch = osRelease.match(/^VERSION_ID="([^"]+)"/m);
osName = nameMatch ? nameMatch[1] : 'Linux';
if (versionMatch) osName += ` ${versionMatch[1]}`;
} catch {
osName = `Linux ${release}`;
}
} else {
osName = `${platform} ${release}`;
}
const color = colors[settings.colors.os] || colors.blue;
const emoji = settings.display.show_emojis ? '⚡ ' : '';
const result = `${color}${emoji}${osName}`;
setCachedValue(this.cache, 'os', result);
return result;
},
cpu(settings) {
const cached = getCachedValue(this.cache, 'cpu', settings);
if (cached) return cached;
let cpuName = '';
if (IS_WINDOWS) {
// Windows-specific CPU detection using WMIC
try {
const wmic = execCommand('wmic cpu get name /format:list');
const nameMatch = wmic.match(/Name=(.+)/);
if (nameMatch) {
cpuName = nameMatch[1].trim();
}
} catch {}
// Fallback to PowerShell if WMIC fails
if (!cpuName) {
try {
const ps = execCommand('powershell.exe -Command "Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Name"');
if (ps.trim()) {
cpuName = ps.trim();
}
} catch {}
}
} else if (IS_LINUX) {
// Try lscpu first (like bash script)
try {
const lscpu = execCommand('lscpu');
const modelMatch = lscpu.match(/Model name:\s*([^\n,]+)/);
if (modelMatch) {
cpuName = modelMatch[1].trim();
}
} catch {}
// Fallback to /proc/cpuinfo (like bash script)
if (!cpuName) {
try {
const cpuInfo = fs.readFileSync('/proc/cpuinfo', 'utf8');
const modelMatch = cpuInfo.match(/model name\s*:\s*([^\n]+)/);
const hardwareMatch = cpuInfo.match(/Hardware\s*:\s*([^\n]+)/);
if (modelMatch) {
cpuName = modelMatch[1].trim();
} else if (hardwareMatch) {
cpuName = hardwareMatch[1].trim();
}
} catch {}
}
} else {
// Use Node.js os module for other platforms
const cpus = os.cpus();
if (cpus.length > 0) {
cpuName = cpus[0].model.trim().replace(/[\r\n]+/g, ' ');
}
}
if (!cpuName) {
setCachedValue(this.cache, 'cpu', '');
return '';
}
//remove "with ..." from cpuName
cpuName = cpuName.trim().replace(/with .*/, '');
const color = colors[settings.colors.cpu] || colors.orange;
const emoji = settings.display.show_emojis ? '📈 ' : '';
const result = `${color}${emoji}${cpuName}`;
setCachedValue(this.cache, 'cpu', result);
return result;
},
gpu(settings) {
const cached = getCachedValue(this.cache, 'gpu', settings);
if (cached) return cached;
if (IS_WINDOWS) {
try {
// Get GPU information using WMIC
const wmic = execCommand('wmic path win32_VideoController get name /format:list');
const nameMatch = wmic.match(/Name=(.+)/);
if (nameMatch) {
const gpu = nameMatch[1].trim();
if (gpu && gpu !== '' && !gpu.includes('Microsoft Basic')) {
const color = colors[settings.colors.gpu] || colors.yellow;
const emoji = settings.display.show_emojis ? '🎮 ' : '';
const result = `${color}${emoji}${gpu}`;
setCachedValue(this.cache, 'gpu', result);
return result;
}
}
} catch {}
// Fallback using PowerShell
try {
const ps = execCommand('powershell.exe -Command "Get-WmiObject -Class Win32_VideoController | Where-Object {$_.Name -notlike \'*Microsoft Basic*\'} | Select-Object -First 1 -ExpandProperty Name"');
if (ps.trim()) {
const gpu = ps.trim();
const color = colors[settings.colors.gpu] || colors.yellow;
const emoji = settings.display.show_emojis ? '🎮 ' : '';
const result = `${color}${emoji}${gpu}`;
setCachedValue(this.cache, 'gpu', result);
return result;
}
} catch {}
} else if (IS_LINUX) {
try {
const lspci = execCommand('lspci');
// Look for VGA and specific GPU brands like the bash script
const gpuMatch = lspci.match(/VGA.*?(RTX|GeForce|AMD|Intel|NVIDIA)[^\n]*/i);
if (gpuMatch) {
let gpu = gpuMatch[0];
// Try to extract clean GPU name from brackets like bash script
const bracketMatch = gpu.match(/\[([^\]]+)\]/);
if (bracketMatch) {
gpu = bracketMatch[1];
} else {
// Fallback: clean up the string
gpu = gpu.replace(/^.*VGA[^:]*:\s*/, '').replace(/\s*\(.*\)$/, '').trim();
}
if (gpu) {
const color = colors[settings.colors.gpu] || colors.yellow;
const emoji = settings.display.show_emojis ? '🎮 ' : '';
const result = `${color}${emoji}${gpu}`;
setCachedValue(this.cache, 'gpu', result);
return result;
}
}
} catch {}
}
setCachedValue(this.cache, 'gpu', '');
return '';
},
disk_used(settings) {
const cached = getCachedValue(this.cache, 'disk_used', settings);
if (cached) return cached;
if (IS_WINDOWS) {
// try {
// // Get disk usage for C: drive using PowerShell
// const ps = execCommand('powershell.exe -Command "Get-WmiObject -Class Win32_LogicalDisk -Filter \'DeviceID="C:"\' | Select-Object @{Name=\'PercentFree\';Expression={[math]::Round((($_.FreeSpace / $_.Size) * 100), 0)}} | Select-Object -ExpandProperty PercentFree"');
// if (ps.trim()) {
// const percentFree = parseInt(ps.trim());
// const percentUsed = 100 - percentFree;
// const color = colors[settings.colors.disk_used] || colors.purple;
// const emoji = settings.display.show_emojis ? '📁 ' : '';
// const result = `${color}${emoji}${percentUsed}%`;
// setCachedValue(this.cache, 'disk_used', result);
// return result;
// }
// } catch {}
// // Fallback using WMIC
// try {
// const wmic = execCommand('wmic logicaldisk where "DeviceID=\'C:\'" get Size,FreeSpace /format:list');
// const sizeMatch = wmic.match(/Size=(\d+)/);
// const freeMatch = wmic.match(/FreeSpace=(\d+)/);
// if (sizeMatch && freeMatch) {
// const size = parseInt(sizeMatch[1]);
// const free = parseInt(freeMatch[1]);
// const used = size - free;
// const percentUsed = Math.round((used / size) * 100);
// const color = colors[settings.colors.disk_used] || colors.purple;
// const emoji = settings.display.show_emojis ? '📁 ' : '';
// const result = `${color}${emoji}${percentUsed}%`;
// setCachedValue(this.cache, 'disk_used', result);
// return result;
// }
// } catch {}
} else if (IS_LINUX) {
try {
const df = execCommand('df -h');
let diskUsage = '';
// Check for Android storage first
if (df.includes('/storage/emulated')) {
const match = df.match(/\s+(\d+%)\s+\/storage\/emulated/);
diskUsage = match ? match[1] : '';
} else {
// Check for root filesystem - look for lines ending with exactly " /"
const lines = df.split('\n');
for (const line of lines) {
if (line.trim().endsWith(' /')) {
const parts = line.trim().split(/\s+/);
// Find the percentage column (should contain %)
const percentIndex = parts.findIndex(part => part.includes('%'));
if (percentIndex !== -1) {
diskUsage = parts[percentIndex];
break;
}
}
}
// Fallback: look for any line with root mount point
if (!diskUsage) {
const rootMatch = df.match(/(\d+%)\s+\/\s*$/m);
diskUsage = rootMatch ? rootMatch[1] : '';
}
}
if (diskUsage) {
const color = colors[settings.colors.disk_used] || colors.purple;
const emoji = settings.display.show_emojis ? '📁 ' : '';
const result = `${color}${emoji}${diskUsage}`;
setCachedValue(this.cache, 'disk_used', result);
return result;
}
} catch {}
}
setCachedValue(this.cache, 'disk_used', '');
return '';
},
ram_used(settings) {
const cached = getCachedValue(this.cache, 'ram_used', settings);
if (cached) return cached;
if (IS_LINUX) {
// Use /proc/meminfo for more accurate Linux memory info like bash script
try {
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const totalMatch = meminfo.match(/MemTotal:\s+(\d+) kB/);
const freeMatch = meminfo.match(/MemFree:\s+(\d+) kB/);
if (totalMatch && freeMatch) {
const totalMB = Math.round(parseInt(totalMatch[1]) / 1024);
const freeMB = Math.round(parseInt(freeMatch[1]) / 1024);
const usedMB = totalMB - freeMB;
const totalGB = Math.round(totalMB / 1024);
const usedGB = Math.round(usedMB / 1024);
const color = colors[settings.colors.ram_used] || colors.yellow;
const emoji = settings.display.show_emojis ? '💾 ' : '';
const result = `${color}${emoji}${usedGB}/${totalGB}GB`;
setCachedValue(this.cache, 'ram_used', result);
return result;
}
} catch {}
}
// Fallback to Node.js os module
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const totalGB = Math.round(totalMem / (1024 * 1024 * 1024));
const usedGB = Math.round(usedMem / (1024 * 1024 * 1024));
const color = colors[settings.colors.ram_used] || colors.yellow;
const emoji = settings.display.show_emojis ? '💾 ' : '';
const result = `${color}${emoji}${usedGB}/${totalGB}GB`;
setCachedValue(this.cache, 'ram_used', result);
return result;
},
top_process(settings) {
const cached = getCachedValue(this.cache, 'top_process', settings);
if (cached) return cached;
if (IS_LINUX) {
try {
const ps = execCommand('ps -eo pcpu,comm --sort=-%cpu --no-headers');
const lines = ps.split('\n');
if (lines.length > 0) {
const topProcess = lines[0].trim().replace(/\s+/, ' ').split(' ');
const cpu = topProcess[0].replace(/\.\d+/, '%');
const process = topProcess[1].split('/').pop();
const color = colors[settings.colors.top_process] || colors.magenta;
const emoji = settings.display.show_emojis ? '🔝 ' : '';
const result = `${color}${emoji}${cpu} ${process}`;
setCachedValue(this.cache, 'top_process', result);
return result;
}
} catch {}
}
setCachedValue(this.cache, 'top_process', '');
return '';
},
uptime(settings) {
const uptimeSeconds = os.uptime();
const days = Math.floor(uptimeSeconds / 86400);
const hours = Math.floor((uptimeSeconds % 86400) / 3600);
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
const color = colors[settings.colors.uptime] || colors.cyan;
const emoji = settings.display.show_emojis ? '⏱️ ' : '';
return `${color}${emoji}${days}d ${hours}h ${minutes}m`;
},
device(settings) {
const cached = getCachedValue(this.cache, 'device', settings);
if (cached) return cached;
if (IS_WINDOWS) {
try {
// Get computer model using WMIC
const wmic = execCommand('wmic csproduct get name /format:list');
const nameMatch = wmic.match(/Name=(.+)/);
if (nameMatch) {
const device = nameMatch[1].trim();
if (device && device !== '') {
const color = colors[settings.colors.device] || colors.blue;
const emoji = settings.display.show_emojis ? '💻 ' : '';
const result = `${color}${emoji}${device}`;
setCachedValue(this.cache, 'device', result);
return result;
}
}
} catch {}
// Fallback using PowerShell
try {
const ps = execCommand('powershell.exe -Command "Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty Model"');
if (ps.trim()) {
const device = ps.trim();
const color = colors[settings.colors.device] || colors.blue;
const emoji = settings.display.show_emojis ? '💻 ' : '';
const result = `${color}${emoji}${device}`;
setCachedValue(this.cache, 'device', result);
return result;
}
} catch {}
} else if (IS_LINUX) {
try {
// Check for Android
if (commandExists('getprop')) {
const device = execCommand('getprop ro.product.model');
if (device) {
const color = colors[settings.colors.device] || colors.blue;
const emoji = settings.display.show_emojis ? '💻 ' : '';
const result = `${color}${emoji}${device}`;
setCachedValue(this.cache, 'device', result);
return result;
}
}
// Check for DMI info
const dmiPath = '/sys/devices/virtual/dmi/id/product_name';
if (fs.existsSync(dmiPath)) {
const device = fs.readFileSync(dmiPath, 'utf8').trim();
if (device) {
const color = colors[settings.colors.device] || colors.blue;
const emoji = settings.display.show_emojis ? '💻 ' : '';
const result = `${color}${emoji}${device}`;
setCachedValue(this.cache, 'device', result);
return result;
}
}
} catch {}
}
setCachedValue(this.cache, 'device', '');
return '';
},
kernel(settings) {
const cached = getCachedValue(this.cache, 'kernel', settings);
if (cached) return cached;
const kernel = os.release();
const color = colors[settings.colors.kernel] || colors.green;
const emoji = settings.display.show_emojis ? '🔧 ' : '';
const result = `${color}${emoji}${kernel}`;
setCachedValue(this.cache, 'kernel', result);
return result;
},
shell(settings) {
if (IS_LINUX) {
try {
const ppid = process.ppid;
const shell = execCommand(`ps -p ${ppid} -o comm=`).split('/').pop();
const color = colors[settings.colors.shell] || colors.orange;
const emoji = settings.display.show_emojis ? '🐚 ' : '';
return `${color}${emoji}${shell}`;
} catch {}
}
return '';
},
pacman(settings) {
const cached = getCachedValue(this.cache, 'pacman', settings);
if (cached) return cached;
const commands = ['apt', 'npm', 'uv', 'docker', 'hx', 'nvim', 'bun', 'yay',
'pacman', 'yum', 'dnf', 'zypper', 'emerge', 'apk', 'snap', 'flatpak'];
const available = commands.filter(cmd => commandExists(cmd));
if (available.length === 0) {
setCachedValue(this.cache, 'pacman', '');
return '';
}
const color = colors[settings.colors.pacman] || colors.cyan;
const emoji = settings.display.show_emojis ? '🚀 ' : '';
const result = `${color}${emoji}${available.join(' ')}`;
setCachedValue(this.cache, 'pacman', result);
return result;
},
ports(settings) {
const cached = getCachedValue(this.cache, 'ports', settings);
if (cached) return cached;
if (IS_LINUX) {
try {
const lsof = execCommand('lsof -nP -iTCP -sTCP:LISTEN');
const lines = lsof.split('\n').slice(1); // Skip header
const ports = new Set();
lines.forEach(line => {
const parts = line.split(/\s+/);
if (parts.length >= 9) {
const address = parts[8];
const portMatch = address.match(/:(\d+)$/);
if (portMatch) {
const port = portMatch[1];
const process = parts[0].substring(0, 4);
ports.add(`${port}${process}`);
}
}
});
if (ports.size > 0) {
const portList = Array.from(ports);
const emoji = settings.display.show_emojis ? '🔌 ' : '';
let output = ` ${emoji}`;
if (settings.colors.ports === 'multicolor') {
const colorCodes = [31, 32, 33, 34, 35, 36]; // Red, Green, Yellow, Blue, Magenta, Cyan
portList.forEach((port, index) => {
const color = colorCodes[index % colorCodes.length];
output += `\x1b[${color}m${port}\x1b[0m `;
});
} else {
const color = colors[settings.colors.ports] || colors.cyan;
output = `${color}${emoji}${portList.join(' ')}`;
}
const result = output.trim();
setCachedValue(this.cache, 'ports', result);
return result;
}
} catch {}
}
setCachedValue(this.cache, 'ports', '');
return '';
},
containers(settings) {
const cached = getCachedValue(this.cache, 'containers', settings);
if (cached) return cached;
if (!commandExists('docker')) {
setCachedValue(this.cache, 'containers', '');
return '';
}
try {
const containerCount = execCommand('docker ps -q').split('\n').filter(line => line.trim()).length;
if (containerCount === 0) {
setCachedValue(this.cache, 'containers', '');
return '';
}
const containers = execCommand('docker ps --format "{{.Names}}\t{{.Ports}}"');
const lines = containers.split('\n').filter(line => line.trim());
const emoji = settings.display.show_emojis ? '📦' : '';
const color = colors[settings.colors.containers] || colors.green;
let output = ` ${color}${emoji}\x1b[0m`;
lines.forEach(line => {
const [name, ports] = line.split('\t');
if (name) {
output += ` ${color}${name}\x1b[0m`;
if (ports) {
const portMatches = ports.match(/->(\d+(-\d+)?)\//g);
if (portMatches) {
const uniquePorts = [...new Set(portMatches.map(p => p.replace(/->\d+(-\d+)?\//, '')))];
uniquePorts.forEach(port => {
output += ` \x1b[33m${port}\x1b[0m`;
});
}
}
}
});
setCachedValue(this.cache, 'containers', output);
return output;
} catch {}
setCachedValue(this.cache, 'containers', '');
return '';
},
// Additional Linux system info functions
memory_available(settings) {
if (!IS_LINUX) return '';
try {
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const availableMatch = meminfo.match(/MemAvailable:\s+(\d+) kB/);
if (availableMatch) {
const availableGB = Math.round(parseInt(availableMatch[1]) / 1024 / 1024);
const color = colors[settings.colors.memory_available] || colors.blue;
const emoji = settings.display.show_emojis ? '🧠 ' : '';
return `${color}${emoji}${availableGB}GB available`;
}
} catch {}
return '';
},
swap_used(settings) {
if (!IS_LINUX) return '';
try {
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const swapTotalMatch = meminfo.match(/SwapTotal:\s+(\d+) kB/);
const swapFreeMatch = meminfo.match(/SwapFree:\s+(\d+) kB/);
if (swapTotalMatch && swapFreeMatch) {
const swapTotal = parseInt(swapTotalMatch[1]);
const swapFree = parseInt(swapFreeMatch[1]);
const swapUsed = swapTotal - swapFree;
if (swapTotal > 0) {
const swapUsedPercent = Math.round((swapUsed / swapTotal) * 100);
const swapUsedMB = Math.round(swapUsed / 1024);
const color = colors[settings.colors.swap_used] || colors.purple;
const emoji = settings.display.show_emojis ? '🔄 ' : '';
return `${color}${emoji}${swapUsedPercent}% (${swapUsedMB}MB) swap`;
}
}
} catch {}
return '';
},
load_average(settings) {
if (!IS_LINUX) return '';
try {
const loadavg = fs.readFileSync('/proc/loadavg', 'utf8');
const loads = loadavg.split(' ').slice(0, 3);
const color = colors[settings.colors.load_average] || colors.red;
const emoji = settings.display.show_emojis ? '⚖️ ' : '';
return `${color}${emoji}${loads.join(' ')}`;
} catch {}
return '';
},
users_logged_in(settings) {
if (!IS_LINUX) return '';
try {
const who = execCommand('who');
const users = who.split('\n').filter(line => line.trim()).length;
if (users > 0) {
const color = colors[settings.colors.users_logged_in] || colors.cyan;
const emoji = settings.display.show_emojis ? '👥 ' : '';
return `${color}${emoji}${users} users`;
}
} catch {}
return '';
},
network_interfaces(settings) {
const cached = getCachedValue(this.cache, 'network_interfaces', settings);
if (cached) return cached;
if (!IS_LINUX) {
setCachedValue(this.cache, 'network_interfaces', '');
return '';
}
try {
const interfaces = os.networkInterfaces();
const activeInterfaces = [];
for (const [name, addrs] of Object.entries(interfaces)) {
if (name !== 'lo') { // Skip loopback
const ipv4Addr = addrs.find(addr => addr.family === 'IPv4' && !addr.internal);
if (ipv4Addr) {
activeInterfaces.push(name);
}
}
}
if (activeInterfaces.length > 0) {
const color = colors[settings.colors.network_interfaces] || colors.yellow;
const emoji = settings.display.show_emojis ? '🌐 ' : '';
const result = `${color}${emoji}${activeInterfaces.join(' ')}`;
setCachedValue(this.cache, 'network_interfaces', result);
return result;
}
} catch {}
setCachedValue(this.cache, 'network_interfaces', '');
return '';
},
mount_points(settings) {
const cached = getCachedValue(this.cache, 'mount_points', settings);
if (cached) return cached;
if (!IS_LINUX) {
setCachedValue(this.cache, 'mount_points', '');
return '';
}
try {
const df = execCommand('df -h');
const lines = df.split('\n').slice(1); // Skip header
const mountPoints = [];
lines.forEach(line => {
const parts = line.trim().split(/\s+/);
if (parts.length >= 6) {
const mountPoint = parts[5];
const usage = parts[4];
if (!mountPoint.startsWith('/dev') && !mountPoint.startsWith('/proc') &&
!mountPoint.startsWith('/sys') && mountPoint !== '/') {
mountPoints.push(`${mountPoint}(${usage})`);
}
}
});
if (mountPoints.length > 0) {
const color = colors[settings.colors.mount_points] || colors.gray;
const emoji = settings.display.show_emojis ? '📂 ' : '';
const result = `${color}${emoji}${mountPoints.slice(0, 3).join(' ')}`;
setCachedValue(this.cache, 'mount_points', result);
return result;
}
} catch {}
setCachedValue(this.cache, 'mount_points', '');
return '';
},
services_running(settings) {
const cached = getCachedValue(this.cache, 'services_running', settings);
if (cached) return cached;
if (!IS_LINUX) {
setCachedValue(this.cache, 'services_running', '');
return '';
}
try {
let serviceCount = 0;
// Try systemctl first
if (commandExists('systemctl')) {
const services = execCommand('systemctl list-units --type=service --state=running --no-pager');
serviceCount = services.split('\n').filter(line => line.includes('.service')).length;
} else if (commandExists('service')) {
// Fallback for older systems
const services = execCommand('service --status-all');
serviceCount = services.split('\n').filter(line => line.includes('+')).length;
}
if (serviceCount > 0) {
const color = colors[settings.colors.services_running] || colors.green;
const emoji = settings.display.show_emojis ? '⚙️ ' : '';
const result = `${color}${emoji}${serviceCount} services`;
setCachedValue(this.cache, 'services_running', result);
return result;
}
} catch {}
setCachedValue(this.cache, 'services_running', '');
return '';
},
temperature(settings) {
const cached = getCachedValue(this.cache, 'temperature', settings);
if (cached) return cached;
if (!IS_LINUX) {
setCachedValue(this.cache, 'temperature', '');
return '';
}
try {
// Try different temperature sources
const tempSources = [
'/sys/class/thermal/thermal_zone0/temp',
'/sys/class/hwmon/hwmon0/temp1_input',
'/sys/class/hwmon/hwmon1/temp1_input'
];
for (const source of tempSources) {
if (fs.existsSync(source)) {
const temp = fs.readFileSync(source, 'utf8').trim();
const tempC = Math.round(parseInt(temp) / 1000);
if (tempC > 0 && tempC < 150) { // Reasonable temperature range
const color = tempC > 70 ? colors.red : tempC > 50 ? colors.yellow : colors.green;
const emoji = settings.display.show_emojis ? '🌡️ ' : '';
const result = `${color}${emoji}${tempC}°C`;
setCachedValue(this.cache, 'temperature', result);
return result;
}
}
}
} catch {}
setCachedValue(this.cache, 'temperature', '');
return '';
},
battery(settings) {
const cached = getCachedValue(this.cache, 'battery', settings);
if (cached) return cached;
if (!IS_LINUX) {
setCachedValue(this.cache, 'battery', '');
return '';
}
try {
const batteryPath = '/sys/class/power_supply/BAT0';
const capacityPath = `${batteryPath}/capacity`;
const statusPath = `${batteryPath}/status`;
if (fs.existsSync(capacityPath)) {
const capacity = fs.readFileSync(capacityPath, 'utf8').trim();
const status = fs.existsSync(statusPath) ?
fs.readFileSync(statusPath, 'utf8').trim() : 'Unknown';
const batteryPercent = parseInt(capacity);
const isCharging = status === 'Charging';
const color = batteryPercent < 20 ? colors.red :
batteryPercent < 50 ? colors.yellow : colors.green;
const emoji = settings.display.show_emojis ?
(isCharging ? '🔌 ' : '🔋 ') : '';
const result = `${color}${emoji}${batteryPercent}%${isCharging ? '+' : ''}`;
setCachedValue(this.cache, 'battery', result);
return result;
}
} catch {}
setCachedValue(this.cache, 'battery', '');
return '';
},
screen_resolution(settings) {
if (!IS_LINUX) return '';
try {
if (process.env.DISPLAY) {
const xrandr = execCommand('xrandr');
const resolutionMatch = xrandr.match(/(\d+x\d+)\+\d+\+\d+/);
if (resolutionMatch) {
const color = colors[settings.colors.screen_resolution] || colors.blue;
const emoji = settings.display.show_emojis ? '🖥️ ' : '';
return `${color}${emoji}${resolutionMatch[1]}`;
}
}
} catch {}
return '';
}
};
// Main function to display system info
async function displaySystemInfo() {
const settings = loadSettings();
const cache = loadCache();
const context = { cache, settings };
// Check if we need IP info and it's not cached
const allKeys = settings.display_order.flat();
const needIPInfo = allKeys.some(key => ['ip', 'isp', 'domain', 'city'].includes(key));
const cachedIPInfo = getCachedValue(cache, 'ipInfo', settings);
if (needIPInfo && !cachedIPInfo) {
context.ipInfo = await fetchIPInfo(settings);
setCachedValue(cache, 'ipInfo', context.ipInfo);
} else if (needIPInfo && cachedIPInfo) {
context.ipInfo = cachedIPInfo;
}
// If single line mode is enabled, flatten everything into one line
if (settings.display.single_line) {
const allItems = [];
for (const group of settings.display_order) {
for (const key of group) {
if (infoFunctions[key]) {
try {
const info = await infoFunctions[key].call(context, settings);
if (info && info.trim()) {
allItems.push(info);
}
} catch (error) {
if (settings.advanced.debug) {
console.error(`Error getting ${key}:`, error.message);
}
}
}
}
}
if (allItems.length > 0) {
const singleLine = allItems.join(' ');
console.log(singleLine + colors.reset);
}
// Save cache and return early
saveCache(cache);
return;
}
// Normal multi-line grouped display with intelligent wrapping
const lines = [];
let currentLine = '';
const maxLineLength = settings.display.line_wrap_length || 120;
for (const group of settings.display_order) {
for (const key of group) {
if (infoFunctions[key]) {
try {
const info = await infoFunctions[key].call(context, settings);
if (info && info.trim()) {
// Remove ANSI color codes to get actual text length
const infoLength = info.replace(/\x1b\[[0-9;]*m/g, '').length;
const currentLineLength = currentLine.replace(/\x1b\[[0-9;]*m/g, '').length;
// If adding this item would exceed the line length, start a new line
if (currentLine && (currentLineLength + infoLength + 1) > maxLineLength) {
lines.push(currentLine);
currentLine = info;
} else {
// Add to current line
if (currentLine) {
currentLine += ' ' + info;
} else {
currentLine = info;
}
}
}
} catch (error) {
if (settings.advanced.debug) {
console.error(`Error getting ${key}:`, error.message);
}
}
} else if (settings.advanced.debug) {
console.error(`Unknown info function: ${key}`);
}
}
}
// Add the last line if it has content
if (currentLine) {
lines.push(currentLine);
}
// Show offline message if no network and IP info was requested but failed
if (needIPInfo && (!context.ipInfo || Object.keys(context.ipInfo).length === 0) &&
settings.network.show_offline_message && lines.length === 0) {
const emoji = settings.display.show_emojis ? '❌ ' : '';
lines.push(`${colors.red}${emoji}No internet connection`);
}
// Save cache after all operations
saveCache(cache);
// Output each line
if (lines.length > 0) {
lines.forEach(line => {
console.log(line + colors.reset);
});
} else if (settings.advanced.debug) {
console.log('No system information could be displayed');
}
}
// Settings management commands
function handleSettingsCommand(args) {
const settings = loadSettings();
if (args.includes('--settings-init')) {
if (saveSettings(DEFAULT_SETTINGS)) {
console.log('Settings initialized with defaults');
} else {
console.log('Failed to initialize settings');
}
return true;
}
if (args.includes('--settings-show')) {
console.log('Current settings:');
console.log(JSON.stringify(settings, null, 2));
return true;
}
if (args.includes('--settings-reset')) {
if (saveSettings(DEFAULT_SETTINGS)) {
console.log('Settings reset to defaults');
} else {
console.log('Failed to reset settings');
}
return true;
}
const cacheResetIndex = args.indexOf('--refresh');
if (cacheResetIndex !== -1) {
try {
if (fs.existsSync(CACHE_FILE)) {
fs.unlinkSync(CACHE_FILE);
}
} catch (error) {
console.error('Error clearing cache:', error.message);
}
}
const setIndex = args.indexOf('--set');
if (setIndex !== -1 && args[setIndex + 1] && args[setIndex + 2]) {
const key = args[setIndex + 1];
const value = args[setIndex + 2];
try {
// Parse JSON value if it looks like JSON
const parsedValue = value.startsWith('{') || value.startsWith('[') ?
JSON.parse(value) : value;
// Set nested property using dot notation
const keys = key.split('.');
let current = settings;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = parsedValue;
if (saveSettings(settings)) {
console.log(`Setting ${key} = ${value}`);
} else {
console.log('Failed to save settings');
}
} catch (error) {
console.error('Error setting value:', error.message);
}
return true;
}
return false;
}
// Installation function - Cross-platform compatible
function installShellGreeting() {
const homeDir = os.homedir();
let configDir, scriptPath;
if (IS_WINDOWS) {
configDir = path.join(homeDir, 'AppData', 'Local');
scriptPath = path.join(configDir, 'systeminfo.js');
} else {
configDir = path.join(homeDir, '.config');
scriptPath = path.join(configDir, 'systeminfo.js');
}
const currentScript = path.resolve(__filename);
try {
// Ensure config directory exists
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Copy this script
fs.copyFileSync(currentScript, scriptPath);
if (!IS_WINDOWS) {
fs.chmodSync(scriptPath, '755');
}
if (IS_WINDOWS) {
// Windows-specific installation
console.log('Windows installation:');
console.log('1. Script copied to:', scriptPath);
console.log('2. To add to PowerShell profile, run:');
console.log(` Add-Content $PROFILE "node '${scriptPath}'"`);
console.log('3. To add to Command Prompt, create a batch file in your startup folder');
const startupBat = path.join(configDir, 'systeminfo-startup.bat');
fs.writeFileSync(startupBat, `@echo off\nnode "${scriptPath}"\n`);
console.log('4. Batch file created:', startupBat);
} else {
// Unix-like installation
// Silence default login messages
try {
const hushLoginPath = path.join(homeDir, '.hushlogin');
fs.writeFileSync(hushLoginPath, '');
} catch {
// Ignore permission errors
}
// Add to bash
const bashrcPath = path.join(homeDir, '.bashrc');
const bashLine = `node ${scriptPath}`;
if (fs.existsSync(bashrcPath)) {
const bashrc = fs.readFileSync(bashrcPath, 'utf8');
if (!bashrc.includes('systeminfo.js')) {
fs.appendFileSync(bashrcPath, `\n${bashLine}\n`);
}
} else {
fs.writeFileSync(bashrcPath, `${bashLine}\n`);
}
// Add to zsh (common on Mac)
const zshrcPath = path.join(homeDir, '.zshrc');
if (fs.existsSync(zshrcPath)) {
const zshrc = fs.readFileSync(zshrcPath, 'utf8');
if (!zshrc.includes('systeminfo.js')) {
fs.appendFileSync(zshrcPath, `\n${bashLine}\n`);
}
}
// Add to fish if config exists
const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish');
if (fs.existsSync(fishConfigPath)) {
const fishConfig = fs.readFileSync(fishConfigPath, 'utf8');
if (!fishConfig.includes('systeminfo.js')) {
fs.appendFileSync(fishConfigPath, `\nset -U fish_greeting ""\n${bashLine}\n`);
}
}
// Add to nushell if config exists
const nushellConfigPath = path.join(homeDir, '.config', 'nushell', 'config.nu');
if (fs.existsSync(nushellConfigPath)) {
const nushellConfig = fs.readFileSync(nushellConfigPath, 'utf8');
if (!nushellConfig.includes('systeminfo.js')) {
fs.appendFileSync(nushellConfigPath, `\n$env.config.show_banner = false\n${bashLine}\n`);
}
}
}
console.log('Shell greeting installation completed!');
} catch (error) {
console.error('Error installing shell greeting:', error.message);
process.exit(1);
}
}
// Main execution
async function main() {
const args = process.argv.slice(2);
// Handle settings commands
if (handleSettingsCommand(args)) {
return;
}
// Check for --install argument
if (args.includes('--install')) {
installShellGreeting();
return;
}
// Check for --single-line argument
if (args.includes('--single-line')) {
const settings = loadSettings();
settings.display.single_line = true;
await displaySystemInfo();
return;
}
// Check for --multi-line argument
if (args.includes('--multi-line')) {
const settings = loadSettings();
settings.display.single_line = false;
await displ