@danielng23/dsh-studio-host-system-metrics 0.1.0

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/lib/index.js ADDED
@@ -0,0 +1,697 @@
1
+ import { execFile } from "node:child_process";
2
+ import { open, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
5
+ import { cpus, freemem, loadavg, networkInterfaces, totalmem, uptime } from "node:os";
6
+ //#region lib/types/index.js
7
+ /** System-monitor Host Remote serving `node:os` resource snapshots to the browser. */
8
+ var __runInitializers = function(thisArg, initializers, value) {
9
+ var useValue = arguments.length > 2;
10
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
11
+ return useValue ? value : void 0;
12
+ };
13
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
14
+ function accept(f) {
15
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
16
+ return f;
17
+ }
18
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
19
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
20
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
21
+ var _, done = false;
22
+ for (var i = decorators.length - 1; i >= 0; i--) {
23
+ var context = {};
24
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
25
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
26
+ context.addInitializer = function(f) {
27
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
28
+ extraInitializers.push(accept(f || null));
29
+ };
30
+ var result = (0, decorators[i])(kind === "accessor" ? {
31
+ get: descriptor.get,
32
+ set: descriptor.set
33
+ } : descriptor[key], context);
34
+ if (kind === "accessor") {
35
+ if (result === void 0) continue;
36
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
37
+ if (_ = accept(result.get)) descriptor.get = _;
38
+ if (_ = accept(result.set)) descriptor.set = _;
39
+ if (_ = accept(result.init)) initializers.unshift(_);
40
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
41
+ else descriptor[key] = _;
42
+ }
43
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
44
+ done = true;
45
+ };
46
+ const execFile$1 = promisify(execFile);
47
+ /** Best-effort shell read: empty string on any failure. */
48
+ async function run(cmd, args) {
49
+ try {
50
+ const { stdout } = await execFile$1(cmd, args, {
51
+ timeout: 5e3,
52
+ encoding: "utf8"
53
+ });
54
+ return stdout;
55
+ } catch {
56
+ return "";
57
+ }
58
+ }
59
+ /** CPU busy ratio since boot across all logical cores, 0..1. */
60
+ function busyRatio() {
61
+ let busy = 0;
62
+ let total = 0;
63
+ for (const core of cpus()) {
64
+ const coreBusy = core.times.user + core.times.nice + core.times.sys + core.times.irq;
65
+ busy += coreBusy;
66
+ total += coreBusy + core.times.idle;
67
+ }
68
+ return total === 0 ? 0 : busy / total;
69
+ }
70
+ /** Raw cumulative tick counts per logical core (client computes usage deltas). */
71
+ function coresTimes() {
72
+ return cpus().map((core) => ({ ...core.times }));
73
+ }
74
+ /** Swap totals in bytes; zeros when the platform exposes none. */
75
+ async function swapUsage() {
76
+ if (process.platform === "darwin") {
77
+ const out = await run("sysctl", ["-n", "vm.swapusage"]);
78
+ const match = /total = ([\d.]+)M\s+used = ([\d.]+)M/.exec(out);
79
+ if (match !== null) return {
80
+ totalBytes: Math.round(Number(match[1]) * 1048576),
81
+ usedBytes: Math.round(Number(match[2]) * 1048576)
82
+ };
83
+ } else if (process.platform === "linux") {
84
+ const out = await run("sh", ["-c", "grep -E \"^(SwapTotal|SwapFree):\" /proc/meminfo"]);
85
+ const totalKb = Number(/(?:^|\n)SwapTotal:\s+(\d+) kB/.exec(out)?.[1] ?? 0);
86
+ const freeKb = Number(/(?:^|\n)SwapFree:\s+(\d+) kB/.exec(out)?.[1] ?? 0);
87
+ return {
88
+ totalBytes: totalKb * 1024,
89
+ usedBytes: (totalKb - freeKb) * 1024
90
+ };
91
+ }
92
+ return {
93
+ totalBytes: 0,
94
+ usedBytes: 0
95
+ };
96
+ }
97
+ /** CPU thermal reading when the platform exposes one without privileges, else null. */
98
+ async function thermalLevel() {
99
+ if (process.platform === "darwin") {
100
+ const value = Number((await run("sysctl", ["-n", "machdep.xcpm.cpu_therm_level"])).trim());
101
+ return Number.isFinite(value) ? value : null;
102
+ }
103
+ if (process.platform === "linux") {
104
+ const value = Number((await run("sh", ["-c", "cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null"])).trim());
105
+ return Number.isFinite(value) ? Math.round(value / 1e3) : null;
106
+ }
107
+ return null;
108
+ }
109
+ /** Power state when readable: 'CHARGE' | 'AC' | 'BATTERY', else null. */
110
+ async function powerState() {
111
+ if (process.platform === "darwin") {
112
+ const out = await run("pmset", ["-g", "batt"]);
113
+ if (out.includes("charging")) return "CHARGE";
114
+ if (out.includes("AC Power")) return "AC";
115
+ if (out.includes("Battery Power")) return "BATTERY";
116
+ return null;
117
+ }
118
+ if (process.platform === "linux") {
119
+ const status = (await run("sh", ["-c", "cat /sys/class/power_supply/*/status 2>/dev/null | head -1"])).trim();
120
+ if (status === "Charging" || status === "Full") return "CHARGE";
121
+ if (status === "Discharging") return "BATTERY";
122
+ return null;
123
+ }
124
+ return null;
125
+ }
126
+ /** Static hardware identity, read once and cached. */
127
+ let hardwareCache;
128
+ async function hardwareInfo() {
129
+ if (hardwareCache !== void 0) return hardwareCache;
130
+ let manufacturer = "Unknown";
131
+ let model = "Unknown";
132
+ let chassis = "Unknown";
133
+ if (process.platform === "darwin") {
134
+ manufacturer = "Apple Inc.";
135
+ chassis = "Laptop";
136
+ model = (await run("sysctl", ["-n", "hw.model"])).trim() || "Unknown";
137
+ } else if (process.platform === "linux") {
138
+ manufacturer = (await run("sh", ["-c", "cat /sys/class/dmi/id/sys_vendor 2>/dev/null"])).trim() || "Unknown";
139
+ model = (await run("sh", ["-c", "cat /sys/class/dmi/id/product_name 2>/dev/null"])).trim() || "Unknown";
140
+ const chassisType = (await run("sh", ["-c", "cat /sys/class/dmi/id/chassis_type 2>/dev/null"])).trim();
141
+ chassis = chassisType === "10" || chassisType === "9" || chassisType === "8" ? "Notebook" : chassisType === "3" ? "Desktop" : chassisType === "" ? "Unknown" : `Type ${chassisType}`;
142
+ }
143
+ hardwareCache = {
144
+ manufacturer,
145
+ model,
146
+ chassis
147
+ };
148
+ return hardwareCache;
149
+ }
150
+ /** Top processes by CPU usage, descending. */
151
+ async function topProcesses(limit = 10) {
152
+ const out = await run("ps", process.platform === "darwin" ? [
153
+ "-axo",
154
+ "pid=,comm=,%cpu=,%mem=",
155
+ "-r"
156
+ ] : [
157
+ "-axo",
158
+ "pid=,comm=,%cpu=,%mem=",
159
+ "--sort=-%cpu"
160
+ ]);
161
+ const rows = [];
162
+ for (const line of out.split("\n")) {
163
+ const match = /^\s*(\d+)\s+(.*?)\s+([\d.]+)\s+([\d.]+)\s*$/.exec(line);
164
+ if (match === null) continue;
165
+ rows.push({
166
+ pid: Number(match[1]),
167
+ name: match[2] ?? "",
168
+ cpuPct: Number(match[3]),
169
+ memPct: Number(match[4])
170
+ });
171
+ if (rows.length >= limit) break;
172
+ }
173
+ return rows;
174
+ }
175
+ /** Total active process count. */
176
+ async function taskCount() {
177
+ const trimmed = (await run("ps", ["-axo", "pid="])).trim();
178
+ return trimmed === "" ? 0 : trimmed.split("\n").length;
179
+ }
180
+ /** The active interface: the first non-internal one with an IPv4 address. */
181
+ function activeInterface() {
182
+ const interfaces = networkInterfaces();
183
+ for (const [name, addresses] of Object.entries(interfaces)) for (const address of addresses ?? []) if (!address.internal && address.family === "IPv4") return {
184
+ name,
185
+ ip: address.address
186
+ };
187
+ return {
188
+ name: "—",
189
+ ip: null
190
+ };
191
+ }
192
+ /** Cumulative rx/tx bytes on the active interface (netstat/proc counters). */
193
+ async function interfaceBytes(name) {
194
+ if (process.platform === "darwin") {
195
+ const lines = (await run("netstat", ["-ib"])).split("\n");
196
+ const header = lines.find((line) => line.includes("Ibytes"));
197
+ const indexOfIbytes = header?.indexOf("Ibytes") ?? -1;
198
+ const indexOfObytes = header?.indexOf("Obytes") ?? -1;
199
+ for (const line of lines) {
200
+ if (new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s`).exec(line) === null) continue;
201
+ const rx = Number(line.slice(indexOfIbytes).split(/\s+/)[0] ?? "0");
202
+ const tx = Number(line.slice(indexOfObytes).split(/\s+/)[0] ?? "0");
203
+ if (Number.isFinite(rx) && Number.isFinite(tx)) return {
204
+ rxBytes: rx,
205
+ txBytes: tx
206
+ };
207
+ }
208
+ return {
209
+ rxBytes: 0,
210
+ txBytes: 0
211
+ };
212
+ }
213
+ if (process.platform === "linux") {
214
+ const out = await run("sh", ["-c", `grep "${name}:" /proc/net/dev`]);
215
+ const match = /:\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/.exec(out);
216
+ if (match !== null) return {
217
+ rxBytes: Number(match[1]),
218
+ txBytes: Number(match[2])
219
+ };
220
+ return {
221
+ rxBytes: 0,
222
+ txBytes: 0
223
+ };
224
+ }
225
+ return {
226
+ rxBytes: 0,
227
+ txBytes: 0
228
+ };
229
+ }
230
+ /** Round-trip ping to 8.8.8.8 in ms, or null when unreachable. */
231
+ async function pingMs() {
232
+ const out = await run("ping", process.platform === "darwin" ? [
233
+ "-c",
234
+ "1",
235
+ "-t",
236
+ "2",
237
+ "8.8.8.8"
238
+ ] : [
239
+ "-c",
240
+ "1",
241
+ "-W",
242
+ "2",
243
+ "8.8.8.8"
244
+ ]);
245
+ const match = /time=([\d.]+)\s*ms/.exec(out);
246
+ return match === null ? null : Number(match[1]);
247
+ }
248
+ /** Storage usage of the process cwd's mount. */
249
+ async function storageInfo() {
250
+ const path = process.cwd();
251
+ if (process.platform === "darwin" || process.platform === "linux") {
252
+ const lines = (await run("df", ["-k", path])).trim().split("\n").slice(1);
253
+ const match = /^[\S]+\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%/.exec(lines[0] ?? "");
254
+ if (match !== null) return {
255
+ path,
256
+ totalBytes: Number(match[1]) * 1024,
257
+ usedBytes: Number(match[2]) * 1024,
258
+ usedPct: Number(match[4])
259
+ };
260
+ }
261
+ return {
262
+ path,
263
+ totalBytes: 0,
264
+ usedBytes: 0,
265
+ usedPct: 0
266
+ };
267
+ }
268
+ /**
269
+ * Remote-only service exposing host resource snapshots to the browser. Every
270
+ * snapshot is projected directly from `node:os` at call time; no cache exists
271
+ * to synchronize.
272
+ * @typert service systemMetrics
273
+ */
274
+ let SystemMetricsService = (() => {
275
+ let _classSuper = TypertRemoteService;
276
+ let _instanceExtraInitializers = [];
277
+ let _snapshot_decorators;
278
+ let _overview_decorators;
279
+ let _listDirectory_decorators;
280
+ let _readFile_decorators;
281
+ let _writeFile_decorators;
282
+ let _runCommand_decorators;
283
+ return class SystemMetricsService extends _classSuper {
284
+ static {
285
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
286
+ _snapshot_decorators = [Remote("snapshot")];
287
+ _overview_decorators = [Remote("overview")];
288
+ _listDirectory_decorators = [Remote("listDirectory")];
289
+ _readFile_decorators = [Remote("readFile")];
290
+ _writeFile_decorators = [Remote("writeFile")];
291
+ _runCommand_decorators = [Remote("runCommand")];
292
+ __esDecorate(this, null, _snapshot_decorators, {
293
+ kind: "method",
294
+ name: "snapshot",
295
+ static: false,
296
+ private: false,
297
+ access: {
298
+ has: (obj) => "snapshot" in obj,
299
+ get: (obj) => obj.snapshot
300
+ },
301
+ metadata: _metadata
302
+ }, null, _instanceExtraInitializers);
303
+ __esDecorate(this, null, _overview_decorators, {
304
+ kind: "method",
305
+ name: "overview",
306
+ static: false,
307
+ private: false,
308
+ access: {
309
+ has: (obj) => "overview" in obj,
310
+ get: (obj) => obj.overview
311
+ },
312
+ metadata: _metadata
313
+ }, null, _instanceExtraInitializers);
314
+ __esDecorate(this, null, _listDirectory_decorators, {
315
+ kind: "method",
316
+ name: "listDirectory",
317
+ static: false,
318
+ private: false,
319
+ access: {
320
+ has: (obj) => "listDirectory" in obj,
321
+ get: (obj) => obj.listDirectory
322
+ },
323
+ metadata: _metadata
324
+ }, null, _instanceExtraInitializers);
325
+ __esDecorate(this, null, _readFile_decorators, {
326
+ kind: "method",
327
+ name: "readFile",
328
+ static: false,
329
+ private: false,
330
+ access: {
331
+ has: (obj) => "readFile" in obj,
332
+ get: (obj) => obj.readFile
333
+ },
334
+ metadata: _metadata
335
+ }, null, _instanceExtraInitializers);
336
+ __esDecorate(this, null, _writeFile_decorators, {
337
+ kind: "method",
338
+ name: "writeFile",
339
+ static: false,
340
+ private: false,
341
+ access: {
342
+ has: (obj) => "writeFile" in obj,
343
+ get: (obj) => obj.writeFile
344
+ },
345
+ metadata: _metadata
346
+ }, null, _instanceExtraInitializers);
347
+ __esDecorate(this, null, _runCommand_decorators, {
348
+ kind: "method",
349
+ name: "runCommand",
350
+ static: false,
351
+ private: false,
352
+ access: {
353
+ has: (obj) => "runCommand" in obj,
354
+ get: (obj) => obj.runCommand
355
+ },
356
+ metadata: _metadata
357
+ }, null, _instanceExtraInitializers);
358
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
359
+ enumerable: true,
360
+ configurable: true,
361
+ writable: true,
362
+ value: _metadata
363
+ });
364
+ }
365
+ constructor(ctx) {
366
+ super(ctx, "systemMetrics");
367
+ __runInitializers(this, _instanceExtraInitializers);
368
+ }
369
+ /**
370
+ * Read the current host resource state.
371
+ * @returns load averages, since-boot CPU busy ratio, memory, and uptime.
372
+ */
373
+ snapshot() {
374
+ return {
375
+ loadavg: loadavg(),
376
+ cpuBusyRatio: busyRatio(),
377
+ totalMemoryBytes: totalmem(),
378
+ freeMemoryBytes: freemem(),
379
+ uptimeSeconds: uptime(),
380
+ timestamp: Date.now()
381
+ };
382
+ }
383
+ /**
384
+ * Read the rich system overview for the left system panel: per-core CPU
385
+ * tick counts (the client computes usage deltas between polls), memory and
386
+ * swap, thermal/power/hardware (best-effort), and top processes.
387
+ * @returns the overview.
388
+ */
389
+ async overview() {
390
+ const memoryTotal = totalmem();
391
+ const memoryFree = freemem();
392
+ const active = activeInterface();
393
+ const [swap, thermal, power, hardware, processes, tasks, bytes, ping, storage] = await Promise.all([
394
+ swapUsage(),
395
+ thermalLevel(),
396
+ powerState(),
397
+ hardwareInfo(),
398
+ topProcesses(),
399
+ taskCount(),
400
+ interfaceBytes(active.name),
401
+ pingMs(),
402
+ storageInfo()
403
+ ]);
404
+ const network = {
405
+ interfaceName: active.name,
406
+ state: active.ip === null ? "IPv4 OFFLINE" : "IPv4 ONLINE",
407
+ ip: active.ip,
408
+ pingMs: ping,
409
+ rxBytes: bytes.rxBytes,
410
+ txBytes: bytes.txBytes
411
+ };
412
+ return {
413
+ timestamp: Date.now(),
414
+ platform: process.platform,
415
+ uptimeSeconds: uptime(),
416
+ loadavg: loadavg(),
417
+ memory: {
418
+ totalBytes: memoryTotal,
419
+ usedBytes: memoryTotal - memoryFree
420
+ },
421
+ swap,
422
+ cores: coresTimes(),
423
+ thermalLevel: thermal,
424
+ powerState: power,
425
+ hardware,
426
+ tasks,
427
+ processes,
428
+ network,
429
+ storage
430
+ };
431
+ }
432
+ /**
433
+ * List one directory for the filesystem browser.
434
+ * @param path - absolute directory to list.
435
+ * @returns the listing (or an error string when unreadable).
436
+ */
437
+ async listDirectory(path) {
438
+ try {
439
+ return {
440
+ path,
441
+ entries: (await readdir(path, { withFileTypes: true })).map((dirent) => ({
442
+ name: dirent.name,
443
+ isDirectory: dirent.isDirectory()
444
+ })).sort((left, right) => Number(right.isDirectory) - Number(left.isDirectory) || left.name.localeCompare(right.name)),
445
+ error: null
446
+ };
447
+ } catch (error) {
448
+ return {
449
+ path,
450
+ entries: [],
451
+ error: error instanceof Error ? error.message : String(error)
452
+ };
453
+ }
454
+ }
455
+ /**
456
+ * Read one file for the bottom-right preview pane / editor. Text payloads
457
+ * are capped at 4 MiB, images at 4 MiB, videos at 12 MiB; oversized files
458
+ * are truncated and flagged rather than refused. Kind is decided by
459
+ * extension with a UTF-8 sniff fallback for unknown extensions.
460
+ * @param path - absolute file path.
461
+ * @returns the preview payload (or an error string when unreadable).
462
+ */
463
+ async readFile(path) {
464
+ try {
465
+ const info = await stat(path);
466
+ if (info.isDirectory()) return {
467
+ path,
468
+ kind: "unsupported",
469
+ mime: "",
470
+ sizeBytes: 0,
471
+ truncated: false,
472
+ text: null,
473
+ dataUrl: null,
474
+ error: "is a directory"
475
+ };
476
+ const byExtension = previewKindOf(path.slice(path.lastIndexOf(".") + 1).toLowerCase());
477
+ if (byExtension !== null && byExtension.kind !== "text") {
478
+ const cap = byExtension.kind === "image" ? PREVIEW_IMAGE_CAP : PREVIEW_VIDEO_CAP;
479
+ const buffer = await readFirst(path, Math.min(info.size, cap));
480
+ return {
481
+ path,
482
+ kind: byExtension.kind,
483
+ mime: byExtension.mime,
484
+ sizeBytes: info.size,
485
+ truncated: info.size > cap,
486
+ text: null,
487
+ dataUrl: `data:${byExtension.mime};base64,${buffer.toString("base64")}`,
488
+ error: null
489
+ };
490
+ }
491
+ const sample = await readFirst(path, Math.min(info.size, 8192));
492
+ const kind = byExtension ?? sniffTextKind(sample);
493
+ if (kind === null) return {
494
+ path,
495
+ kind: "unsupported",
496
+ mime: "application/octet-stream",
497
+ sizeBytes: info.size,
498
+ truncated: info.size > PREVIEW_TEXT_CAP,
499
+ text: null,
500
+ dataUrl: null,
501
+ error: null
502
+ };
503
+ const buffer = info.size > PREVIEW_TEXT_CAP ? await readFirst(path, PREVIEW_TEXT_CAP) : sample;
504
+ return {
505
+ path,
506
+ kind: "text",
507
+ mime: kind.mime,
508
+ sizeBytes: info.size,
509
+ truncated: info.size > PREVIEW_TEXT_CAP,
510
+ text: buffer.toString("utf8"),
511
+ dataUrl: null,
512
+ error: null
513
+ };
514
+ } catch (error) {
515
+ return {
516
+ path,
517
+ kind: "unsupported",
518
+ mime: "",
519
+ sizeBytes: 0,
520
+ truncated: false,
521
+ text: null,
522
+ dataUrl: null,
523
+ error: error instanceof Error ? error.message : String(error)
524
+ };
525
+ }
526
+ }
527
+ /**
528
+ * Write one text file from the bottom-right editor. Creates or replaces the
529
+ * file at `path` with the given UTF-8 content; the parent directory must
530
+ * exist. Trust surface matches `readFile`: the GUI already reads arbitrary
531
+ * paths the host process can reach, so writing carries the same parity.
532
+ * @param path - absolute file path.
533
+ * @param content - full text content to persist.
534
+ * @returns the write result (or an error string when unwritable).
535
+ */
536
+ async writeFile(path, content) {
537
+ try {
538
+ await writeFile(path, content, "utf8");
539
+ return {
540
+ path,
541
+ sizeBytes: Buffer.byteLength(content, "utf8"),
542
+ error: null
543
+ };
544
+ } catch (error) {
545
+ return {
546
+ path,
547
+ sizeBytes: 0,
548
+ error: error instanceof Error ? error.message : String(error)
549
+ };
550
+ }
551
+ }
552
+ /**
553
+ * Run one shell command for the bottom-right terminal panel. Executes
554
+ * `command` through `sh -c` with a 30s timeout and a 4 MiB output cap.
555
+ * Non-zero exits return the captured output with the real exit code; a
556
+ * command that cannot start (or times out) returns exitCode null.
557
+ * Trust surface matches `writeFile`: the GUI already reads and writes
558
+ * arbitrary paths the host process can reach, so executing commands the
559
+ * operator types carries the same parity.
560
+ * @param command - the shell command line to execute.
561
+ * @returns captured stdout/stderr and the exit code.
562
+ */
563
+ async runCommand(command) {
564
+ try {
565
+ const { stdout, stderr } = await execFile$1("sh", ["-c", command], {
566
+ timeout: 3e4,
567
+ maxBuffer: 4 * 1024 * 1024,
568
+ encoding: "utf8"
569
+ });
570
+ return {
571
+ stdout,
572
+ stderr,
573
+ exitCode: 0
574
+ };
575
+ } catch (error) {
576
+ const err = error;
577
+ return {
578
+ stdout: typeof err.stdout === "string" ? err.stdout : "",
579
+ stderr: typeof err.stderr === "string" ? err.stderr : error instanceof Error ? error.message : String(error),
580
+ exitCode: typeof err.code === "number" ? err.code : null
581
+ };
582
+ }
583
+ }
584
+ };
585
+ })();
586
+ /** Read at most `length` bytes from the file start. */
587
+ async function readFirst(path, length) {
588
+ const handle = await open(path, "r");
589
+ try {
590
+ const buffer = Buffer.alloc(length);
591
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
592
+ return bytesRead === length ? buffer : buffer.subarray(0, bytesRead);
593
+ } finally {
594
+ await handle.close();
595
+ }
596
+ }
597
+ /** Preview payload caps (bytes). */
598
+ const PREVIEW_TEXT_CAP = 4 * 1024 * 1024;
599
+ const PREVIEW_IMAGE_CAP = 4 * 1024 * 1024;
600
+ const PREVIEW_VIDEO_CAP = 12 * 1024 * 1024;
601
+ /** Image MIME by extension. */
602
+ const IMAGE_MIME = {
603
+ png: "image/png",
604
+ jpg: "image/jpeg",
605
+ jpeg: "image/jpeg",
606
+ gif: "image/gif",
607
+ webp: "image/webp",
608
+ svg: "image/svg+xml",
609
+ bmp: "image/bmp",
610
+ ico: "image/x-icon",
611
+ avif: "image/avif"
612
+ };
613
+ /** Video MIME by extension. */
614
+ const VIDEO_MIME = {
615
+ mp4: "video/mp4",
616
+ m4v: "video/mp4",
617
+ webm: "video/webm",
618
+ ogv: "video/ogg",
619
+ ogg: "video/ogg",
620
+ mov: "video/quicktime"
621
+ };
622
+ /** Extensions treated as plain text. */
623
+ const TEXT_EXTENSIONS = new Set([
624
+ "txt",
625
+ "text",
626
+ "md",
627
+ "markdown",
628
+ "json",
629
+ "yaml",
630
+ "yml",
631
+ "toml",
632
+ "ini",
633
+ "cfg",
634
+ "conf",
635
+ "log",
636
+ "csv",
637
+ "ts",
638
+ "tsx",
639
+ "js",
640
+ "jsx",
641
+ "mjs",
642
+ "cjs",
643
+ "css",
644
+ "scss",
645
+ "html",
646
+ "htm",
647
+ "xml",
648
+ "sh",
649
+ "bash",
650
+ "zsh",
651
+ "py",
652
+ "rb",
653
+ "go",
654
+ "rs",
655
+ "java",
656
+ "c",
657
+ "h",
658
+ "cpp",
659
+ "hpp",
660
+ "cs",
661
+ "php",
662
+ "sql",
663
+ "env",
664
+ "lock"
665
+ ]);
666
+ /** Decide the preview kind from the file extension (null = sniff). */
667
+ function previewKindOf(extension) {
668
+ const imageMime = IMAGE_MIME[extension];
669
+ if (imageMime !== void 0) return {
670
+ kind: "image",
671
+ mime: imageMime
672
+ };
673
+ const videoMime = VIDEO_MIME[extension];
674
+ if (videoMime !== void 0) return {
675
+ kind: "video",
676
+ mime: videoMime
677
+ };
678
+ if (TEXT_EXTENSIONS.has(extension)) return {
679
+ kind: "text",
680
+ mime: "text/plain"
681
+ };
682
+ return null;
683
+ }
684
+ /** UTF-8 sniff: no NUL bytes and few control bytes → likely plain text. */
685
+ function sniffTextKind(sample) {
686
+ let control = 0;
687
+ for (const byte of sample) {
688
+ if (byte === 0) return null;
689
+ if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) control += 1;
690
+ }
691
+ return sample.length > 0 && control / sample.length < .05 ? {
692
+ kind: "text",
693
+ mime: "text/plain"
694
+ } : null;
695
+ }
696
+ //#endregion
697
+ export { SystemMetricsService, SystemMetricsService as default };
@@ -0,0 +1,13 @@
1
+ //#region lib/types/invariant.js
2
+ /** Package-owned invariant companion. @module @danielng23/dsh-host-system-metrics/invariant */
3
+ const PACKAGE_NAME = "@danielng23/dsh-host-system-metrics";
4
+ /** Cordis companion plugin name. */
5
+ const name = "host-system-metrics-invariant";
6
+ /** Service required before the companion can reserve package ownership. */
7
+ const inject = ["invariants"];
8
+ /** No runtime invariant: every snapshot is projected directly from `node:os` at call time. */
9
+ const install = () => {};
10
+ /** Register this package's invariant companion. */
11
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
12
+ //#endregion
13
+ export { apply, inject, name };
@@ -0,0 +1,3 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */
2
+
3
+ export declare const TYPERT: unknown