about-system 0.0.40 → 0.0.45
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 +11 -1
- package/dist/about-system-cli.js +1 -1
- package/dist/gpu-geekbench.json +1 -0
- package/dist/index.js +1 -1
- package/dist/system-info-api-CChFyamn.js +2396 -0
- package/dist/system-info-api-CChFyamn.js.map +1 -0
- package/dist/system-info-api.d.ts +61 -17
- package/dist/system-info-api.js +1 -1
- package/package.json +3 -3
- package/src/info/hardware.ts +246 -34
- package/src/info/memory.ts +183 -53
- package/src/info/network.ts +6 -6
- package/src/info/platform.ts +14 -1
- package/src/info/process.ts +20 -4
- package/src/info/settings.ts +7 -2
- package/src/info/software.ts +15 -3
- package/src/info/system-status.ts +67 -3
- package/src/system-info-api.ts +8 -1
- package/src/systeminfo-types.d.ts +47 -8
- package/dist/system-info-api-J8OqC6YU.js +0 -2148
- package/dist/system-info-api-J8OqC6YU.js.map +0 -1
- /package/dist/{cpu-geekbench-1k.json → cpu-geekbench.json} +0 -0
package/src/info/memory.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import os from "os";
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import type { InfoContext } from "../types/internal-types";
|
|
9
|
-
import { IS_LINUX } from "../utils/platform";
|
|
9
|
+
import { IS_LINUX, IS_MAC } from "../utils/platform";
|
|
10
10
|
import { execCommand } from "../utils/command";
|
|
11
11
|
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
12
12
|
|
|
@@ -56,56 +56,101 @@ export function ram_used(context: InfoContext): string {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Gets available memory in gigabytes
|
|
59
|
-
* Reads MemAvailable from /proc/meminfo (Linux
|
|
59
|
+
* Reads MemAvailable from /proc/meminfo (Linux) or derives it from
|
|
60
|
+
* vm_stat's free+inactive pages (macOS)
|
|
60
61
|
* @returns Available memory with "GB available" suffix or empty string
|
|
61
62
|
* @example "12GB available", "4GB available"
|
|
62
63
|
*/
|
|
63
64
|
export function memory_available(): string {
|
|
64
|
-
if (
|
|
65
|
+
if (IS_LINUX) {
|
|
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
|
+
if (IS_MAC) {
|
|
78
|
+
try {
|
|
79
|
+
const vmStat = execCommand("vm_stat");
|
|
80
|
+
const pageSizeMatch = vmStat.match(/page size of (\d+) bytes/);
|
|
81
|
+
const freeMatch = vmStat.match(/Pages free:\s+(\d+)\./);
|
|
82
|
+
const inactiveMatch = vmStat.match(/Pages inactive:\s+(\d+)\./);
|
|
83
|
+
|
|
84
|
+
if (pageSizeMatch && freeMatch && inactiveMatch) {
|
|
85
|
+
const pageSize = parseInt(pageSizeMatch[1]);
|
|
86
|
+
const availablePages = parseInt(freeMatch[1]) + parseInt(inactiveMatch[1]);
|
|
87
|
+
const availableGB = Math.round((availablePages * pageSize) / (1024 * 1024 * 1024));
|
|
88
|
+
return `${availableGB}GB available`;
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
65
93
|
|
|
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
94
|
return "";
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
/**
|
|
78
98
|
* Gets swap memory usage
|
|
79
|
-
* Calculates swap usage from /proc/meminfo (Linux
|
|
99
|
+
* Calculates swap usage from /proc/meminfo (Linux) or `sysctl vm.swapusage`
|
|
100
|
+
* (macOS)
|
|
80
101
|
* @returns Swap usage as "percentage (size MB) swap" or empty string
|
|
81
102
|
* @example "15% (512MB) swap", "0% (0MB) swap"
|
|
82
103
|
*/
|
|
83
104
|
export function swap_used(): string {
|
|
84
|
-
if (
|
|
105
|
+
if (IS_LINUX) {
|
|
106
|
+
try {
|
|
107
|
+
const meminfo = fs.readFileSync("/proc/meminfo", "utf8");
|
|
108
|
+
const swapTotalMatch = meminfo.match(/SwapTotal:\s+(\d+) kB/);
|
|
109
|
+
const swapFreeMatch = meminfo.match(/SwapFree:\s+(\d+) kB/);
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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`;
|
|
111
|
+
if (swapTotalMatch && swapFreeMatch) {
|
|
112
|
+
const swapTotal = parseInt(swapTotalMatch[1]);
|
|
113
|
+
const swapFree = parseInt(swapFreeMatch[1]);
|
|
114
|
+
const swapUsed = swapTotal - swapFree;
|
|
115
|
+
|
|
116
|
+
if (swapTotal > 0) {
|
|
117
|
+
const swapUsedPercent = Math.round((swapUsed / swapTotal) * 100);
|
|
118
|
+
const swapUsedMB = Math.round(swapUsed / 1024);
|
|
119
|
+
return `${swapUsedPercent}% (${swapUsedMB}MB) swap`;
|
|
120
|
+
}
|
|
100
121
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
122
|
+
} catch {}
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (IS_MAC) {
|
|
127
|
+
try {
|
|
128
|
+
const swapusage = execCommand("sysctl -n vm.swapusage");
|
|
129
|
+
const match = swapusage.match(
|
|
130
|
+
/total\s*=\s*([\d.]+)M\s+used\s*=\s*([\d.]+)M\s+free\s*=\s*([\d.]+)M/
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (match) {
|
|
134
|
+
const swapTotal = parseFloat(match[1]);
|
|
135
|
+
const swapUsedMB = parseFloat(match[2]);
|
|
136
|
+
|
|
137
|
+
if (swapTotal > 0) {
|
|
138
|
+
const swapUsedPercent = Math.round((swapUsedMB / swapTotal) * 100);
|
|
139
|
+
return `${swapUsedPercent}% (${Math.round(swapUsedMB)}MB) swap`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return "0% (0MB) swap";
|
|
143
|
+
}
|
|
144
|
+
} catch {}
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
return "";
|
|
104
149
|
}
|
|
105
150
|
|
|
106
151
|
/**
|
|
107
152
|
* Gets root filesystem disk usage percentage
|
|
108
|
-
* Uses df command on Linux/Android
|
|
153
|
+
* Uses df command on Linux/Android/macOS
|
|
109
154
|
* @param context - Info context with cache
|
|
110
155
|
* @returns Percentage string or empty string
|
|
111
156
|
* @example "45%", "78%"
|
|
@@ -114,7 +159,7 @@ export function disk_used(context: InfoContext): string {
|
|
|
114
159
|
const cached = getCachedValue(context.cache, "disk_used");
|
|
115
160
|
if (cached !== null) return cached;
|
|
116
161
|
|
|
117
|
-
if (IS_LINUX) {
|
|
162
|
+
if (IS_LINUX || IS_MAC) {
|
|
118
163
|
try {
|
|
119
164
|
const df = execCommand("df -h");
|
|
120
165
|
let diskUsage = "";
|
|
@@ -151,35 +196,108 @@ export function disk_used(context: InfoContext): string {
|
|
|
151
196
|
}
|
|
152
197
|
|
|
153
198
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
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"
|
|
199
|
+
* Minimum total size (in KB) for a partition to be considered a real disk
|
|
200
|
+
* rather than a small system/boot partition
|
|
158
201
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
202
|
+
const MIN_REAL_DISK_KB = 10 * 1024 * 1024; // 10GB
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Gets disk size (used/total) for each real disk
|
|
206
|
+
* Uses `df -k` on Linux/macOS, filtering out pseudo filesystems, system/boot
|
|
207
|
+
* partitions (e.g. /boot, /System/Volumes/*), and anything under 10GB, since
|
|
208
|
+
* those aren't disks a user would care about. When exactly one real disk is
|
|
209
|
+
* found (the common case) the result omits the mount point; when multiple
|
|
210
|
+
* real disks are found (e.g. a data drive or external volume), each is
|
|
211
|
+
* labeled by its mount point.
|
|
212
|
+
* @param context - Info context with cache
|
|
213
|
+
* @returns Disk size as "used/total GB" or "mount(used/total GB) ..." or empty string
|
|
214
|
+
* @example "256/512GB", "/(100/500GB) /Volumes/Backup(900/2000GB)"
|
|
215
|
+
*/
|
|
216
|
+
export function disk_size(context: InfoContext): string {
|
|
217
|
+
const cached = getCachedValue(context.cache, "disk_size");
|
|
218
|
+
if (cached !== null) return cached;
|
|
219
|
+
|
|
220
|
+
if (!IS_LINUX && !IS_MAC) {
|
|
221
|
+
setCachedValue(context.cache, "disk_size", "");
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
161
224
|
|
|
162
225
|
try {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
226
|
+
const df = execCommand("df -k");
|
|
227
|
+
const lines = df.trim().split("\n").slice(1);
|
|
228
|
+
const disks: { mount: string; usedGB: number; totalGB: number }[] = [];
|
|
229
|
+
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
const parts = line.trim().split(/\s+/);
|
|
232
|
+
if (parts.length < 6) continue;
|
|
233
|
+
|
|
234
|
+
const filesystem = parts[0];
|
|
235
|
+
const totalKB = parseInt(parts[1]);
|
|
236
|
+
const usedKB = parseInt(parts[2]);
|
|
237
|
+
const mount = parts[parts.length - 1];
|
|
238
|
+
|
|
239
|
+
if (isNaN(totalKB) || isNaN(usedKB) || totalKB < MIN_REAL_DISK_KB) continue;
|
|
240
|
+
|
|
241
|
+
if (IS_MAC) {
|
|
242
|
+
// Only the boot disk ("/") and externally mounted real disks count.
|
|
243
|
+
// APFS internal system volumes (Data, VM, Preboot, Update, ...) all
|
|
244
|
+
// live under /System/Volumes and aren't separate physical disks.
|
|
245
|
+
if (mount !== "/" && !mount.startsWith("/Volumes/")) continue;
|
|
246
|
+
} else {
|
|
247
|
+
if (
|
|
248
|
+
filesystem.startsWith("devtmpfs") ||
|
|
249
|
+
filesystem.startsWith("tmpfs") ||
|
|
250
|
+
filesystem === "overlay" ||
|
|
251
|
+
mount.startsWith("/boot") ||
|
|
252
|
+
mount.startsWith("/dev") ||
|
|
253
|
+
mount.startsWith("/proc") ||
|
|
254
|
+
mount.startsWith("/sys") ||
|
|
255
|
+
mount.startsWith("/run")
|
|
256
|
+
) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
disks.push({
|
|
262
|
+
mount,
|
|
263
|
+
usedGB: Math.round(usedKB / (1024 * 1024)),
|
|
264
|
+
totalGB: Math.round(totalKB / (1024 * 1024)),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let result = "";
|
|
269
|
+
if (disks.length === 1) {
|
|
270
|
+
result = `${disks[0].usedGB}/${disks[0].totalGB}GB`;
|
|
271
|
+
} else if (disks.length > 1) {
|
|
272
|
+
result = disks
|
|
273
|
+
.map((d) => `${d.mount}(${d.usedGB}/${d.totalGB}GB)`)
|
|
274
|
+
.join(" ");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
setCachedValue(context.cache, "disk_size", result);
|
|
278
|
+
return result;
|
|
166
279
|
} catch {}
|
|
280
|
+
|
|
281
|
+
setCachedValue(context.cache, "disk_size", "");
|
|
167
282
|
return "";
|
|
168
283
|
}
|
|
169
284
|
|
|
170
285
|
/**
|
|
171
286
|
* Gets mounted filesystem information
|
|
172
|
-
* Lists non-system mount points with usage from df (Linux
|
|
173
|
-
*
|
|
287
|
+
* Lists non-system mount points with usage from df (Linux/macOS)
|
|
288
|
+
* On Linux, excludes /, /dev, /proc, /sys and internal /System/Volumes mounts.
|
|
289
|
+
* On macOS, APFS internal volumes (Data, VM, Preboot, Update, ...) all live
|
|
290
|
+
* under /System/Volumes and are not user-relevant, so only /Volumes (external
|
|
291
|
+
* disks, network shares, disk images) are reported.
|
|
174
292
|
* @param context - Info context with cache
|
|
175
293
|
* @returns Space-separated mount points with usage or empty string
|
|
176
|
-
* @example "/home(45%) /mnt/data(78%)", "/
|
|
294
|
+
* @example "/home(45%) /mnt/data(78%)", "/Volumes/Backup(78%)"
|
|
177
295
|
*/
|
|
178
296
|
export function mount_points(context: InfoContext): string {
|
|
179
297
|
const cached = getCachedValue(context.cache, "mount_points");
|
|
180
298
|
if (cached !== null) return cached;
|
|
181
299
|
|
|
182
|
-
if (!IS_LINUX) {
|
|
300
|
+
if (!IS_LINUX && !IS_MAC) {
|
|
183
301
|
setCachedValue(context.cache, "mount_points", "");
|
|
184
302
|
return "";
|
|
185
303
|
}
|
|
@@ -191,17 +309,29 @@ export function mount_points(context: InfoContext): string {
|
|
|
191
309
|
|
|
192
310
|
lines.forEach((line) => {
|
|
193
311
|
const parts = line.trim().split(/\s+/);
|
|
194
|
-
if (parts.length
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
312
|
+
if (parts.length < 2) return;
|
|
313
|
+
|
|
314
|
+
const mountPoint = parts[parts.length - 1];
|
|
315
|
+
const usage = parts.find((part) => /^\d+%$/.test(part)) || "";
|
|
316
|
+
|
|
317
|
+
if (!usage) return;
|
|
318
|
+
|
|
319
|
+
if (IS_MAC) {
|
|
320
|
+
// Skip the "Macintosh HD" self-symlink and any other symlink back to /
|
|
321
|
+
if (mountPoint.startsWith("/Volumes/") && mountPoint !== "/") {
|
|
203
322
|
mountPoints.push(`${mountPoint}(${usage})`);
|
|
204
323
|
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
!mountPoint.startsWith("/dev") &&
|
|
329
|
+
!mountPoint.startsWith("/proc") &&
|
|
330
|
+
!mountPoint.startsWith("/sys") &&
|
|
331
|
+
!mountPoint.startsWith("/System/Volumes") &&
|
|
332
|
+
mountPoint !== "/"
|
|
333
|
+
) {
|
|
334
|
+
mountPoints.push(`${mountPoint}(${usage})`);
|
|
205
335
|
}
|
|
206
336
|
});
|
|
207
337
|
|
package/src/info/network.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import os from "os";
|
|
7
7
|
import type { InfoContext } from "../types/internal-types";
|
|
8
|
-
import { IS_LINUX } from "../utils/platform";
|
|
8
|
+
import { IS_LINUX, IS_MAC } from "../utils/platform";
|
|
9
9
|
import { execCommand } from "../utils/command";
|
|
10
10
|
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
11
11
|
|
|
@@ -135,16 +135,16 @@ export async function isp(context: InfoContext): Promise<string> {
|
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
137
|
* Gets active network interface names
|
|
138
|
-
* Lists non-loopback interfaces with IPv4 addresses (Linux only)
|
|
138
|
+
* Lists non-loopback interfaces with IPv4 addresses (Linux/macOS only)
|
|
139
139
|
* @param context - Info context with cache
|
|
140
140
|
* @returns Space-separated interface names or empty string
|
|
141
|
-
* @example "eth0 wlan0", "
|
|
141
|
+
* @example "eth0 wlan0", "en0 en1"
|
|
142
142
|
*/
|
|
143
143
|
export function network_interfaces(context: InfoContext): string {
|
|
144
144
|
const cached = getCachedValue(context.cache, "network_interfaces");
|
|
145
145
|
if (cached !== null) return cached;
|
|
146
146
|
|
|
147
|
-
if (!IS_LINUX) {
|
|
147
|
+
if (!IS_LINUX && !IS_MAC) {
|
|
148
148
|
setCachedValue(context.cache, "network_interfaces", "");
|
|
149
149
|
return "";
|
|
150
150
|
}
|
|
@@ -174,7 +174,7 @@ export function network_interfaces(context: InfoContext): string {
|
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
176
176
|
* Gets open TCP ports with service names
|
|
177
|
-
* Uses lsof to find listening TCP ports (Linux only)
|
|
177
|
+
* Uses lsof to find listening TCP ports (Linux/macOS only)
|
|
178
178
|
* @param context - Info context with cache
|
|
179
179
|
* @returns Space-separated port+process pairs or empty string
|
|
180
180
|
* @example "80http 443http 22ssh", "3000node 5432post"
|
|
@@ -183,7 +183,7 @@ export function ports(context: InfoContext): string {
|
|
|
183
183
|
const cached = getCachedValue(context.cache, "ports");
|
|
184
184
|
if (cached !== null) return cached;
|
|
185
185
|
|
|
186
|
-
if (IS_LINUX) {
|
|
186
|
+
if (IS_LINUX || IS_MAC) {
|
|
187
187
|
try {
|
|
188
188
|
const lsof = execCommand("lsof -nP -iTCP -sTCP:LISTEN");
|
|
189
189
|
const lines = lsof.split("\n").slice(1);
|
package/src/info/platform.ts
CHANGED
|
@@ -50,7 +50,12 @@ export function os_info(context: InfoContext): string {
|
|
|
50
50
|
osName = `Windows ${release}`;
|
|
51
51
|
}
|
|
52
52
|
} else if (IS_MAC) {
|
|
53
|
-
|
|
53
|
+
const productName = execCommand("sw_vers -productName");
|
|
54
|
+
const productVersion = execCommand("sw_vers -productVersion");
|
|
55
|
+
osName =
|
|
56
|
+
productName && productVersion
|
|
57
|
+
? `${productName} ${productVersion}`
|
|
58
|
+
: `macOS ${release}`;
|
|
54
59
|
} else if (IS_LINUX) {
|
|
55
60
|
try {
|
|
56
61
|
const osRelease = fs.readFileSync("/etc/os-release", "utf8");
|
|
@@ -119,6 +124,14 @@ export function device(context: InfoContext): string {
|
|
|
119
124
|
return device;
|
|
120
125
|
}
|
|
121
126
|
} catch {}
|
|
127
|
+
} else if (IS_MAC) {
|
|
128
|
+
const profile = execCommand("system_profiler SPHardwareDataType");
|
|
129
|
+
const nameMatch = profile.match(/Model Name:\s*(.+)/);
|
|
130
|
+
if (nameMatch) {
|
|
131
|
+
const device = nameMatch[1].trim();
|
|
132
|
+
setCachedValue(context.cache, "device", device);
|
|
133
|
+
return device;
|
|
134
|
+
}
|
|
122
135
|
} else if (IS_LINUX) {
|
|
123
136
|
try {
|
|
124
137
|
if (commandExists("getprop")) {
|
package/src/info/process.ts
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
import os from "os";
|
|
7
7
|
import type { InfoContext } from "../types/internal-types";
|
|
8
|
-
import { IS_LINUX } from "../utils/platform";
|
|
8
|
+
import { IS_LINUX, IS_MAC } from "../utils/platform";
|
|
9
9
|
import { execCommand } from "../utils/command";
|
|
10
10
|
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Gets the highest CPU-consuming process
|
|
14
|
-
* Uses ps command to find top process by CPU usage (Linux only)
|
|
14
|
+
* Uses ps command to find top process by CPU usage (Linux/macOS only)
|
|
15
15
|
* @param context - Info context with cache
|
|
16
16
|
* @returns Process info as "percentage processname" or empty string
|
|
17
17
|
* @example "8% firefox", "15% chrome", "3% systemd"
|
|
@@ -33,6 +33,22 @@ export function top_process(context: InfoContext): string {
|
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
35
|
} catch {}
|
|
36
|
+
} else if (IS_MAC) {
|
|
37
|
+
try {
|
|
38
|
+
// -c gives the short command name, -r sorts by %CPU descending
|
|
39
|
+
const ps = execCommand("ps -Aceo pcpu,comm -r");
|
|
40
|
+
const lines = ps.split("\n").filter((line) => line.trim());
|
|
41
|
+
if (lines.length > 1) {
|
|
42
|
+
const topProcess = lines[1].trim().split(/\s+/);
|
|
43
|
+
const cpuValue = Math.round(parseFloat(topProcess[0]));
|
|
44
|
+
const process = topProcess.slice(1).join(" ");
|
|
45
|
+
if (!isNaN(cpuValue) && process) {
|
|
46
|
+
const result = `${cpuValue}% ${process}`;
|
|
47
|
+
setCachedValue(context.cache, "top_process", result);
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
setCachedValue(context.cache, "top_process", "");
|
|
@@ -54,12 +70,12 @@ export function uptime(): string {
|
|
|
54
70
|
|
|
55
71
|
/**
|
|
56
72
|
* Gets number of logged in users
|
|
57
|
-
* Uses who command to count active user sessions (Linux only)
|
|
73
|
+
* Uses who command to count active user sessions (Linux/macOS only)
|
|
58
74
|
* @returns User count with "users" suffix or empty string
|
|
59
75
|
* @example "3 users", "1 users"
|
|
60
76
|
*/
|
|
61
77
|
export function users_logged_in(): string {
|
|
62
|
-
if (!IS_LINUX) return "";
|
|
78
|
+
if (!IS_LINUX && !IS_MAC) return "";
|
|
63
79
|
|
|
64
80
|
try {
|
|
65
81
|
const who = execCommand("who");
|
package/src/info/settings.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const DEFAULT_SETTINGS = {
|
|
|
37
37
|
["user", "hostname", "os", "device", "kernel", "cpu", "gpu", "bench"],
|
|
38
38
|
[
|
|
39
39
|
"disk_used",
|
|
40
|
+
"disk_size",
|
|
40
41
|
"ram_used",
|
|
41
42
|
"top_process",
|
|
42
43
|
"uptime",
|
|
@@ -44,13 +45,14 @@ export const DEFAULT_SETTINGS = {
|
|
|
44
45
|
"battery",
|
|
45
46
|
"load_average",
|
|
46
47
|
],
|
|
47
|
-
["ip", "iplocal", "city"
|
|
48
|
+
["ip", "iplocal", "city"],
|
|
48
49
|
["shell", "services_running", "pacman", "containers"],
|
|
49
50
|
],
|
|
50
51
|
colors: {
|
|
51
52
|
user: "red",
|
|
52
53
|
hostname: "orange",
|
|
53
54
|
disk_used: "purple",
|
|
55
|
+
disk_size: "purple",
|
|
54
56
|
ram_used: "yellow",
|
|
55
57
|
top_process: "magenta",
|
|
56
58
|
uptime: "cyan",
|
|
@@ -97,6 +99,7 @@ export const DEFAULT_SETTINGS = {
|
|
|
97
99
|
shell: "🐚 ",
|
|
98
100
|
pacman: "🚀 ",
|
|
99
101
|
disk_used: "📁 ",
|
|
102
|
+
disk_size: "💽 ",
|
|
100
103
|
ram_used: "💾 ",
|
|
101
104
|
top_process: "🔝 ",
|
|
102
105
|
uptime: "⏱️ ",
|
|
@@ -131,6 +134,7 @@ export const DEFAULT_SETTINGS = {
|
|
|
131
134
|
shell: "Shell",
|
|
132
135
|
pacman: "Packages",
|
|
133
136
|
disk_used: "Disk",
|
|
137
|
+
disk_size: "Disk Size",
|
|
134
138
|
ram_used: "RAM",
|
|
135
139
|
top_process: "Top",
|
|
136
140
|
uptime: "Uptime",
|
|
@@ -150,7 +154,7 @@ export const DEFAULT_SETTINGS = {
|
|
|
150
154
|
},
|
|
151
155
|
display: {
|
|
152
156
|
show_emojis: true,
|
|
153
|
-
single_line:
|
|
157
|
+
single_line: true,
|
|
154
158
|
line_wrap_length: process?.stdout?.columns || 100,
|
|
155
159
|
},
|
|
156
160
|
network: {
|
|
@@ -168,6 +172,7 @@ export interface Settings {
|
|
|
168
172
|
labels: Record<string, string>;
|
|
169
173
|
display: {
|
|
170
174
|
show_emojis: boolean;
|
|
175
|
+
/** true: print one continuous line and let the terminal soft-wrap it; false: hard-wrap at line_wrap_length */
|
|
171
176
|
single_line: boolean;
|
|
172
177
|
line_wrap_length: number;
|
|
173
178
|
};
|
package/src/info/software.ts
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
* @module info/software
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import os from "os";
|
|
6
7
|
import process from "process";
|
|
7
8
|
import type { InfoContext } from "../types/internal-types";
|
|
8
|
-
import {
|
|
9
|
+
import { IS_WINDOWS } from "../utils/platform";
|
|
9
10
|
import { execCommand, commandExists } from "../utils/command";
|
|
10
11
|
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Gets the current shell name
|
|
14
|
-
* Uses
|
|
15
|
+
* Uses the user's login shell (macOS/Linux), falls back to ps for the
|
|
16
|
+
* parent process shell
|
|
15
17
|
* @returns Shell name or empty string on Windows
|
|
16
18
|
* @example "bash", "zsh", "fish", "nu"
|
|
17
19
|
*/
|
|
@@ -19,7 +21,16 @@ export function shell(context: InfoContext): string {
|
|
|
19
21
|
const cached = getCachedValue(context.cache, "shell");
|
|
20
22
|
if (cached !== null) return cached;
|
|
21
23
|
|
|
22
|
-
if (
|
|
24
|
+
if (!IS_WINDOWS) {
|
|
25
|
+
try {
|
|
26
|
+
const loginShell = os.userInfo().shell;
|
|
27
|
+
if (loginShell) {
|
|
28
|
+
const shellName = loginShell.split("/").pop() as string;
|
|
29
|
+
setCachedValue(context.cache, "shell", shellName);
|
|
30
|
+
return shellName;
|
|
31
|
+
}
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
23
34
|
try {
|
|
24
35
|
const ppid = process.ppid;
|
|
25
36
|
const shellName = execCommand(`ps -p ${ppid} -o comm=`).split("/").pop();
|
|
@@ -47,6 +58,7 @@ export function packages(context: InfoContext): string {
|
|
|
47
58
|
|
|
48
59
|
const commands = [
|
|
49
60
|
"apt",
|
|
61
|
+
"brew",
|
|
50
62
|
"npm",
|
|
51
63
|
"uv",
|
|
52
64
|
"docker",
|
|
@@ -6,17 +6,25 @@
|
|
|
6
6
|
import fs from "fs";
|
|
7
7
|
import os from "os";
|
|
8
8
|
import type { InfoContext } from "../types/internal-types";
|
|
9
|
-
import { IS_LINUX } from "../utils/platform";
|
|
9
|
+
import { IS_LINUX, IS_MAC } from "../utils/platform";
|
|
10
10
|
import { execCommand, commandExists } from "../utils/command";
|
|
11
11
|
import { getCachedValue, setCachedValue } from "../cache/cache";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Gets system load averages
|
|
15
|
-
* Reads 1, 5, and 15 minute load averages from /proc/loadavg (Linux
|
|
15
|
+
* Reads 1, 5, and 15 minute load averages from /proc/loadavg (Linux) or
|
|
16
|
+
* os.loadavg() (macOS)
|
|
16
17
|
* @returns Space-separated load averages (1m 5m 15m) or empty string
|
|
17
18
|
* @example "0.52 0.58 0.59", "2.10 1.95 1.88"
|
|
18
19
|
*/
|
|
19
20
|
export function load_average(context: InfoContext): string {
|
|
21
|
+
if (IS_MAC) {
|
|
22
|
+
return os
|
|
23
|
+
.loadavg()
|
|
24
|
+
.map((n) => n.toFixed(2))
|
|
25
|
+
.join(" ");
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
if (!IS_LINUX) return "";
|
|
21
29
|
|
|
22
30
|
try {
|
|
@@ -38,6 +46,22 @@ export function battery(context: InfoContext): string {
|
|
|
38
46
|
const cached = getCachedValue(context.cache, "battery");
|
|
39
47
|
if (cached !== null) return cached;
|
|
40
48
|
|
|
49
|
+
if (IS_MAC) {
|
|
50
|
+
try {
|
|
51
|
+
const batt = execCommand("pmset -g batt");
|
|
52
|
+
const percentMatch = batt.match(/(\d+)%/);
|
|
53
|
+
if (percentMatch) {
|
|
54
|
+
const isCharging = /;\s*(charging|charged);/i.test(batt);
|
|
55
|
+
const result = `${percentMatch[1]}%${isCharging ? "+" : ""}`;
|
|
56
|
+
setCachedValue(context.cache, "battery", result);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
|
|
61
|
+
setCachedValue(context.cache, "battery", "");
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
if (!IS_LINUX) {
|
|
42
66
|
setCachedValue(context.cache, "battery", "");
|
|
43
67
|
return "";
|
|
@@ -68,7 +92,9 @@ export function battery(context: InfoContext): string {
|
|
|
68
92
|
|
|
69
93
|
/**
|
|
70
94
|
* Gets system temperature in Celsius
|
|
71
|
-
* Reads from thermal zone or hwmon sensors (Linux
|
|
95
|
+
* Reads from thermal zone or hwmon sensors (Linux), or from the
|
|
96
|
+
* `osx-cpu-temp` CLI (macOS, if installed via `brew install osx-cpu-temp`) -
|
|
97
|
+
* macOS has no unprivileged API for this, so nothing is reported without it
|
|
72
98
|
* @param context - Info context with cache
|
|
73
99
|
* @returns Temperature with °C suffix or empty string
|
|
74
100
|
* @example "45°C", "62°C"
|
|
@@ -77,6 +103,26 @@ export function temperature(context: InfoContext): string {
|
|
|
77
103
|
const cached = getCachedValue(context.cache, "temperature");
|
|
78
104
|
if (cached !== null) return cached;
|
|
79
105
|
|
|
106
|
+
if (IS_MAC) {
|
|
107
|
+
if (commandExists("osx-cpu-temp")) {
|
|
108
|
+
try {
|
|
109
|
+
const output = execCommand("osx-cpu-temp");
|
|
110
|
+
const match = output.match(/([\d.]+)\s*°?C/);
|
|
111
|
+
if (match) {
|
|
112
|
+
const tempC = Math.round(parseFloat(match[1]));
|
|
113
|
+
if (tempC > 0 && tempC < 150) {
|
|
114
|
+
const result = `${tempC}°C`;
|
|
115
|
+
setCachedValue(context.cache, "temperature", result);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setCachedValue(context.cache, "temperature", "");
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
|
|
80
126
|
if (!IS_LINUX) {
|
|
81
127
|
setCachedValue(context.cache, "temperature", "");
|
|
82
128
|
return "";
|
|
@@ -118,6 +164,24 @@ export function services_running(context: InfoContext): string {
|
|
|
118
164
|
const cached = getCachedValue(context.cache, "services_running");
|
|
119
165
|
if (cached !== null) return cached;
|
|
120
166
|
|
|
167
|
+
if (IS_MAC) {
|
|
168
|
+
try {
|
|
169
|
+
const services = execCommand("launchctl list");
|
|
170
|
+
const serviceCount = services
|
|
171
|
+
.split("\n")
|
|
172
|
+
.filter((line) => line.trim() && !line.startsWith("PID")).length;
|
|
173
|
+
|
|
174
|
+
if (serviceCount > 0) {
|
|
175
|
+
const result = `${serviceCount} services`;
|
|
176
|
+
setCachedValue(context.cache, "services_running", result);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
} catch {}
|
|
180
|
+
|
|
181
|
+
setCachedValue(context.cache, "services_running", "");
|
|
182
|
+
return "";
|
|
183
|
+
}
|
|
184
|
+
|
|
121
185
|
if (!IS_LINUX) {
|
|
122
186
|
setCachedValue(context.cache, "services_running", "");
|
|
123
187
|
return "";
|