about-system 0.0.18 → 0.0.21
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.
- package/README.md +99 -207
- package/dist/about-system-cli.d.ts +0 -0
- package/dist/about-system-cli.js +113 -205
- package/dist/about-system-cli.js.map +1 -1
- package/dist/index.d.ts +3 -9
- package/dist/index.js +4 -4
- package/dist/index.js.map +0 -0
- package/dist/system-info-api-DAF2cjeE.js +837 -0
- package/dist/system-info-api-DAF2cjeE.js.map +1 -0
- package/dist/system-info-api.d.ts +313 -194
- package/dist/system-info-api.js +9 -791
- package/dist/system-info-api.js.map +1 -1
- package/package.json +6 -6
- package/src/about-system-cli.ts +124 -221
- package/src/cache/cache-config.ts +68 -0
- package/src/cache/cache.ts +95 -0
- package/src/index.ts +0 -0
- package/src/info/hardware.ts +173 -0
- package/src/info/memory.ts +215 -0
- package/src/info/network.ts +213 -0
- package/src/info/platform.ts +145 -0
- package/src/info/process.ts +72 -0
- package/src/info/settings.ts +209 -0
- package/src/info/software.ts +192 -0
- package/src/info/system-status.ts +152 -0
- package/src/system-info-api.ts +78 -1117
- package/src/systeminfo-types.d.ts +0 -0
- package/src/types/internal-types.ts +21 -0
- package/src/utils/command.ts +47 -0
- package/src/utils/network.ts +58 -0
- package/src/utils/platform.ts +13 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache management utilities
|
|
3
|
+
* @module cache
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { CACHE_FILE, CACHE_DURATION } from "./cache-config";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a cached value with timestamp
|
|
11
|
+
* @interface CacheEntry
|
|
12
|
+
*/
|
|
13
|
+
export interface CacheEntry {
|
|
14
|
+
/** The cached value */
|
|
15
|
+
value: any;
|
|
16
|
+
/** Unix timestamp when the value was cached */
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cache storage structure
|
|
22
|
+
* @interface Cache
|
|
23
|
+
*/
|
|
24
|
+
export interface Cache {
|
|
25
|
+
[key: string]: CacheEntry;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Loads cache from disk
|
|
30
|
+
* @returns {Cache} Cached data or empty object if cache doesn't exist or is corrupted
|
|
31
|
+
*/
|
|
32
|
+
export function loadCache(): Cache {
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
35
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// Corrupted cache - return empty object
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Saves cache to disk
|
|
45
|
+
* @param {Cache} cache - Cache object to save
|
|
46
|
+
*/
|
|
47
|
+
export function saveCache(cache: Cache): void {
|
|
48
|
+
try {
|
|
49
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Silently fail if can't write cache
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Checks if a cache entry is still valid based on configured duration
|
|
57
|
+
* @param {CacheEntry} cacheEntry - The cache entry to validate
|
|
58
|
+
* @param {string} key - The key name to determine cache duration
|
|
59
|
+
* @returns {boolean} True if cache is still valid
|
|
60
|
+
*/
|
|
61
|
+
export function isCacheValid(cacheEntry: CacheEntry, key: string): boolean {
|
|
62
|
+
if (!cacheEntry || !cacheEntry.timestamp) return false;
|
|
63
|
+
const age = Date.now() - cacheEntry.timestamp;
|
|
64
|
+
const maxAge = CACHE_DURATION[key as keyof typeof CACHE_DURATION] || 60000;
|
|
65
|
+
return age < maxAge;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves a value from cache if valid
|
|
70
|
+
* @param {Cache} cache - Cache object
|
|
71
|
+
* @param {string} key - Key to retrieve
|
|
72
|
+
* @returns {any} Cached value or null if not found/expired
|
|
73
|
+
*/
|
|
74
|
+
export function getCachedValue(cache: Cache, key: string): any {
|
|
75
|
+
if (!cache[key]) return null;
|
|
76
|
+
const cacheEntry = cache[key];
|
|
77
|
+
if (!isCacheValid(cacheEntry, key)) {
|
|
78
|
+
delete cache[key];
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return cacheEntry.value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Stores a value in cache with current timestamp
|
|
86
|
+
* @param {Cache} cache - Cache object
|
|
87
|
+
* @param {string} key - Key to store under
|
|
88
|
+
* @param {any} value - Value to cache
|
|
89
|
+
*/
|
|
90
|
+
export function setCachedValue(cache: Cache, key: string, value: any): void {
|
|
91
|
+
cache[key] = {
|
|
92
|
+
value,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardware-related system information functions
|
|
3
|
+
* @module info/hardware
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from "os";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import type { InfoContext } from "../types/internal-types";
|
|
9
|
+
import { IS_WINDOWS, IS_LINUX } from "../utils/platform";
|
|
10
|
+
import { execCommand } from "../utils/command";
|
|
11
|
+
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gets CPU model name and specifications
|
|
15
|
+
* Uses platform-specific commands (wmic/lscpu/cpuinfo)
|
|
16
|
+
* Automatically strips "with Radeon Graphics" and similar suffixes
|
|
17
|
+
* @param context - Info context with cache
|
|
18
|
+
* @returns CPU model string or empty string
|
|
19
|
+
* @example "Intel Core i7-12700K", "AMD Ryzen 9 5900HX", "Apple M2 Pro"
|
|
20
|
+
*/
|
|
21
|
+
export function cpu(context: InfoContext): string {
|
|
22
|
+
const cached = getCachedValue(context.cache, "cpu");
|
|
23
|
+
if (cached) return cached;
|
|
24
|
+
|
|
25
|
+
let cpuName = "";
|
|
26
|
+
|
|
27
|
+
if (IS_WINDOWS) {
|
|
28
|
+
try {
|
|
29
|
+
const wmic = execCommand("wmic cpu get name /format:list");
|
|
30
|
+
const nameMatch = wmic.match(/Name=(.+)/);
|
|
31
|
+
if (nameMatch) {
|
|
32
|
+
cpuName = nameMatch[1].trim();
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
|
|
36
|
+
if (!cpuName) {
|
|
37
|
+
try {
|
|
38
|
+
const ps = execCommand(
|
|
39
|
+
'powershell.exe -Command "Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Name"'
|
|
40
|
+
);
|
|
41
|
+
if (ps.trim()) {
|
|
42
|
+
cpuName = ps.trim();
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
} else if (IS_LINUX) {
|
|
47
|
+
try {
|
|
48
|
+
const lscpu = execCommand("lscpu");
|
|
49
|
+
const modelMatch = lscpu.match(/Model name:\s*([^\n,]+)/);
|
|
50
|
+
if (modelMatch) {
|
|
51
|
+
cpuName = modelMatch[1].trim();
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
if (!cpuName) {
|
|
56
|
+
try {
|
|
57
|
+
const cpuInfo = fs.readFileSync("/proc/cpuinfo", "utf8");
|
|
58
|
+
const modelMatch = cpuInfo.match(/model name\s*:\s*([^\n]+)/);
|
|
59
|
+
const hardwareMatch = cpuInfo.match(/Hardware\s*:\s*([^\n]+)/);
|
|
60
|
+
|
|
61
|
+
if (modelMatch) {
|
|
62
|
+
cpuName = modelMatch[1].trim();
|
|
63
|
+
} else if (hardwareMatch) {
|
|
64
|
+
cpuName = hardwareMatch[1].trim();
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const cpus = os.cpus();
|
|
70
|
+
if (cpus.length > 0) {
|
|
71
|
+
cpuName = cpus[0].model.trim().replace(/[\r\n]+/g, " ");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!cpuName) {
|
|
76
|
+
setCachedValue(context.cache, "cpu", "");
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cpuName = cpuName.trim().replace(/with .*/, "");
|
|
81
|
+
|
|
82
|
+
setCachedValue(context.cache, "cpu", cpuName);
|
|
83
|
+
return cpuName;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets graphics card model name
|
|
88
|
+
* Extracts GPU info from lspci (Linux) or WMI (Windows)
|
|
89
|
+
* Filters out basic/generic display adapters
|
|
90
|
+
* @param context - Info context with cache
|
|
91
|
+
* @returns GPU model string or empty string
|
|
92
|
+
* @example "NVIDIA GeForce RTX 4070", "AMD Radeon RX 6800 XT"
|
|
93
|
+
*/
|
|
94
|
+
export function gpu(context: InfoContext): string {
|
|
95
|
+
const cached = getCachedValue(context.cache, "gpu");
|
|
96
|
+
if (cached !== null) return cached;
|
|
97
|
+
|
|
98
|
+
if (IS_WINDOWS) {
|
|
99
|
+
try {
|
|
100
|
+
const wmic = execCommand(
|
|
101
|
+
"wmic path win32_VideoController get name /format:list"
|
|
102
|
+
);
|
|
103
|
+
const nameMatch = wmic.match(/Name=(.+)/);
|
|
104
|
+
if (nameMatch) {
|
|
105
|
+
const gpu = nameMatch[1].trim();
|
|
106
|
+
if (gpu && gpu !== "" && !gpu.includes("Microsoft Basic")) {
|
|
107
|
+
setCachedValue(context.cache, "gpu", gpu);
|
|
108
|
+
return gpu;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const ps = execCommand(
|
|
115
|
+
"powershell.exe -Command \"Get-WmiObject -Class Win32_VideoController | Where-Object {$_.Name -notlike '*Microsoft Basic*'} | Select-Object -First 1 -ExpandProperty Name\""
|
|
116
|
+
);
|
|
117
|
+
if (ps.trim()) {
|
|
118
|
+
const gpu = ps.trim();
|
|
119
|
+
setCachedValue(context.cache, "gpu", gpu);
|
|
120
|
+
return gpu;
|
|
121
|
+
}
|
|
122
|
+
} catch {}
|
|
123
|
+
} else if (IS_LINUX) {
|
|
124
|
+
try {
|
|
125
|
+
const lspci = execCommand("lspci");
|
|
126
|
+
const gpuMatch = lspci.match(
|
|
127
|
+
/VGA.*?(RTX|GeForce|AMD|Intel|NVIDIA)[^\n]*/i
|
|
128
|
+
);
|
|
129
|
+
if (gpuMatch) {
|
|
130
|
+
let gpu = gpuMatch[0];
|
|
131
|
+
|
|
132
|
+
const bracketMatch = gpu.match(/\[([^\]]+)\]/);
|
|
133
|
+
if (bracketMatch) {
|
|
134
|
+
gpu = bracketMatch[1];
|
|
135
|
+
} else {
|
|
136
|
+
gpu = gpu
|
|
137
|
+
.replace(/^.*VGA[^:]*:\s*/, "")
|
|
138
|
+
.replace(/\s*\(.*\)$/, "")
|
|
139
|
+
.trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (gpu) {
|
|
143
|
+
setCachedValue(context.cache, "gpu", gpu);
|
|
144
|
+
return gpu;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setCachedValue(context.cache, "gpu", "");
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets screen resolution from X11 display
|
|
156
|
+
* Uses xrandr command (Linux with X11/Xorg only)
|
|
157
|
+
* @returns Resolution in WIDTHxHEIGHT format or empty string
|
|
158
|
+
* @example "1920x1080", "2560x1440", "3840x2160"
|
|
159
|
+
*/
|
|
160
|
+
export function screen_resolution(): string {
|
|
161
|
+
if (!IS_LINUX) return "";
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
if (process.env.DISPLAY) {
|
|
165
|
+
const xrandr = execCommand("xrandr");
|
|
166
|
+
const resolutionMatch = xrandr.match(/(\d+x\d+)\+\d+\+\d+/);
|
|
167
|
+
if (resolutionMatch) {
|
|
168
|
+
return resolutionMatch[1];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory and disk-related system information functions
|
|
3
|
+
* @module info/memory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from "os";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import type { InfoContext } from "../types/internal-types";
|
|
9
|
+
import { IS_LINUX } from "../utils/platform";
|
|
10
|
+
import { execCommand } from "../utils/command";
|
|
11
|
+
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gets memory usage in gigabytes
|
|
15
|
+
* Reads from /proc/meminfo on Linux, falls back to os.totalmem()
|
|
16
|
+
* @param context - Info context with cache
|
|
17
|
+
* @returns Memory usage as "used/total GB"
|
|
18
|
+
* @example "12/32GB", "6/16GB"
|
|
19
|
+
*/
|
|
20
|
+
export function ram_used(context: InfoContext): string {
|
|
21
|
+
const cached = getCachedValue(context.cache, "ram_used");
|
|
22
|
+
if (cached) return cached;
|
|
23
|
+
|
|
24
|
+
if (IS_LINUX) {
|
|
25
|
+
try {
|
|
26
|
+
const meminfo = fs.readFileSync("/proc/meminfo", "utf8");
|
|
27
|
+
const totalMatch = meminfo.match(/MemTotal:\s+(\d+) kB/);
|
|
28
|
+
const freeMatch = meminfo.match(/MemFree:\s+(\d+) kB/);
|
|
29
|
+
|
|
30
|
+
if (totalMatch && freeMatch) {
|
|
31
|
+
const totalMB = Math.round(parseInt(totalMatch[1]) / 1024);
|
|
32
|
+
const freeMB = Math.round(parseInt(freeMatch[1]) / 1024);
|
|
33
|
+
const usedMB = totalMB - freeMB;
|
|
34
|
+
|
|
35
|
+
const totalGB = Math.round(totalMB / 1024);
|
|
36
|
+
const usedGB = Math.round(usedMB / 1024);
|
|
37
|
+
|
|
38
|
+
const result = `${usedGB}/${totalGB}GB`;
|
|
39
|
+
setCachedValue(context.cache, "ram_used", result);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const totalMem = os.totalmem();
|
|
46
|
+
const freeMem = os.freemem();
|
|
47
|
+
const usedMem = totalMem - freeMem;
|
|
48
|
+
|
|
49
|
+
const totalGB = Math.round(totalMem / (1024 * 1024 * 1024));
|
|
50
|
+
const usedGB = Math.round(usedMem / (1024 * 1024 * 1024));
|
|
51
|
+
|
|
52
|
+
const result = `${usedGB}/${totalGB}GB`;
|
|
53
|
+
setCachedValue(context.cache, "ram_used", result);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets available memory in gigabytes
|
|
59
|
+
* Reads MemAvailable from /proc/meminfo (Linux only)
|
|
60
|
+
* @returns Available memory with "GB available" suffix or empty string
|
|
61
|
+
* @example "12GB available", "4GB available"
|
|
62
|
+
*/
|
|
63
|
+
export function memory_available(): string {
|
|
64
|
+
if (!IS_LINUX) return "";
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const meminfo = fs.readFileSync("/proc/meminfo", "utf8");
|
|
68
|
+
const availableMatch = meminfo.match(/MemAvailable:\s+(\d+) kB/);
|
|
69
|
+
if (availableMatch) {
|
|
70
|
+
const availableGB = Math.round(parseInt(availableMatch[1]) / 1024 / 1024);
|
|
71
|
+
return `${availableGB}GB available`;
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets swap memory usage
|
|
79
|
+
* Calculates swap usage from /proc/meminfo (Linux only)
|
|
80
|
+
* @returns Swap usage as "percentage (size MB) swap" or empty string
|
|
81
|
+
* @example "15% (512MB) swap", "0% (0MB) swap"
|
|
82
|
+
*/
|
|
83
|
+
export function swap_used(): string {
|
|
84
|
+
if (!IS_LINUX) return "";
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const meminfo = fs.readFileSync("/proc/meminfo", "utf8");
|
|
88
|
+
const swapTotalMatch = meminfo.match(/SwapTotal:\s+(\d+) kB/);
|
|
89
|
+
const swapFreeMatch = meminfo.match(/SwapFree:\s+(\d+) kB/);
|
|
90
|
+
|
|
91
|
+
if (swapTotalMatch && swapFreeMatch) {
|
|
92
|
+
const swapTotal = parseInt(swapTotalMatch[1]);
|
|
93
|
+
const swapFree = parseInt(swapFreeMatch[1]);
|
|
94
|
+
const swapUsed = swapTotal - swapFree;
|
|
95
|
+
|
|
96
|
+
if (swapTotal > 0) {
|
|
97
|
+
const swapUsedPercent = Math.round((swapUsed / swapTotal) * 100);
|
|
98
|
+
const swapUsedMB = Math.round(swapUsed / 1024);
|
|
99
|
+
return `${swapUsedPercent}% (${swapUsedMB}MB) swap`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets root filesystem disk usage percentage
|
|
108
|
+
* Uses df command on Linux/Android
|
|
109
|
+
* @param context - Info context with cache
|
|
110
|
+
* @returns Percentage string or empty string
|
|
111
|
+
* @example "45%", "78%"
|
|
112
|
+
*/
|
|
113
|
+
export function disk_used(context: InfoContext): string {
|
|
114
|
+
const cached = getCachedValue(context.cache, "disk_used");
|
|
115
|
+
if (cached !== null) return cached;
|
|
116
|
+
|
|
117
|
+
if (IS_LINUX) {
|
|
118
|
+
try {
|
|
119
|
+
const df = execCommand("df -h");
|
|
120
|
+
let diskUsage = "";
|
|
121
|
+
|
|
122
|
+
if (df.includes("/storage/emulated")) {
|
|
123
|
+
const match = df.match(/\s+(\d+%)\s+\/storage\/emulated/);
|
|
124
|
+
diskUsage = match ? match[1] : "";
|
|
125
|
+
} else {
|
|
126
|
+
const lines = df.split("\n");
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
if (line.trim().endsWith(" /")) {
|
|
129
|
+
const parts = line.trim().split(/\s+/);
|
|
130
|
+
const percentIndex = parts.findIndex((part) => part.includes("%"));
|
|
131
|
+
if (percentIndex !== -1) {
|
|
132
|
+
diskUsage = parts[percentIndex];
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!diskUsage) {
|
|
139
|
+
const rootMatch = df.match(/(\d+%)\s+\/\s*$/m);
|
|
140
|
+
diskUsage = rootMatch ? rootMatch[1] : "";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setCachedValue(context.cache, "disk_used", diskUsage);
|
|
145
|
+
return diskUsage;
|
|
146
|
+
} catch {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setCachedValue(context.cache, "disk_used", "");
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Gets system load averages
|
|
155
|
+
* Reads 1, 5, and 15 minute load averages from /proc/loadavg (Linux only)
|
|
156
|
+
* @returns Space-separated load averages (1m 5m 15m) or empty string
|
|
157
|
+
* @example "0.52 0.58 0.59", "2.10 1.95 1.88"
|
|
158
|
+
*/
|
|
159
|
+
export function load_average(): string {
|
|
160
|
+
if (!IS_LINUX) return "";
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const loadavg = fs.readFileSync("/proc/loadavg", "utf8");
|
|
164
|
+
const loads = loadavg.split(" ").slice(0, 3);
|
|
165
|
+
return loads.join(" ");
|
|
166
|
+
} catch {}
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Gets mounted filesystem information
|
|
172
|
+
* Lists non-system mount points with usage from df (Linux only)
|
|
173
|
+
* Excludes /, /dev, /proc, and /sys mounts
|
|
174
|
+
* @param context - Info context with cache
|
|
175
|
+
* @returns Space-separated mount points with usage or empty string
|
|
176
|
+
* @example "/home(45%) /mnt/data(78%)", "/media/usb(12%)"
|
|
177
|
+
*/
|
|
178
|
+
export function mount_points(context: InfoContext): string {
|
|
179
|
+
const cached = getCachedValue(context.cache, "mount_points");
|
|
180
|
+
if (cached !== null) return cached;
|
|
181
|
+
|
|
182
|
+
if (!IS_LINUX) {
|
|
183
|
+
setCachedValue(context.cache, "mount_points", "");
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const df = execCommand("df -h");
|
|
189
|
+
const lines = df.split("\n").slice(1);
|
|
190
|
+
const mountPoints: string[] = [];
|
|
191
|
+
|
|
192
|
+
lines.forEach((line) => {
|
|
193
|
+
const parts = line.trim().split(/\s+/);
|
|
194
|
+
if (parts.length >= 6) {
|
|
195
|
+
const mountPoint = parts[5];
|
|
196
|
+
const usage = parts[4];
|
|
197
|
+
if (
|
|
198
|
+
!mountPoint.startsWith("/dev") &&
|
|
199
|
+
!mountPoint.startsWith("/proc") &&
|
|
200
|
+
!mountPoint.startsWith("/sys") &&
|
|
201
|
+
mountPoint !== "/"
|
|
202
|
+
) {
|
|
203
|
+
mountPoints.push(`${mountPoint}(${usage})`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const result = mountPoints.slice(0, 3).join(" ");
|
|
209
|
+
setCachedValue(context.cache, "mount_points", result);
|
|
210
|
+
return result;
|
|
211
|
+
} catch {}
|
|
212
|
+
|
|
213
|
+
setCachedValue(context.cache, "mount_points", "");
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network-related system information functions
|
|
3
|
+
* @module info/network
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from "os";
|
|
7
|
+
import type { InfoContext } from "../types/internal-types";
|
|
8
|
+
import { IS_LINUX } from "../utils/platform";
|
|
9
|
+
import { execCommand } from "../utils/command";
|
|
10
|
+
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets public IP address from ipinfo.io
|
|
14
|
+
* @param context - Info context with cache and IP info
|
|
15
|
+
* @returns Public IPv4 address or empty string
|
|
16
|
+
* @example "203.0.113.42"
|
|
17
|
+
*/
|
|
18
|
+
export async function ip(context: InfoContext): Promise<string> {
|
|
19
|
+
const cached = getCachedValue(context.cache, "ip");
|
|
20
|
+
if (cached) return cached;
|
|
21
|
+
|
|
22
|
+
if (!context.ipInfo) {
|
|
23
|
+
setCachedValue(context.cache, "ip", "");
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
const ip = context.ipInfo.ip || "";
|
|
27
|
+
setCachedValue(context.cache, "ip", ip);
|
|
28
|
+
return ip;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets local/private IP address(es)
|
|
33
|
+
* Tries ifconfig and ip commands on Linux, falls back to Node.js API
|
|
34
|
+
* @returns Space-separated local IP addresses (RFC 1918 ranges)
|
|
35
|
+
* @example "192.168.1.100" or "10.0.0.50 192.168.1.100"
|
|
36
|
+
*/
|
|
37
|
+
export function iplocal(): string {
|
|
38
|
+
if (IS_LINUX) {
|
|
39
|
+
try {
|
|
40
|
+
const ifconfig = execCommand("ifconfig 2>/dev/null");
|
|
41
|
+
const wlanMatch = ifconfig.match(
|
|
42
|
+
/wlan0[\s\S]*?inet (\d+\.\d+\.\d+\.\d+)/
|
|
43
|
+
);
|
|
44
|
+
if (wlanMatch) {
|
|
45
|
+
return wlanMatch[1];
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const ipAddr = execCommand("ip addr show 2>/dev/null");
|
|
51
|
+
const addresses: string[] = [];
|
|
52
|
+
const matches = ipAddr.matchAll(/inet (\d+\.\d+\.\d+\.\d+)\/\d+/g);
|
|
53
|
+
for (const match of matches) {
|
|
54
|
+
if (!match[1].startsWith("127.")) {
|
|
55
|
+
addresses.push(match[1]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (addresses.length > 0) {
|
|
59
|
+
return addresses.join(" ");
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const interfaces = os.networkInterfaces();
|
|
65
|
+
const addresses: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const name of Object.keys(interfaces)) {
|
|
68
|
+
for (const device of interfaces[name] || []) {
|
|
69
|
+
if (device.family === "IPv4" && !device.internal) {
|
|
70
|
+
addresses.push(device.address);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return addresses.join(" ");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets geographic city based on public IP
|
|
80
|
+
* @param context - Info context with IP geolocation data
|
|
81
|
+
* @returns City name or empty string
|
|
82
|
+
* @example "San Francisco", "New York", "London"
|
|
83
|
+
*/
|
|
84
|
+
export async function city(context: InfoContext): Promise<string> {
|
|
85
|
+
const cached = getCachedValue(context.cache, "city");
|
|
86
|
+
if (cached !== null) return cached;
|
|
87
|
+
|
|
88
|
+
if (!context.ipInfo || !context.ipInfo.city) {
|
|
89
|
+
setCachedValue(context.cache, "city", "");
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
const city = context.ipInfo.city;
|
|
93
|
+
setCachedValue(context.cache, "city", city);
|
|
94
|
+
return city;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gets reverse DNS hostname with HTTP prefix
|
|
99
|
+
* @param context - Info context with IP info
|
|
100
|
+
* @returns Domain with http:// prefix or empty string
|
|
101
|
+
* @example "http://example.com", "http://host-203-0-113-42.example.net"
|
|
102
|
+
*/
|
|
103
|
+
export async function domain(context: InfoContext): Promise<string> {
|
|
104
|
+
const cached = getCachedValue(context.cache, "domain");
|
|
105
|
+
if (cached !== null) return cached;
|
|
106
|
+
|
|
107
|
+
if (!context.ipInfo || !context.ipInfo.hostname) {
|
|
108
|
+
setCachedValue(context.cache, "domain", "");
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
const domain = `http://${context.ipInfo.hostname}`;
|
|
112
|
+
setCachedValue(context.cache, "domain", domain);
|
|
113
|
+
return domain;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Gets Internet Service Provider name
|
|
118
|
+
* Strips AS number prefix from organization string
|
|
119
|
+
* @param context - Info context with IP info
|
|
120
|
+
* @returns ISP name or empty string
|
|
121
|
+
* @example "Comcast Cable", "Verizon Business", "Cloudflare Inc"
|
|
122
|
+
*/
|
|
123
|
+
export async function isp(context: InfoContext): Promise<string> {
|
|
124
|
+
const cached = getCachedValue(context.cache, "isp");
|
|
125
|
+
if (cached !== null) return cached;
|
|
126
|
+
|
|
127
|
+
if (!context.ipInfo || !context.ipInfo.org) {
|
|
128
|
+
setCachedValue(context.cache, "isp", "");
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
const isp = context.ipInfo.org.split(" ").slice(1).join(" ");
|
|
132
|
+
setCachedValue(context.cache, "isp", isp);
|
|
133
|
+
return isp;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gets active network interface names
|
|
138
|
+
* Lists non-loopback interfaces with IPv4 addresses (Linux only)
|
|
139
|
+
* @param context - Info context with cache
|
|
140
|
+
* @returns Space-separated interface names or empty string
|
|
141
|
+
* @example "eth0 wlan0", "enp0s3"
|
|
142
|
+
*/
|
|
143
|
+
export function network_interfaces(context: InfoContext): string {
|
|
144
|
+
const cached = getCachedValue(context.cache, "network_interfaces");
|
|
145
|
+
if (cached !== null) return cached;
|
|
146
|
+
|
|
147
|
+
if (!IS_LINUX) {
|
|
148
|
+
setCachedValue(context.cache, "network_interfaces", "");
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const interfaces = os.networkInterfaces();
|
|
154
|
+
const activeInterfaces: string[] = [];
|
|
155
|
+
|
|
156
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
157
|
+
if (name !== "lo") {
|
|
158
|
+
const ipv4Addr = addrs?.find(
|
|
159
|
+
(addr) => addr.family === "IPv4" && !addr.internal
|
|
160
|
+
);
|
|
161
|
+
if (ipv4Addr) {
|
|
162
|
+
activeInterfaces.push(name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = activeInterfaces.join(" ");
|
|
168
|
+
setCachedValue(context.cache, "network_interfaces", result);
|
|
169
|
+
return result;
|
|
170
|
+
} catch {}
|
|
171
|
+
|
|
172
|
+
setCachedValue(context.cache, "network_interfaces", "");
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Gets open TCP ports with service names
|
|
177
|
+
* Uses lsof to find listening TCP ports (Linux only)
|
|
178
|
+
* @param context - Info context with cache
|
|
179
|
+
* @returns Space-separated port+process pairs or empty string
|
|
180
|
+
* @example "80http 443http 22ssh", "3000node 5432post"
|
|
181
|
+
*/
|
|
182
|
+
export function ports(context: InfoContext): string {
|
|
183
|
+
const cached = getCachedValue(context.cache, "ports");
|
|
184
|
+
if (cached !== null) return cached;
|
|
185
|
+
|
|
186
|
+
if (IS_LINUX) {
|
|
187
|
+
try {
|
|
188
|
+
const lsof = execCommand("lsof -nP -iTCP -sTCP:LISTEN");
|
|
189
|
+
const lines = lsof.split("\n").slice(1);
|
|
190
|
+
const ports = new Set<string>();
|
|
191
|
+
|
|
192
|
+
lines.forEach((line) => {
|
|
193
|
+
const parts = line.split(/\s+/);
|
|
194
|
+
if (parts.length >= 9) {
|
|
195
|
+
const address = parts[8];
|
|
196
|
+
const portMatch = address.match(/:(\d+)$/);
|
|
197
|
+
if (portMatch) {
|
|
198
|
+
const port = portMatch[1];
|
|
199
|
+
const process = parts[0].substring(0, 4);
|
|
200
|
+
ports.add(`${port}${process}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const result = Array.from(ports).join(" ");
|
|
206
|
+
setCachedValue(context.cache, "ports", result);
|
|
207
|
+
return result;
|
|
208
|
+
} catch {}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setCachedValue(context.cache, "ports", "");
|
|
212
|
+
return "";
|
|
213
|
+
}
|