about-system 0.0.41 → 0.0.46

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.
@@ -15,7 +15,7 @@ declare function battery(context: InfoContext): string;
15
15
  * Uses fuzzy matching to find similar CPU models
16
16
  * @param context - Info context with cache
17
17
  * @returns Benchmark info with score and rank, or empty string
18
- * @example "💪 37.9k #1", "💪 20.5k #68"
18
+ * @example "37.9k #1", "20.5k #68"
19
19
  */
20
20
  declare function bench(context: InfoContext): string;
21
21
 
@@ -102,9 +102,23 @@ declare function cpu_bench_info(context: InfoContext): string;
102
102
  */
103
103
  declare function device(context: InfoContext): string;
104
104
 
105
+ /**
106
+ * Gets disk size (used/total) for each real disk
107
+ * Uses `df -k` on Linux/macOS, filtering out pseudo filesystems, system/boot
108
+ * partitions (e.g. /boot, /System/Volumes/*), and anything under 10GB, since
109
+ * those aren't disks a user would care about. When exactly one real disk is
110
+ * found (the common case) the result omits the mount point; when multiple
111
+ * real disks are found (e.g. a data drive or external volume), each is
112
+ * labeled by its mount point.
113
+ * @param context - Info context with cache
114
+ * @returns Disk size as "used/total GB" or "mount(used/total GB) ..." or empty string
115
+ * @example "256/512GB", "/(100/500GB) /Volumes/Backup(900/2000GB)"
116
+ */
117
+ declare function disk_size(context: InfoContext): string;
118
+
105
119
  /**
106
120
  * Gets root filesystem disk usage percentage
107
- * Uses df command on Linux/Android
121
+ * Uses df command on Linux/Android/macOS
108
122
  * @param context - Info context with cache
109
123
  * @returns Percentage string or empty string
110
124
  * @example "45%", "78%"
@@ -139,7 +153,7 @@ declare function gpu(context: InfoContext): string;
139
153
  * Uses fuzzy matching to find similar GPU models
140
154
  * @param context - Info context with cache
141
155
  * @returns Benchmark info with score and rank, or empty string
142
- * @example "💪 37.9k #1", "💪 20.5k #68"
156
+ * @example "37.9k #1", "20.5k #68"
143
157
  */
144
158
  declare function gpu_bench(context: InfoContext): string;
145
159
 
@@ -188,6 +202,7 @@ export declare const infoFunctions: {
188
202
  gpu_bench: typeof gpu_bench;
189
203
  gpu_bench_info: typeof gpu_bench_info;
190
204
  disk_used: typeof disk_used;
205
+ disk_size: typeof disk_size;
191
206
  ram_used: typeof ram_used;
192
207
  top_process: typeof top_process;
193
208
  uptime: typeof uptime;
@@ -264,7 +279,8 @@ declare function kernel(context: InfoContext): string;
264
279
 
265
280
  /**
266
281
  * Gets system load averages
267
- * Reads 1, 5, and 15 minute load averages from /proc/loadavg (Linux only)
282
+ * Reads 1, 5, and 15 minute load averages from /proc/loadavg (Linux) or
283
+ * os.loadavg() (macOS)
268
284
  * @returns Space-separated load averages (1m 5m 15m) or empty string
269
285
  * @example "0.52 0.58 0.59", "2.10 1.95 1.88"
270
286
  */
@@ -277,7 +293,8 @@ export declare function loadCache(): Cache_2;
277
293
 
278
294
  /**
279
295
  * Gets available memory in gigabytes
280
- * Reads MemAvailable from /proc/meminfo (Linux only)
296
+ * Reads MemAvailable from /proc/meminfo (Linux) or derives it from
297
+ * vm_stat's free+inactive pages (macOS)
281
298
  * @returns Available memory with "GB available" suffix or empty string
282
299
  * @example "12GB available", "4GB available"
283
300
  */
@@ -285,20 +302,23 @@ declare function memory_available(): string;
285
302
 
286
303
  /**
287
304
  * Gets mounted filesystem information
288
- * Lists non-system mount points with usage from df (Linux only)
289
- * Excludes /, /dev, /proc, and /sys mounts
305
+ * Lists non-system mount points with usage from df (Linux/macOS)
306
+ * On Linux, excludes /, /dev, /proc, /sys and internal /System/Volumes mounts.
307
+ * On macOS, APFS internal volumes (Data, VM, Preboot, Update, ...) all live
308
+ * under /System/Volumes and are not user-relevant, so only /Volumes (external
309
+ * disks, network shares, disk images) are reported.
290
310
  * @param context - Info context with cache
291
311
  * @returns Space-separated mount points with usage or empty string
292
- * @example "/home(45%) /mnt/data(78%)", "/media/usb(12%)"
312
+ * @example "/home(45%) /mnt/data(78%)", "/Volumes/Backup(78%)"
293
313
  */
294
314
  declare function mount_points(context: InfoContext): string;
295
315
 
296
316
  /**
297
317
  * Gets active network interface names
298
- * Lists non-loopback interfaces with IPv4 addresses (Linux only)
318
+ * Lists non-loopback interfaces with IPv4 addresses (Linux/macOS only)
299
319
  * @param context - Info context with cache
300
320
  * @returns Space-separated interface names or empty string
301
- * @example "eth0 wlan0", "enp0s3"
321
+ * @example "eth0 wlan0", "en0 en1"
302
322
  */
303
323
  declare function network_interfaces(context: InfoContext): string;
304
324
 
@@ -322,7 +342,7 @@ declare function packages(context: InfoContext): string;
322
342
 
323
343
  /**
324
344
  * Gets open TCP ports with service names
325
- * Uses lsof to find listening TCP ports (Linux only)
345
+ * Uses lsof to find listening TCP ports (Linux/macOS only)
326
346
  * @param context - Info context with cache
327
347
  * @returns Space-separated port+process pairs or empty string
328
348
  * @example "80http 443http 22ssh", "3000node 5432post"
@@ -344,8 +364,8 @@ declare function ram_used(context: InfoContext): string;
344
364
  export declare function saveCache(cache: Cache_2): void;
345
365
 
346
366
  /**
347
- * Gets screen resolution from X11 display
348
- * Uses xrandr command (Linux with X11/Xorg only)
367
+ * Gets screen resolution
368
+ * Uses xrandr on Linux (X11/Xorg only) or system_profiler on macOS
349
369
  * @returns Resolution in WIDTHxHEIGHT format or empty string
350
370
  * @example "1920x1080", "2560x1440", "3840x2160"
351
371
  */
@@ -362,7 +382,8 @@ declare function services_running(context: InfoContext): string;
362
382
 
363
383
  /**
364
384
  * Gets the current shell name
365
- * Uses ps command to find parent process shell (Linux/Unix only)
385
+ * Uses the user's login shell (macOS/Linux), falls back to ps for the
386
+ * parent process shell
366
387
  * @returns Shell name or empty string on Windows
367
388
  * @example "bash", "zsh", "fish", "nu"
368
389
  */
@@ -370,7 +391,8 @@ declare function shell(context: InfoContext): string;
370
391
 
371
392
  /**
372
393
  * Gets swap memory usage
373
- * Calculates swap usage from /proc/meminfo (Linux only)
394
+ * Calculates swap usage from /proc/meminfo (Linux) or `sysctl vm.swapusage`
395
+ * (macOS)
374
396
  * @returns Swap usage as "percentage (size MB) swap" or empty string
375
397
  * @example "15% (512MB) swap", "0% (0MB) swap"
376
398
  */
@@ -378,7 +400,9 @@ declare function swap_used(): string;
378
400
 
379
401
  /**
380
402
  * Gets system temperature in Celsius
381
- * Reads from thermal zone or hwmon sensors (Linux only)
403
+ * Reads from thermal zone or hwmon sensors (Linux), or from the
404
+ * `osx-cpu-temp` CLI (macOS, if installed via `brew install osx-cpu-temp`) -
405
+ * macOS has no unprivileged API for this, so nothing is reported without it
382
406
  * @param context - Info context with cache
383
407
  * @returns Temperature with °C suffix or empty string
384
408
  * @example "45°C", "62°C"
@@ -387,7 +411,7 @@ declare function temperature(context: InfoContext): string;
387
411
 
388
412
  /**
389
413
  * Gets the highest CPU-consuming process
390
- * Uses ps command to find top process by CPU usage (Linux only)
414
+ * Uses ps command to find top process by CPU usage (Linux/macOS only)
391
415
  * @param context - Info context with cache
392
416
  * @returns Process info as "percentage processname" or empty string
393
417
  * @example "8% firefox", "15% chrome", "3% systemd"
@@ -409,7 +433,7 @@ declare function user(): string;
409
433
 
410
434
  /**
411
435
  * Gets number of logged in users
412
- * Uses who command to count active user sessions (Linux only)
436
+ * Uses who command to count active user sessions (Linux/macOS only)
413
437
  * @returns User count with "users" suffix or empty string
414
438
  * @example "3 users", "1 users"
415
439
  */
@@ -2,7 +2,7 @@ import "os";
2
2
  import "fs";
3
3
  import "https";
4
4
  import "path";
5
- import { g as m, i as r, l as p, s as n } from "./system-info-api-DCP1ZJVR.js";
5
+ import { g as m, i as r, l as p, s as n } from "./system-info-api-CChFyamn.js";
6
6
  export {
7
7
  m as getSystemInfo,
8
8
  r as infoFunctions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "about-system",
3
- "version": "0.0.41",
3
+ "version": "0.0.46",
4
4
  "description": "A Node.js script to display key system information with emojis. Cross-platform support for Windows, macOS, and Linux with customizable output and caching.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,9 +56,9 @@
56
56
  "license": "rights.institute/prosper",
57
57
  "repository": {
58
58
  "type": "git",
59
- "url": "https://github.com/OpenSourceAGI/starter-app-dev-tools/tree/master/packages/about-system-info"
59
+ "url": "https://github.com/OpenSourceAGI/dev-tools-starter-agent/tree/master/packages/about-system-info"
60
60
  },
61
- "homepage": "https://github.com/OpenSourceAGI/starter-app-dev-tools/tree/master/packages/about-system-info",
61
+ "homepage": "https://github.com/OpenSourceAGI/dev-tools-starter-agent/tree/master/packages/about-system-info",
62
62
  "files": [
63
63
  "dist/**/*",
64
64
  "src/**/*.ts",
@@ -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_WINDOWS, IS_LINUX } from "../utils/platform";
9
+ import { IS_WINDOWS, IS_MAC, IS_LINUX } from "../utils/platform";
10
10
  import { execCommand } from "../utils/command";
11
11
  import { getCachedValue, setCachedValue } from "../cache/cache";
12
12
  import Fuse from "fuse.js";
@@ -133,6 +133,19 @@ export function gpu(context: InfoContext): string {
133
133
  setCachedValue(context.cache, "gpu", result);
134
134
  return result;
135
135
  }
136
+ } else if (IS_MAC) {
137
+ try {
138
+ const profile = execCommand("system_profiler SPDisplaysDataType");
139
+ const gpus = Array.from(profile.matchAll(/Chipset Model:\s*(.+)/g)).map(
140
+ (match) => match[1].trim()
141
+ );
142
+
143
+ if (gpus.length > 0) {
144
+ const result = gpus.join(", ");
145
+ setCachedValue(context.cache, "gpu", result);
146
+ return result;
147
+ }
148
+ } catch {}
136
149
  } else if (IS_LINUX) {
137
150
  try {
138
151
  const lspci = execCommand("lspci");
@@ -173,12 +186,23 @@ export function gpu(context: InfoContext): string {
173
186
  }
174
187
 
175
188
  /**
176
- * Gets screen resolution from X11 display
177
- * Uses xrandr command (Linux with X11/Xorg only)
189
+ * Gets screen resolution
190
+ * Uses xrandr on Linux (X11/Xorg only) or system_profiler on macOS
178
191
  * @returns Resolution in WIDTHxHEIGHT format or empty string
179
192
  * @example "1920x1080", "2560x1440", "3840x2160"
180
193
  */
181
194
  export function screen_resolution(): string {
195
+ if (IS_MAC) {
196
+ try {
197
+ const profile = execCommand("system_profiler SPDisplaysDataType");
198
+ const resolutionMatch = profile.match(/Resolution:\s*(\d+) x (\d+)/);
199
+ if (resolutionMatch) {
200
+ return `${resolutionMatch[1]}x${resolutionMatch[2]}`;
201
+ }
202
+ } catch {}
203
+ return "";
204
+ }
205
+
182
206
  if (!IS_LINUX) return "";
183
207
 
184
208
  try {
@@ -198,7 +222,7 @@ export function screen_resolution(): string {
198
222
  * Uses fuzzy matching to find similar CPU models
199
223
  * @param context - Info context with cache
200
224
  * @returns Benchmark info with score and rank, or empty string
201
- * @example "💪 37.9k #1", "💪 20.5k #68"
225
+ * @example "37.9k #1", "20.5k #68"
202
226
  */
203
227
  export function bench(context: InfoContext): string {
204
228
  const cached = getCachedValue(context.cache, "bench");
@@ -212,7 +236,7 @@ export function bench(context: InfoContext): string {
212
236
 
213
237
  try {
214
238
  let benchmarkPath: string;
215
- const benchmarkFile = "cpu-geekbench-1k.json";
239
+ const benchmarkFile = "cpu-geekbench.json";
216
240
 
217
241
  // Try multiple resolution strategies
218
242
  if (fs.existsSync(benchmarkFile)) {
@@ -252,7 +276,7 @@ export function bench(context: InfoContext): string {
252
276
  const match = results[0].item;
253
277
  const score = Math.round(match.score / 100) / 10;
254
278
  const rank = match.rank;
255
- const result = `💪 ${score}k #${rank}`;
279
+ const result = `${score}k #${rank}`;
256
280
  setCachedValue(context.cache, "bench", result);
257
281
  return result;
258
282
  }
@@ -283,7 +307,7 @@ export function cpu_bench_info(context: InfoContext): string {
283
307
 
284
308
  try {
285
309
  let benchmarkPath: string;
286
- const benchmarkFile = "cpu-geekbench-1k.json";
310
+ const benchmarkFile = "cpu-geekbench.json";
287
311
 
288
312
  // Try multiple resolution strategies
289
313
  if (fs.existsSync(benchmarkFile)) {
@@ -357,7 +381,7 @@ export function cpu_bench_info(context: InfoContext): string {
357
381
  * Uses fuzzy matching to find similar GPU models
358
382
  * @param context - Info context with cache
359
383
  * @returns Benchmark info with score and rank, or empty string
360
- * @example "💪 37.9k #1", "💪 20.5k #68"
384
+ * @example "37.9k #1", "20.5k #68"
361
385
  */
362
386
  export function gpu_bench(context: InfoContext): string {
363
387
  const cached = getCachedValue(context.cache, "gpu_bench");
@@ -411,7 +435,7 @@ export function gpu_bench(context: InfoContext): string {
411
435
  const match = results[0].item;
412
436
  const score = Math.round(match.score / 100) / 10;
413
437
  const rank = match.rank;
414
- const result = `💪 ${score}k #${rank}`;
438
+ const result = `${score}k #${rank}`;
415
439
  setCachedValue(context.cache, "gpu_bench", result);
416
440
  return result;
417
441
  }
@@ -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 only)
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 (!IS_LINUX) return "";
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 only)
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 (!IS_LINUX) return "";
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
- 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`;
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
- } catch {}
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
- * 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"
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
- export function load_average(): string {
160
- if (!IS_LINUX) return "";
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 loadavg = fs.readFileSync("/proc/loadavg", "utf8");
164
- const loads = loadavg.split(" ").slice(0, 3);
165
- return loads.join(" ");
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 only)
173
- * Excludes /, /dev, /proc, and /sys mounts
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%)", "/media/usb(12%)"
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 >= 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
- ) {
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
 
@@ -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", "enp0s3"
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);