about-system 0.0.17 → 0.0.19

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