about-system 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +235 -0
  2. package/about-system.js +1430 -0
  3. package/package.json +57 -0
@@ -0,0 +1,1430 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * =========================================================
5
+ * System Info with Emojis - Node.js Version
6
+ * =========================================================
7
+ * Node.js script to display key system information with emojis
8
+ * The output and order are fully customizable: edit DISPLAY_ORDER
9
+ * to control which info blocks are shown and in what order.
10
+ * Supported blocks: user, hostname, ip, iplocal, city, domain, isp, os,
11
+ * cpu, gpu, disk_used, ram_used, top_process, device, kernel, uptime,
12
+ * shell, pacman, ports, containers, memory_available, swap_used,
13
+ * load_average, users_logged_in, network_interfaces, mount_points,
14
+ * services_running, temperature, battery, screen_resolution.
15
+ * Network info (ip, city, domain, isp) is fetched from ipinfo.io
16
+ * only if needed.
17
+ *
18
+ * Author: Adapted from vtempest bash version (2022-25)
19
+ * Updated: 2025-09-01
20
+ * License: MIT
21
+ */
22
+
23
+ const os = require('os');
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const { execSync } = require('child_process');
27
+ const https = require('https');
28
+
29
+ // Cache configuration
30
+ const CACHE_FILE = path.join(os.tmpdir(), 'systeminfo-cache.json');
31
+ const SETTINGS_FILE = path.join(os.homedir(), '.config', 'systeminfo-settings.json');
32
+ const CACHE_DURATION = {
33
+ // Cache durations in milliseconds
34
+ ip: 5 * 60 * 1000, // 5 minutes for IP info
35
+ cpu: 24 * 60 * 60 * 1000, // 24 hours for CPU info
36
+ gpu: 24 * 60 * 60 * 1000, // 24 hours for GPU info
37
+ os: 24 * 60 * 60 * 1000, // 24 hours for OS info
38
+ device: 24 * 60 * 60 * 1000, // 24 hours for device info
39
+ kernel: 60 * 60 * 1000, // 1 hour for kernel (can change on updates)
40
+ pacman: 10 * 60 * 1000, // 10 minutes for installed packages
41
+ ports: 30 * 1000, // 30 seconds for ports (changes frequently)
42
+ containers: 30 * 1000, // 30 seconds for containers
43
+ top_process: 5 * 1000, // 5 seconds for top process
44
+ disk_used: 60 * 1000, // 1 minute for disk usage
45
+ ram_used: 10 * 1000, // 10 seconds for RAM usage
46
+ services_running: 30 * 1000, // 30 seconds for services
47
+ temperature: 30 * 1000, // 30 seconds for temperature
48
+ battery: 60 * 1000, // 1 minute for battery
49
+ network_interfaces: 5 * 60 * 1000, // 5 minutes for network interfaces
50
+ mount_points: 10 * 60 * 1000 // 10 minutes for mount points
51
+ };
52
+
53
+ // Default settings
54
+ const DEFAULT_SETTINGS = {
55
+ version: "1.0.0",
56
+ display_order: [
57
+ ['user', 'hostname', 'os', 'cpu', 'gpu', 'device', 'kernel'],
58
+ ['disk_used', 'ram_used', 'top_process', 'uptime', 'temperature', 'battery', 'load_average'],
59
+ ['ip', 'iplocal', 'city', 'domain', 'isp'],
60
+ ['shell', 'pacman', 'ports', 'containers', 'services_running']
61
+ ],
62
+ colors: {
63
+ user: "red",
64
+ hostname: "orange",
65
+ disk_used: "purple",
66
+ ram_used: "yellow",
67
+ top_process: "magenta",
68
+ uptime: "cyan",
69
+ ip: "green",
70
+ iplocal: "yellow",
71
+ city: "green",
72
+ domain: "gray",
73
+ isp: "lightblue",
74
+ os: "blue",
75
+ cpu: "orange",
76
+ gpu: "yellow",
77
+ device: "blue",
78
+ kernel: "green",
79
+ shell: "orange",
80
+ pacman: "cyan",
81
+ ports: "multicolor",
82
+ containers: "green",
83
+ memory_available: "blue",
84
+ swap_used: "purple",
85
+ load_average: "red",
86
+ users_logged_in: "cyan",
87
+ network_interfaces: "yellow",
88
+ mount_points: "gray",
89
+ services_running: "green",
90
+ temperature: "red",
91
+ battery: "green",
92
+ screen_resolution: "blue"
93
+ },
94
+ cache: {
95
+ enabled: true,
96
+ custom_durations: {}
97
+ },
98
+ network: {
99
+ timeout: 5000,
100
+ ipinfo_token: "da2d6cc4baa5d1",
101
+ show_offline_message: true
102
+ },
103
+ display: {
104
+ show_emojis: true,
105
+ compact_mode: false,
106
+ separator: "\n",
107
+ max_width: 120,
108
+ multiline: true,
109
+ group_similar: true
110
+ },
111
+ advanced: {
112
+ debug: false,
113
+ performance_logging: false,
114
+ fallback_commands: true
115
+ }
116
+ };
117
+
118
+ // Color codes for terminal output
119
+ const colors = {
120
+ reset: '\x1b[0m',
121
+ red: '\x1b[38;5;196m',
122
+ orange: '\x1b[38;5;208m',
123
+ yellow: '\x1b[38;5;226m',
124
+ green: '\x1b[38;5;46m',
125
+ blue: '\x1b[38;5;39m',
126
+ cyan: '\x1b[38;5;51m',
127
+ purple: '\x1b[38;5;171m',
128
+ magenta: '\x1b[38;5;213m',
129
+ gray: '\x1b[38;5;250m',
130
+ lightblue: '\x1b[38;5;220m'
131
+ };
132
+
133
+ // Platform detection constants
134
+ const IS_WINDOWS = os.platform() === 'win32';
135
+ const IS_MAC = os.platform() === 'darwin';
136
+ const IS_LINUX = os.platform() === 'linux';
137
+
138
+ // Cache management functions
139
+ function loadCache() {
140
+ try {
141
+ if (fs.existsSync(CACHE_FILE)) {
142
+ const cacheData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
143
+ return cacheData;
144
+ }
145
+ } catch (error) {
146
+ // If cache is corrupted, ignore it
147
+ }
148
+ return {};
149
+ }
150
+
151
+ function saveCache(cache) {
152
+ try {
153
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
154
+ } catch (error) {
155
+ // Silently fail if can't write cache
156
+ }
157
+ }
158
+
159
+ function isCacheValid(cacheEntry, key) {
160
+ if (!cacheEntry || !cacheEntry.timestamp) return false;
161
+ const age = Date.now() - cacheEntry.timestamp;
162
+ const maxAge = CACHE_DURATION[key] || 60000; // Default 1 minute
163
+ return age < maxAge;
164
+ }
165
+
166
+ // Cache helper functions
167
+ function getCachedValue(cache, key, settings = null) {
168
+ if (!cache[key]) return null;
169
+
170
+ const cacheEntry = cache[key];
171
+ if (!isCacheValid(cacheEntry, key)) {
172
+ delete cache[key];
173
+ return null;
174
+ }
175
+
176
+ return cacheEntry.value;
177
+ }
178
+
179
+ function setCachedValue(cache, key, value) {
180
+ cache[key] = {
181
+ value: value,
182
+ timestamp: Date.now()
183
+ };
184
+ }
185
+
186
+ // Settings save function
187
+ function saveSettings(settings) {
188
+ try {
189
+ const configDir = path.dirname(SETTINGS_FILE);
190
+ if (!fs.existsSync(configDir)) {
191
+ fs.mkdirSync(configDir, { recursive: true });
192
+ }
193
+ fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
194
+ return true;
195
+ } catch (error) {
196
+ return false;
197
+ }
198
+ }
199
+
200
+ function loadSettings() {
201
+ let settings;
202
+ try {
203
+ if (fs.existsSync(SETTINGS_FILE)) {
204
+ settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
205
+ // Merge with defaults to ensure all properties exist
206
+ settings = { ...DEFAULT_SETTINGS, ...settings };
207
+ } else {
208
+ settings = DEFAULT_SETTINGS;
209
+ }
210
+ } catch {
211
+ settings = DEFAULT_SETTINGS;
212
+ }
213
+ return settings;
214
+ }
215
+
216
+ // Helper function to execute shell commands safely
217
+ function execCommand(command, options = {}) {
218
+ try {
219
+ const cmd = IS_WINDOWS ? `cmd /c ${command}` : command;
220
+ return execSync(cmd, {
221
+ encoding: 'utf8',
222
+ timeout: 10000,
223
+ stdio: ['pipe', 'pipe', 'ignore'],
224
+ ...options
225
+ }).toString().trim();
226
+ } catch (error) {
227
+ return '';
228
+ }
229
+ }
230
+
231
+ // Helper function to check if command exists
232
+ function commandExists(command) {
233
+ try {
234
+ if (IS_WINDOWS) {
235
+ execSync(`where ${command}`, { stdio: 'ignore' });
236
+ } else {
237
+ execSync(`which ${command}`, { stdio: 'ignore' });
238
+ }
239
+ return true;
240
+ } catch {
241
+ return false;
242
+ }
243
+ }
244
+
245
+ // Helper function to fetch IP info from ipinfo.io
246
+ async function fetchIPInfo(settings) {
247
+ return new Promise((resolve) => {
248
+ const token = settings.network.ipinfo_token;
249
+ const timeout = settings.network.timeout;
250
+ const url = `https://ipinfo.io/json${token ? `?token=${token}` : ''}`;
251
+
252
+ const req = https.get(url, (res) => {
253
+ let data = '';
254
+ res.on('data', (chunk) => data += chunk);
255
+ res.on('end', () => {
256
+ try {
257
+ resolve(JSON.parse(data));
258
+ } catch {
259
+ resolve({});
260
+ }
261
+ });
262
+ });
263
+
264
+ req.on('error', () => resolve({}));
265
+ req.setTimeout(timeout, () => {
266
+ req.destroy();
267
+ resolve({});
268
+ });
269
+ });
270
+ }
271
+
272
+ // System info functions
273
+ const infoFunctions = {
274
+ user(settings) {
275
+ const color = colors[settings.colors.user] || colors.red;
276
+ const emoji = settings.display.show_emojis ? '👤 ' : '';
277
+ return `${color}${emoji}${os.userInfo().username}`;
278
+ },
279
+
280
+ hostname(settings) {
281
+ const color = colors[settings.colors.hostname] || colors.orange;
282
+ const emoji = settings.display.show_emojis ? '🏠 ' : '';
283
+ return `${color}${emoji}${os.hostname()}`;
284
+ },
285
+
286
+ async ip(settings) {
287
+ const cached = getCachedValue(this.cache, 'ip', settings);
288
+ if (cached) return cached;
289
+
290
+ if (!this.ipInfo) {
291
+ const emoji = settings.display.show_emojis ? '🌎 ' : '';
292
+ const result = settings.network.show_offline_message ?
293
+ `${colors.gray}${emoji}No Network` : '';
294
+ setCachedValue(this.cache, 'ip', result);
295
+ return result;
296
+ }
297
+ const ip = this.ipInfo.ip || 'No IP';
298
+ const color = colors[settings.colors.ip] || colors.green;
299
+ const emoji = settings.display.show_emojis ? '🌎 ' : '';
300
+ const result = `${color}${emoji}${ip}`;
301
+ setCachedValue(this.cache, 'ip', result);
302
+ return result;
303
+ },
304
+
305
+ iplocal(settings) {
306
+ const color = colors[settings.colors.iplocal] || colors.yellow;
307
+ const emoji = settings.display.show_emojis ? '🌐 ' : '';
308
+
309
+ if (IS_LINUX) {
310
+ // Try ifconfig first (like bash script)
311
+ try {
312
+ const ifconfig = execCommand('ifconfig 2>/dev/null');
313
+ const wlanMatch = ifconfig.match(/wlan0[\s\S]*?inet (\d+\.\d+\.\d+\.\d+)/);
314
+ if (wlanMatch) {
315
+ return `${color}${emoji}${wlanMatch[1]}`;
316
+ }
317
+ } catch {}
318
+
319
+ // Fallback to ip command (like bash script)
320
+ try {
321
+ const ipAddr = execCommand('ip addr show 2>/dev/null');
322
+ const addresses = [];
323
+ const matches = ipAddr.matchAll(/inet (\d+\.\d+\.\d+\.\d+)\/\d+/g);
324
+ for (const match of matches) {
325
+ if (!match[1].startsWith('127.')) {
326
+ addresses.push(match[1]);
327
+ }
328
+ }
329
+ if (addresses.length > 0) {
330
+ return `${color}${emoji}${addresses.join(' ')}`;
331
+ }
332
+ } catch {}
333
+ }
334
+
335
+ // Fallback to Node.js method
336
+ const interfaces = os.networkInterfaces();
337
+ const addresses = [];
338
+
339
+ for (const name of Object.keys(interfaces)) {
340
+ for (const interface of interfaces[name]) {
341
+ if (interface.family === 'IPv4' && !interface.internal) {
342
+ addresses.push(interface.address);
343
+ }
344
+ }
345
+ }
346
+
347
+ if (addresses.length === 0) return '';
348
+ return `${color}${emoji}${addresses.join(' ')}`;
349
+ },
350
+
351
+ async city(settings) {
352
+ const cached = getCachedValue(this.cache, 'city', settings);
353
+ if (cached) return cached;
354
+
355
+ if (!this.ipInfo || !this.ipInfo.city) {
356
+ setCachedValue(this.cache, 'city', '');
357
+ return '';
358
+ }
359
+ const color = colors[settings.colors.city] || colors.green;
360
+ const emoji = settings.display.show_emojis ? '📍 ' : '';
361
+ const result = `${color}${emoji}${this.ipInfo.city}`;
362
+ setCachedValue(this.cache, 'city', result);
363
+ return result;
364
+ },
365
+
366
+ async domain(settings) {
367
+ const cached = getCachedValue(this.cache, 'domain', settings);
368
+ if (cached) return cached;
369
+
370
+ if (!this.ipInfo || !this.ipInfo.hostname) {
371
+ setCachedValue(this.cache, 'domain', '');
372
+ return '';
373
+ }
374
+ const color = colors[settings.colors.domain] || colors.gray;
375
+ const emoji = settings.display.show_emojis ? '🔗 ' : '';
376
+ const result = `${color}${emoji}http://${this.ipInfo.hostname}`;
377
+ setCachedValue(this.cache, 'domain', result);
378
+ return result;
379
+ },
380
+
381
+ async isp(settings) {
382
+ const cached = getCachedValue(this.cache, 'isp', settings);
383
+ if (cached) return cached;
384
+
385
+ if (!this.ipInfo || !this.ipInfo.org) {
386
+ setCachedValue(this.cache, 'isp', '');
387
+ return '';
388
+ }
389
+ const isp = this.ipInfo.org.split(' ').slice(1).join(' '); // Remove AS number
390
+ const color = colors[settings.colors.isp] || colors.lightblue;
391
+ const emoji = settings.display.show_emojis ? '👮 ' : '';
392
+ const result = `${color}${emoji}${isp}`;
393
+ setCachedValue(this.cache, 'isp', result);
394
+ return result;
395
+ },
396
+
397
+ os(settings) {
398
+ const cached = getCachedValue(this.cache, 'os', settings);
399
+ if (cached) return cached;
400
+
401
+ const platform = os.platform();
402
+ const release = os.release();
403
+ let osName = '';
404
+
405
+ if (IS_WINDOWS) {
406
+ try {
407
+ const version = execCommand('ver');
408
+ const match = version.match(/Microsoft Windows \[Version ([^\]]+)\]/);
409
+ osName = match ? `Windows ${match[1]}` : `Windows ${release}`;
410
+ } catch {
411
+ osName = `Windows ${release}`;
412
+ }
413
+ } else if (IS_MAC) {
414
+ osName = `macOS ${release}`;
415
+ } else if (IS_LINUX) {
416
+ try {
417
+ const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
418
+ const nameMatch = osRelease.match(/^NAME="([^"]+)"/m);
419
+ const versionMatch = osRelease.match(/^VERSION_ID="([^"]+)"/m);
420
+ osName = nameMatch ? nameMatch[1] : 'Linux';
421
+ if (versionMatch) osName += ` ${versionMatch[1]}`;
422
+ } catch {
423
+ osName = `Linux ${release}`;
424
+ }
425
+ } else {
426
+ osName = `${platform} ${release}`;
427
+ }
428
+
429
+ const color = colors[settings.colors.os] || colors.blue;
430
+ const emoji = settings.display.show_emojis ? '⚡ ' : '';
431
+ const result = `${color}${emoji}${osName}`;
432
+ setCachedValue(this.cache, 'os', result);
433
+ return result;
434
+ },
435
+
436
+ cpu(settings) {
437
+ const cached = getCachedValue(this.cache, 'cpu', settings);
438
+ if (cached) return cached;
439
+
440
+ let cpuName = '';
441
+
442
+ if (IS_LINUX) {
443
+ // Try lscpu first (like bash script)
444
+ try {
445
+ const lscpu = execCommand('lscpu');
446
+ const modelMatch = lscpu.match(/Model name:\s*([^\n,]+)/);
447
+ if (modelMatch) {
448
+ cpuName = modelMatch[1].trim();
449
+ }
450
+ } catch {}
451
+
452
+ // Fallback to /proc/cpuinfo (like bash script)
453
+ if (!cpuName) {
454
+ try {
455
+ const cpuInfo = fs.readFileSync('/proc/cpuinfo', 'utf8');
456
+ const modelMatch = cpuInfo.match(/model name\s*:\s*([^\n]+)/);
457
+ const hardwareMatch = cpuInfo.match(/Hardware\s*:\s*([^\n]+)/);
458
+
459
+ if (modelMatch) {
460
+ cpuName = modelMatch[1].trim();
461
+ } else if (hardwareMatch) {
462
+ cpuName = hardwareMatch[1].trim();
463
+ }
464
+ } catch {}
465
+ }
466
+ } else {
467
+ // Use Node.js os module for other platforms
468
+ const cpus = os.cpus();
469
+ if (cpus.length > 0) {
470
+ cpuName = cpus[0].model;
471
+ }
472
+ }
473
+
474
+ if (!cpuName) {
475
+ setCachedValue(this.cache, 'cpu', '');
476
+ return '';
477
+ }
478
+
479
+ const color = colors[settings.colors.cpu] || colors.orange;
480
+ const emoji = settings.display.show_emojis ? '📈 ' : '';
481
+ const result = `${color}${emoji}${cpuName}`;
482
+ setCachedValue(this.cache, 'cpu', result);
483
+ return result;
484
+ },
485
+
486
+ gpu(settings) {
487
+ const cached = getCachedValue(this.cache, 'gpu', settings);
488
+ if (cached) return cached;
489
+
490
+ if (IS_LINUX) {
491
+ try {
492
+ const lspci = execCommand('lspci');
493
+ // Look for VGA and specific GPU brands like the bash script
494
+ const gpuMatch = lspci.match(/VGA.*?(RTX|GeForce|AMD|Intel|NVIDIA)[^\n]*/i);
495
+ if (gpuMatch) {
496
+ let gpu = gpuMatch[0];
497
+
498
+ // Try to extract clean GPU name from brackets like bash script
499
+ const bracketMatch = gpu.match(/\[([^\]]+)\]/);
500
+ if (bracketMatch) {
501
+ gpu = bracketMatch[1];
502
+ } else {
503
+ // Fallback: clean up the string
504
+ gpu = gpu.replace(/^.*VGA[^:]*:\s*/, '').replace(/\s*\(.*\)$/, '').trim();
505
+ }
506
+
507
+ if (gpu) {
508
+ const color = colors[settings.colors.gpu] || colors.yellow;
509
+ const emoji = settings.display.show_emojis ? '🎮 ' : '';
510
+ const result = `${color}${emoji}${gpu}`;
511
+ setCachedValue(this.cache, 'gpu', result);
512
+ return result;
513
+ }
514
+ }
515
+ } catch {}
516
+ }
517
+
518
+ setCachedValue(this.cache, 'gpu', '');
519
+ return '';
520
+ },
521
+
522
+ disk_used(settings) {
523
+ const cached = getCachedValue(this.cache, 'disk_used', settings);
524
+ if (cached) return cached;
525
+
526
+ if (IS_LINUX) {
527
+ try {
528
+ const df = execCommand('df -h');
529
+ let diskUsage = '';
530
+
531
+ // Check for Android storage first
532
+ if (df.includes('/storage/emulated')) {
533
+ const match = df.match(/\s+(\d+%)\s+\/storage\/emulated/);
534
+ diskUsage = match ? match[1] : '';
535
+ } else {
536
+ // Check for root filesystem - look for lines ending with exactly " /"
537
+ const lines = df.split('\n');
538
+ for (const line of lines) {
539
+ if (line.trim().endsWith(' /')) {
540
+ const parts = line.trim().split(/\s+/);
541
+ // Find the percentage column (should contain %)
542
+ const percentIndex = parts.findIndex(part => part.includes('%'));
543
+ if (percentIndex !== -1) {
544
+ diskUsage = parts[percentIndex];
545
+ break;
546
+ }
547
+ }
548
+ }
549
+
550
+ // Fallback: look for any line with root mount point
551
+ if (!diskUsage) {
552
+ const rootMatch = df.match(/(\d+%)\s+\/\s*$/m);
553
+ diskUsage = rootMatch ? rootMatch[1] : '';
554
+ }
555
+ }
556
+
557
+ if (diskUsage) {
558
+ const color = colors[settings.colors.disk_used] || colors.purple;
559
+ const emoji = settings.display.show_emojis ? '📁 ' : '';
560
+ const result = `${color}${emoji}${diskUsage}`;
561
+ setCachedValue(this.cache, 'disk_used', result);
562
+ return result;
563
+ }
564
+ } catch {}
565
+ }
566
+
567
+ setCachedValue(this.cache, 'disk_used', '');
568
+ return '';
569
+ },
570
+
571
+ ram_used(settings) {
572
+ const cached = getCachedValue(this.cache, 'ram_used', settings);
573
+ if (cached) return cached;
574
+
575
+ if (IS_LINUX) {
576
+ // Use /proc/meminfo for more accurate Linux memory info like bash script
577
+ try {
578
+ const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
579
+ const totalMatch = meminfo.match(/MemTotal:\s+(\d+) kB/);
580
+ const freeMatch = meminfo.match(/MemFree:\s+(\d+) kB/);
581
+
582
+ if (totalMatch && freeMatch) {
583
+ const totalMB = Math.round(parseInt(totalMatch[1]) / 1024);
584
+ const freeMB = Math.round(parseInt(freeMatch[1]) / 1024);
585
+ const usedMB = totalMB - freeMB;
586
+
587
+ const totalGB = Math.round(totalMB / 1024);
588
+ const usedGB = Math.round(usedMB / 1024);
589
+
590
+ const color = colors[settings.colors.ram_used] || colors.yellow;
591
+ const emoji = settings.display.show_emojis ? '💾 ' : '';
592
+ const result = `${color}${emoji}${usedGB}/${totalGB}GB`;
593
+ setCachedValue(this.cache, 'ram_used', result);
594
+ return result;
595
+ }
596
+ } catch {}
597
+ }
598
+
599
+ // Fallback to Node.js os module
600
+ const totalMem = os.totalmem();
601
+ const freeMem = os.freemem();
602
+ const usedMem = totalMem - freeMem;
603
+
604
+ const totalGB = Math.round(totalMem / (1024 * 1024 * 1024));
605
+ const usedGB = Math.round(usedMem / (1024 * 1024 * 1024));
606
+
607
+ const color = colors[settings.colors.ram_used] || colors.yellow;
608
+ const emoji = settings.display.show_emojis ? '💾 ' : '';
609
+ const result = `${color}${emoji}${usedGB}/${totalGB}GB`;
610
+ setCachedValue(this.cache, 'ram_used', result);
611
+ return result;
612
+ },
613
+
614
+ top_process(settings) {
615
+ const cached = getCachedValue(this.cache, 'top_process', settings);
616
+ if (cached) return cached;
617
+
618
+ if (IS_LINUX) {
619
+ try {
620
+ const ps = execCommand('ps -eo pcpu,comm --sort=-%cpu --no-headers');
621
+ const lines = ps.split('\n');
622
+ if (lines.length > 0) {
623
+ const topProcess = lines[0].trim().replace(/\s+/, ' ').split(' ');
624
+ const cpu = topProcess[0].replace(/\.\d+/, '%');
625
+ const process = topProcess[1].split('/').pop();
626
+ const color = colors[settings.colors.top_process] || colors.magenta;
627
+ const emoji = settings.display.show_emojis ? '🔝 ' : '';
628
+ const result = `${color}${emoji}${cpu} ${process}`;
629
+ setCachedValue(this.cache, 'top_process', result);
630
+ return result;
631
+ }
632
+ } catch {}
633
+ }
634
+
635
+ setCachedValue(this.cache, 'top_process', '');
636
+ return '';
637
+ },
638
+
639
+ uptime(settings) {
640
+ const uptimeSeconds = os.uptime();
641
+ const days = Math.floor(uptimeSeconds / 86400);
642
+ const hours = Math.floor((uptimeSeconds % 86400) / 3600);
643
+ const minutes = Math.floor((uptimeSeconds % 3600) / 60);
644
+
645
+ const color = colors[settings.colors.uptime] || colors.cyan;
646
+ const emoji = settings.display.show_emojis ? '⏱️ ' : '';
647
+ return `${color}${emoji}${days}d ${hours}h ${minutes}m`;
648
+ },
649
+
650
+ device(settings) {
651
+ const cached = getCachedValue(this.cache, 'device', settings);
652
+ if (cached) return cached;
653
+
654
+ if (IS_LINUX) {
655
+ try {
656
+ // Check for Android
657
+ if (commandExists('getprop')) {
658
+ const device = execCommand('getprop ro.product.model');
659
+ if (device) {
660
+ const color = colors[settings.colors.device] || colors.blue;
661
+ const emoji = settings.display.show_emojis ? '💻 ' : '';
662
+ const result = `${color}${emoji}${device}`;
663
+ setCachedValue(this.cache, 'device', result);
664
+ return result;
665
+ }
666
+ }
667
+
668
+ // Check for DMI info
669
+ const dmiPath = '/sys/devices/virtual/dmi/id/product_name';
670
+ if (fs.existsSync(dmiPath)) {
671
+ const device = fs.readFileSync(dmiPath, 'utf8').trim();
672
+ if (device) {
673
+ const color = colors[settings.colors.device] || colors.blue;
674
+ const emoji = settings.display.show_emojis ? '💻 ' : '';
675
+ const result = `${color}${emoji}${device}`;
676
+ setCachedValue(this.cache, 'device', result);
677
+ return result;
678
+ }
679
+ }
680
+ } catch {}
681
+ }
682
+
683
+ setCachedValue(this.cache, 'device', '');
684
+ return '';
685
+ },
686
+
687
+ kernel(settings) {
688
+ const cached = getCachedValue(this.cache, 'kernel', settings);
689
+ if (cached) return cached;
690
+
691
+ const kernel = os.release();
692
+ const color = colors[settings.colors.kernel] || colors.green;
693
+ const emoji = settings.display.show_emojis ? '🔧 ' : '';
694
+ const result = `${color}${emoji}${kernel}`;
695
+ setCachedValue(this.cache, 'kernel', result);
696
+ return result;
697
+ },
698
+
699
+ shell(settings) {
700
+ if (IS_LINUX) {
701
+ try {
702
+ const ppid = process.ppid;
703
+ const shell = execCommand(`ps -p ${ppid} -o comm=`).split('/').pop();
704
+ const color = colors[settings.colors.shell] || colors.orange;
705
+ const emoji = settings.display.show_emojis ? '🐚 ' : '';
706
+ return `${color}${emoji}${shell}`;
707
+ } catch {}
708
+ }
709
+ return '';
710
+ },
711
+
712
+ pacman(settings) {
713
+ const cached = getCachedValue(this.cache, 'pacman', settings);
714
+ if (cached) return cached;
715
+
716
+ const commands = ['apt', 'npm', 'pip', 'docker', 'hx', 'nvim', 'bun', 'yay',
717
+ 'pacman', 'yum', 'dnf', 'zypper', 'emerge', 'apk', 'snap', 'flatpak'];
718
+ const available = commands.filter(cmd => commandExists(cmd));
719
+
720
+ if (available.length === 0) {
721
+ setCachedValue(this.cache, 'pacman', '');
722
+ return '';
723
+ }
724
+
725
+ const color = colors[settings.colors.pacman] || colors.cyan;
726
+ const emoji = settings.display.show_emojis ? '🚀 ' : '';
727
+ const result = `${color}${emoji}${available.join(' ')}`;
728
+ setCachedValue(this.cache, 'pacman', result);
729
+ return result;
730
+ },
731
+
732
+ ports(settings) {
733
+ const cached = getCachedValue(this.cache, 'ports', settings);
734
+ if (cached) return cached;
735
+
736
+ if (IS_LINUX) {
737
+ try {
738
+ const lsof = execCommand('lsof -nP -iTCP -sTCP:LISTEN');
739
+ const lines = lsof.split('\n').slice(1); // Skip header
740
+ const ports = new Set();
741
+
742
+ lines.forEach(line => {
743
+ const parts = line.split(/\s+/);
744
+ if (parts.length >= 9) {
745
+ const address = parts[8];
746
+ const portMatch = address.match(/:(\d+)$/);
747
+ if (portMatch) {
748
+ const port = portMatch[1];
749
+ const process = parts[0].substring(0, 4);
750
+ ports.add(`${port}${process}`);
751
+ }
752
+ }
753
+ });
754
+
755
+ if (ports.size > 0) {
756
+ const portList = Array.from(ports);
757
+ const emoji = settings.display.show_emojis ? '🔌 ' : '';
758
+ let output = ` ${emoji}`;
759
+
760
+ if (settings.colors.ports === 'multicolor') {
761
+ const colorCodes = [31, 32, 33, 34, 35, 36]; // Red, Green, Yellow, Blue, Magenta, Cyan
762
+ portList.forEach((port, index) => {
763
+ const color = colorCodes[index % colorCodes.length];
764
+ output += `\x1b[${color}m${port}\x1b[0m `;
765
+ });
766
+ } else {
767
+ const color = colors[settings.colors.ports] || colors.cyan;
768
+ output = `${color}${emoji}${portList.join(' ')}`;
769
+ }
770
+
771
+ const result = output.trim();
772
+ setCachedValue(this.cache, 'ports', result);
773
+ return result;
774
+ }
775
+ } catch {}
776
+ }
777
+
778
+ setCachedValue(this.cache, 'ports', '');
779
+ return '';
780
+ },
781
+
782
+ containers(settings) {
783
+ const cached = getCachedValue(this.cache, 'containers', settings);
784
+ if (cached) return cached;
785
+
786
+ if (!commandExists('docker')) {
787
+ setCachedValue(this.cache, 'containers', '');
788
+ return '';
789
+ }
790
+
791
+ try {
792
+ const containerCount = execCommand('docker ps -q').split('\n').filter(line => line.trim()).length;
793
+ if (containerCount === 0) {
794
+ setCachedValue(this.cache, 'containers', '');
795
+ return '';
796
+ }
797
+
798
+ const containers = execCommand('docker ps --format "{{.Names}}\t{{.Ports}}"');
799
+ const lines = containers.split('\n').filter(line => line.trim());
800
+
801
+ const emoji = settings.display.show_emojis ? '📦' : '';
802
+ const color = colors[settings.colors.containers] || colors.green;
803
+ let output = ` ${color}${emoji}\x1b[0m`;
804
+
805
+ lines.forEach(line => {
806
+ const [name, ports] = line.split('\t');
807
+ if (name) {
808
+ output += ` ${color}${name}\x1b[0m`;
809
+
810
+ if (ports) {
811
+ const portMatches = ports.match(/->(\d+(-\d+)?)\//g);
812
+ if (portMatches) {
813
+ const uniquePorts = [...new Set(portMatches.map(p => p.replace(/->\d+(-\d+)?\//, '')))];
814
+ uniquePorts.forEach(port => {
815
+ output += ` \x1b[33m${port}\x1b[0m`;
816
+ });
817
+ }
818
+ }
819
+ }
820
+ });
821
+
822
+ setCachedValue(this.cache, 'containers', output);
823
+ return output;
824
+ } catch {}
825
+
826
+ setCachedValue(this.cache, 'containers', '');
827
+ return '';
828
+ },
829
+
830
+ // Additional Linux system info functions
831
+ memory_available(settings) {
832
+ if (!IS_LINUX) return '';
833
+
834
+ try {
835
+ const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
836
+ const availableMatch = meminfo.match(/MemAvailable:\s+(\d+) kB/);
837
+ if (availableMatch) {
838
+ const availableGB = Math.round(parseInt(availableMatch[1]) / 1024 / 1024);
839
+ const color = colors[settings.colors.memory_available] || colors.blue;
840
+ const emoji = settings.display.show_emojis ? '🧠 ' : '';
841
+ return `${color}${emoji}${availableGB}GB available`;
842
+ }
843
+ } catch {}
844
+ return '';
845
+ },
846
+
847
+ swap_used(settings) {
848
+ if (!IS_LINUX) return '';
849
+
850
+ try {
851
+ const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
852
+ const swapTotalMatch = meminfo.match(/SwapTotal:\s+(\d+) kB/);
853
+ const swapFreeMatch = meminfo.match(/SwapFree:\s+(\d+) kB/);
854
+
855
+ if (swapTotalMatch && swapFreeMatch) {
856
+ const swapTotal = parseInt(swapTotalMatch[1]);
857
+ const swapFree = parseInt(swapFreeMatch[1]);
858
+ const swapUsed = swapTotal - swapFree;
859
+
860
+ if (swapTotal > 0) {
861
+ const swapUsedPercent = Math.round((swapUsed / swapTotal) * 100);
862
+ const swapUsedMB = Math.round(swapUsed / 1024);
863
+ const color = colors[settings.colors.swap_used] || colors.purple;
864
+ const emoji = settings.display.show_emojis ? '🔄 ' : '';
865
+ return `${color}${emoji}${swapUsedPercent}% (${swapUsedMB}MB) swap`;
866
+ }
867
+ }
868
+ } catch {}
869
+ return '';
870
+ },
871
+
872
+ load_average(settings) {
873
+ if (!IS_LINUX) return '';
874
+
875
+ try {
876
+ const loadavg = fs.readFileSync('/proc/loadavg', 'utf8');
877
+ const loads = loadavg.split(' ').slice(0, 3);
878
+ const color = colors[settings.colors.load_average] || colors.red;
879
+ const emoji = settings.display.show_emojis ? '⚖️ ' : '';
880
+ return `${color}${emoji}${loads.join(' ')}`;
881
+ } catch {}
882
+ return '';
883
+ },
884
+
885
+ users_logged_in(settings) {
886
+ if (!IS_LINUX) return '';
887
+
888
+ try {
889
+ const who = execCommand('who');
890
+ const users = who.split('\n').filter(line => line.trim()).length;
891
+ if (users > 0) {
892
+ const color = colors[settings.colors.users_logged_in] || colors.cyan;
893
+ const emoji = settings.display.show_emojis ? '👥 ' : '';
894
+ return `${color}${emoji}${users} users`;
895
+ }
896
+ } catch {}
897
+ return '';
898
+ },
899
+
900
+ network_interfaces(settings) {
901
+ const cached = getCachedValue(this.cache, 'network_interfaces', settings);
902
+ if (cached) return cached;
903
+
904
+ if (!IS_LINUX) {
905
+ setCachedValue(this.cache, 'network_interfaces', '');
906
+ return '';
907
+ }
908
+
909
+ try {
910
+ const interfaces = os.networkInterfaces();
911
+ const activeInterfaces = [];
912
+
913
+ for (const [name, addrs] of Object.entries(interfaces)) {
914
+ if (name !== 'lo') { // Skip loopback
915
+ const ipv4Addr = addrs.find(addr => addr.family === 'IPv4' && !addr.internal);
916
+ if (ipv4Addr) {
917
+ activeInterfaces.push(name);
918
+ }
919
+ }
920
+ }
921
+
922
+ if (activeInterfaces.length > 0) {
923
+ const color = colors[settings.colors.network_interfaces] || colors.yellow;
924
+ const emoji = settings.display.show_emojis ? '🌐 ' : '';
925
+ const result = `${color}${emoji}${activeInterfaces.join(' ')}`;
926
+ setCachedValue(this.cache, 'network_interfaces', result);
927
+ return result;
928
+ }
929
+ } catch {}
930
+
931
+ setCachedValue(this.cache, 'network_interfaces', '');
932
+ return '';
933
+ },
934
+
935
+ mount_points(settings) {
936
+ const cached = getCachedValue(this.cache, 'mount_points', settings);
937
+ if (cached) return cached;
938
+
939
+ if (!IS_LINUX) {
940
+ setCachedValue(this.cache, 'mount_points', '');
941
+ return '';
942
+ }
943
+
944
+ try {
945
+ const df = execCommand('df -h');
946
+ const lines = df.split('\n').slice(1); // Skip header
947
+ const mountPoints = [];
948
+
949
+ lines.forEach(line => {
950
+ const parts = line.trim().split(/\s+/);
951
+ if (parts.length >= 6) {
952
+ const mountPoint = parts[5];
953
+ const usage = parts[4];
954
+ if (!mountPoint.startsWith('/dev') && !mountPoint.startsWith('/proc') &&
955
+ !mountPoint.startsWith('/sys') && mountPoint !== '/') {
956
+ mountPoints.push(`${mountPoint}(${usage})`);
957
+ }
958
+ }
959
+ });
960
+
961
+ if (mountPoints.length > 0) {
962
+ const color = colors[settings.colors.mount_points] || colors.gray;
963
+ const emoji = settings.display.show_emojis ? '📂 ' : '';
964
+ const result = `${color}${emoji}${mountPoints.slice(0, 3).join(' ')}`;
965
+ setCachedValue(this.cache, 'mount_points', result);
966
+ return result;
967
+ }
968
+ } catch {}
969
+
970
+ setCachedValue(this.cache, 'mount_points', '');
971
+ return '';
972
+ },
973
+
974
+ services_running(settings) {
975
+ const cached = getCachedValue(this.cache, 'services_running', settings);
976
+ if (cached) return cached;
977
+
978
+ if (!IS_LINUX) {
979
+ setCachedValue(this.cache, 'services_running', '');
980
+ return '';
981
+ }
982
+
983
+ try {
984
+ let serviceCount = 0;
985
+
986
+ // Try systemctl first
987
+ if (commandExists('systemctl')) {
988
+ const services = execCommand('systemctl list-units --type=service --state=running --no-pager');
989
+ serviceCount = services.split('\n').filter(line => line.includes('.service')).length;
990
+ } else if (commandExists('service')) {
991
+ // Fallback for older systems
992
+ const services = execCommand('service --status-all');
993
+ serviceCount = services.split('\n').filter(line => line.includes('+')).length;
994
+ }
995
+
996
+ if (serviceCount > 0) {
997
+ const color = colors[settings.colors.services_running] || colors.green;
998
+ const emoji = settings.display.show_emojis ? '⚙️ ' : '';
999
+ const result = `${color}${emoji}${serviceCount} services`;
1000
+ setCachedValue(this.cache, 'services_running', result);
1001
+ return result;
1002
+ }
1003
+ } catch {}
1004
+
1005
+ setCachedValue(this.cache, 'services_running', '');
1006
+ return '';
1007
+ },
1008
+
1009
+ temperature(settings) {
1010
+ const cached = getCachedValue(this.cache, 'temperature', settings);
1011
+ if (cached) return cached;
1012
+
1013
+ if (!IS_LINUX) {
1014
+ setCachedValue(this.cache, 'temperature', '');
1015
+ return '';
1016
+ }
1017
+
1018
+ try {
1019
+ // Try different temperature sources
1020
+ const tempSources = [
1021
+ '/sys/class/thermal/thermal_zone0/temp',
1022
+ '/sys/class/hwmon/hwmon0/temp1_input',
1023
+ '/sys/class/hwmon/hwmon1/temp1_input'
1024
+ ];
1025
+
1026
+ for (const source of tempSources) {
1027
+ if (fs.existsSync(source)) {
1028
+ const temp = fs.readFileSync(source, 'utf8').trim();
1029
+ const tempC = Math.round(parseInt(temp) / 1000);
1030
+
1031
+ if (tempC > 0 && tempC < 150) { // Reasonable temperature range
1032
+ const color = tempC > 70 ? colors.red : tempC > 50 ? colors.yellow : colors.green;
1033
+ const emoji = settings.display.show_emojis ? '🌡️ ' : '';
1034
+ const result = `${color}${emoji}${tempC}°C`;
1035
+ setCachedValue(this.cache, 'temperature', result);
1036
+ return result;
1037
+ }
1038
+ }
1039
+ }
1040
+ } catch {}
1041
+
1042
+ setCachedValue(this.cache, 'temperature', '');
1043
+ return '';
1044
+ },
1045
+
1046
+ battery(settings) {
1047
+ const cached = getCachedValue(this.cache, 'battery', settings);
1048
+ if (cached) return cached;
1049
+
1050
+ if (!IS_LINUX) {
1051
+ setCachedValue(this.cache, 'battery', '');
1052
+ return '';
1053
+ }
1054
+
1055
+ try {
1056
+ const batteryPath = '/sys/class/power_supply/BAT0';
1057
+ const capacityPath = `${batteryPath}/capacity`;
1058
+ const statusPath = `${batteryPath}/status`;
1059
+
1060
+ if (fs.existsSync(capacityPath)) {
1061
+ const capacity = fs.readFileSync(capacityPath, 'utf8').trim();
1062
+ const status = fs.existsSync(statusPath) ?
1063
+ fs.readFileSync(statusPath, 'utf8').trim() : 'Unknown';
1064
+
1065
+ const batteryPercent = parseInt(capacity);
1066
+ const isCharging = status === 'Charging';
1067
+ const color = batteryPercent < 20 ? colors.red :
1068
+ batteryPercent < 50 ? colors.yellow : colors.green;
1069
+ const emoji = settings.display.show_emojis ?
1070
+ (isCharging ? '🔌 ' : '🔋 ') : '';
1071
+ const result = `${color}${emoji}${batteryPercent}%${isCharging ? '+' : ''}`;
1072
+ setCachedValue(this.cache, 'battery', result);
1073
+ return result;
1074
+ }
1075
+ } catch {}
1076
+
1077
+ setCachedValue(this.cache, 'battery', '');
1078
+ return '';
1079
+ },
1080
+
1081
+ screen_resolution(settings) {
1082
+ if (!IS_LINUX) return '';
1083
+
1084
+ try {
1085
+ if (process.env.DISPLAY) {
1086
+ const xrandr = execCommand('xrandr');
1087
+ const resolutionMatch = xrandr.match(/(\d+x\d+)\+\d+\+\d+/);
1088
+ if (resolutionMatch) {
1089
+ const color = colors[settings.colors.screen_resolution] || colors.blue;
1090
+ const emoji = settings.display.show_emojis ? '🖥️ ' : '';
1091
+ return `${color}${emoji}${resolutionMatch[1]}`;
1092
+ }
1093
+ }
1094
+ } catch {}
1095
+ return '';
1096
+ }
1097
+ };
1098
+
1099
+ // Main function to display system info
1100
+ async function displaySystemInfo() {
1101
+ const settings = loadSettings();
1102
+ const cache = loadCache();
1103
+ const context = { cache, settings };
1104
+
1105
+ // Check if we need IP info and it's not cached
1106
+ const allKeys = settings.display_order.flat();
1107
+ const needIPInfo = allKeys.some(key => ['ip', 'isp', 'domain', 'city'].includes(key));
1108
+ const cachedIPInfo = getCachedValue(cache, 'ipInfo', settings);
1109
+
1110
+ if (needIPInfo && !cachedIPInfo) {
1111
+ context.ipInfo = await fetchIPInfo(settings);
1112
+ setCachedValue(cache, 'ipInfo', context.ipInfo);
1113
+ } else if (needIPInfo && cachedIPInfo) {
1114
+ context.ipInfo = cachedIPInfo;
1115
+ }
1116
+
1117
+ const lines = [];
1118
+
1119
+ for (const group of settings.display_order) {
1120
+ const lineItems = [];
1121
+
1122
+ for (const key of group) {
1123
+ if (infoFunctions[key]) {
1124
+ try {
1125
+ const info = await infoFunctions[key].call(context, settings);
1126
+ if (info && info.trim()) {
1127
+ lineItems.push(info);
1128
+ }
1129
+ } catch (error) {
1130
+ if (settings.advanced.debug) {
1131
+ console.error(`Error getting ${key}:`, error.message);
1132
+ }
1133
+ // Continue on errors unless debug mode
1134
+ }
1135
+ } else if (settings.advanced.debug) {
1136
+ console.error(`Unknown info function: ${key}`);
1137
+ }
1138
+ }
1139
+
1140
+ // Only add the line if it has content
1141
+ if (lineItems.length > 0) {
1142
+ lines.push(lineItems.join(' '));
1143
+ }
1144
+ }
1145
+
1146
+ // Show offline message if no network and IP info was requested but failed
1147
+ if (needIPInfo && (!context.ipInfo || Object.keys(context.ipInfo).length === 0) &&
1148
+ settings.network.show_offline_message && lines.length === 0) {
1149
+ const emoji = settings.display.show_emojis ? '❌ ' : '';
1150
+ lines.push(`${colors.red}${emoji}No internet connection`);
1151
+ }
1152
+
1153
+ // Save cache after all operations
1154
+ saveCache(cache);
1155
+
1156
+ // Output each line
1157
+ if (lines.length > 0) {
1158
+ lines.forEach(line => {
1159
+ console.log(line + colors.reset);
1160
+ });
1161
+ } else if (settings.advanced.debug) {
1162
+ console.log('No system information could be displayed');
1163
+ }
1164
+ }
1165
+
1166
+ // Settings management commands
1167
+ function handleSettingsCommand(args) {
1168
+ const settings = loadSettings();
1169
+
1170
+ if (args.includes('--settings-init')) {
1171
+ if (saveSettings(DEFAULT_SETTINGS)) {
1172
+ console.log('Settings initialized with defaults');
1173
+ } else {
1174
+ console.log('Failed to initialize settings');
1175
+ }
1176
+ return true;
1177
+ }
1178
+
1179
+ if (args.includes('--settings-show')) {
1180
+ console.log('Current settings:');
1181
+ console.log(JSON.stringify(settings, null, 2));
1182
+ return true;
1183
+ }
1184
+
1185
+ if (args.includes('--settings-reset')) {
1186
+ if (saveSettings(DEFAULT_SETTINGS)) {
1187
+ console.log('Settings reset to defaults');
1188
+ } else {
1189
+ console.log('Failed to reset settings');
1190
+ }
1191
+ return true;
1192
+ }
1193
+
1194
+ const cacheResetIndex = args.indexOf('--cache-clear');
1195
+ if (cacheResetIndex !== -1) {
1196
+ try {
1197
+ if (fs.existsSync(CACHE_FILE)) {
1198
+ fs.unlinkSync(CACHE_FILE);
1199
+ }
1200
+ console.log('Cache cleared');
1201
+ } catch (error) {
1202
+ console.error('Error clearing cache:', error.message);
1203
+ }
1204
+ return true;
1205
+ }
1206
+
1207
+ const setIndex = args.indexOf('--set');
1208
+ if (setIndex !== -1 && args[setIndex + 1] && args[setIndex + 2]) {
1209
+ const key = args[setIndex + 1];
1210
+ const value = args[setIndex + 2];
1211
+
1212
+ try {
1213
+ // Parse JSON value if it looks like JSON
1214
+ const parsedValue = value.startsWith('{') || value.startsWith('[') ?
1215
+ JSON.parse(value) : value;
1216
+
1217
+ // Set nested property using dot notation
1218
+ const keys = key.split('.');
1219
+ let current = settings;
1220
+ for (let i = 0; i < keys.length - 1; i++) {
1221
+ if (!current[keys[i]]) current[keys[i]] = {};
1222
+ current = current[keys[i]];
1223
+ }
1224
+ current[keys[keys.length - 1]] = parsedValue;
1225
+
1226
+ if (saveSettings(settings)) {
1227
+ console.log(`Setting ${key} = ${value}`);
1228
+ } else {
1229
+ console.log('Failed to save settings');
1230
+ }
1231
+ } catch (error) {
1232
+ console.error('Error setting value:', error.message);
1233
+ }
1234
+ return true;
1235
+ }
1236
+
1237
+ return false;
1238
+ }
1239
+
1240
+ // Installation function - Cross-platform compatible
1241
+ function installShellGreeting() {
1242
+ const homeDir = os.homedir();
1243
+
1244
+ let configDir, scriptPath;
1245
+ if (IS_WINDOWS) {
1246
+ configDir = path.join(homeDir, 'AppData', 'Local');
1247
+ scriptPath = path.join(configDir, 'systeminfo.js');
1248
+ } else {
1249
+ configDir = path.join(homeDir, '.config');
1250
+ scriptPath = path.join(configDir, 'systeminfo.js');
1251
+ }
1252
+
1253
+ const currentScript = path.resolve(__filename);
1254
+
1255
+ try {
1256
+ // Ensure config directory exists
1257
+ if (!fs.existsSync(configDir)) {
1258
+ fs.mkdirSync(configDir, { recursive: true });
1259
+ }
1260
+
1261
+ // Copy this script
1262
+ fs.copyFileSync(currentScript, scriptPath);
1263
+ if (!IS_WINDOWS) {
1264
+ fs.chmodSync(scriptPath, '755');
1265
+ }
1266
+
1267
+ if (IS_WINDOWS) {
1268
+ // Windows-specific installation
1269
+ console.log('Windows installation:');
1270
+ console.log('1. Script copied to:', scriptPath);
1271
+ console.log('2. To add to PowerShell profile, run:');
1272
+ console.log(` Add-Content $PROFILE "node '${scriptPath}'"`);
1273
+ console.log('3. To add to Command Prompt, create a batch file in your startup folder');
1274
+
1275
+ const startupBat = path.join(configDir, 'systeminfo-startup.bat');
1276
+ fs.writeFileSync(startupBat, `@echo off\nnode "${scriptPath}"\n`);
1277
+ console.log('4. Batch file created:', startupBat);
1278
+
1279
+ } else {
1280
+ // Unix-like installation
1281
+
1282
+ // Silence default login messages
1283
+ try {
1284
+ const hushLoginPath = path.join(homeDir, '.hushlogin');
1285
+ fs.writeFileSync(hushLoginPath, '');
1286
+ } catch {
1287
+ // Ignore permission errors
1288
+ }
1289
+
1290
+ // Add to bash
1291
+ const bashrcPath = path.join(homeDir, '.bashrc');
1292
+ const bashLine = `node ${scriptPath}`;
1293
+
1294
+ if (fs.existsSync(bashrcPath)) {
1295
+ const bashrc = fs.readFileSync(bashrcPath, 'utf8');
1296
+ if (!bashrc.includes('systeminfo.js')) {
1297
+ fs.appendFileSync(bashrcPath, `\n${bashLine}\n`);
1298
+ }
1299
+ } else {
1300
+ fs.writeFileSync(bashrcPath, `${bashLine}\n`);
1301
+ }
1302
+
1303
+ // Add to zsh (common on Mac)
1304
+ const zshrcPath = path.join(homeDir, '.zshrc');
1305
+ if (fs.existsSync(zshrcPath)) {
1306
+ const zshrc = fs.readFileSync(zshrcPath, 'utf8');
1307
+ if (!zshrc.includes('systeminfo.js')) {
1308
+ fs.appendFileSync(zshrcPath, `\n${bashLine}\n`);
1309
+ }
1310
+ }
1311
+
1312
+ // Add to fish if config exists
1313
+ const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish');
1314
+ if (fs.existsSync(fishConfigPath)) {
1315
+ const fishConfig = fs.readFileSync(fishConfigPath, 'utf8');
1316
+ if (!fishConfig.includes('systeminfo.js')) {
1317
+ fs.appendFileSync(fishConfigPath, `\nset -U fish_greeting ""\n${bashLine}\n`);
1318
+ }
1319
+ }
1320
+
1321
+ // Add to nushell if config exists
1322
+ const nushellConfigPath = path.join(homeDir, '.config', 'nushell', 'config.nu');
1323
+ if (fs.existsSync(nushellConfigPath)) {
1324
+ const nushellConfig = fs.readFileSync(nushellConfigPath, 'utf8');
1325
+ if (!nushellConfig.includes('systeminfo.js')) {
1326
+ fs.appendFileSync(nushellConfigPath, `\n$env.config.show_banner = false\n${bashLine}\n`);
1327
+ }
1328
+ }
1329
+ }
1330
+
1331
+ console.log('Shell greeting installation completed!');
1332
+
1333
+ } catch (error) {
1334
+ console.error('Error installing shell greeting:', error.message);
1335
+ process.exit(1);
1336
+ }
1337
+ }
1338
+
1339
+ // Main execution
1340
+ async function main() {
1341
+ const args = process.argv.slice(2);
1342
+
1343
+ // Handle settings commands
1344
+ if (handleSettingsCommand(args)) {
1345
+ return;
1346
+ }
1347
+
1348
+ // Check for --install argument
1349
+ if (args.includes('--install')) {
1350
+ installShellGreeting();
1351
+ return;
1352
+ }
1353
+
1354
+ // Check for --help argument
1355
+ if (args.includes('--help') || args.includes('-h')) {
1356
+ console.log(`
1357
+ System Info Script - Node.js Version
1358
+
1359
+ Usage:
1360
+ node systeminfo.js [options]
1361
+
1362
+ Options:
1363
+ --help, -h Show this help message
1364
+ --install Install as shell greeting
1365
+ --settings-init Initialize settings file with defaults
1366
+ --settings-show Display current settings
1367
+ --settings-reset Reset settings to defaults
1368
+ --cache-clear Clear the cache file
1369
+ --set <key> <value> Set a configuration value (use dot notation)
1370
+
1371
+ Examples:
1372
+ node systeminfo.js
1373
+ node systeminfo.js --install
1374
+ node systeminfo.js --set display.show_emojis false
1375
+ node systeminfo.js --set colors.user blue
1376
+ node systeminfo.js --set display_order '[["user","hostname"],["disk_used","ram_used"]]'
1377
+
1378
+ Settings file: ${SETTINGS_FILE}
1379
+ Cache file: ${CACHE_FILE}
1380
+
1381
+ Platform: ${IS_WINDOWS ? 'Windows' : IS_MAC ? 'macOS' : IS_LINUX ? 'Linux' : 'Unknown'}
1382
+
1383
+ Available display blocks:
1384
+ Basic: user, hostname, uptime, shell, os, kernel, device
1385
+ Resources: disk_used, ram_used, memory_available, swap_used, top_process
1386
+ Network: ip, iplocal, city, domain, isp, network_interfaces
1387
+ Hardware: cpu, gpu, temperature, battery, screen_resolution
1388
+ System: load_average, users_logged_in, mount_points, services_running
1389
+ Tools: pacman, ports, containers
1390
+
1391
+ Available colors:
1392
+ red, orange, yellow, green, blue, cyan, purple, magenta, gray, lightblue
1393
+ (use "multicolor" for ports to get rainbow effect)
1394
+
1395
+ Display Format:
1396
+ The script organizes information into 4 logical categories:
1397
+ - Line 1: Device Info - user, hostname, OS, CPU, GPU, device model, kernel
1398
+ - Line 2: Dynamic Stats - disk usage, RAM, top process, uptime, temperature, battery, load average
1399
+ - Line 3: Network - IP addresses, location, domain, ISP
1400
+ - Line 4: System Apps - shell, package managers, open ports, containers, running services
1401
+
1402
+ To customize grouping, modify display_order as an array of arrays:
1403
+ --set display_order '[["user","hostname","os"],["disk_used","ram_used"],["ip","city"],["shell","pacman"]]'
1404
+
1405
+ Linux-specific features:
1406
+ - Temperature monitoring from thermal zones
1407
+ - Battery status and charging indication
1408
+ - System load averages
1409
+ - Swap usage monitoring
1410
+ - Active service count
1411
+ - Mount point information
1412
+ - Memory availability from /proc/meminfo
1413
+ - Screen resolution via xrandr
1414
+ `);
1415
+ return;
1416
+ }
1417
+
1418
+ // Display system info
1419
+ await displaySystemInfo();
1420
+ }
1421
+
1422
+ // Run the script
1423
+ if (require.main === module) {
1424
+ main().catch(error => {
1425
+ console.error('Error:', error.message);
1426
+ process.exit(1);
1427
+ });
1428
+ }
1429
+
1430
+ module.exports = { displaySystemInfo, installShellGreeting };