@4pm/cli 1.4.0 → 1.4.2

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.
Files changed (2) hide show
  1. package/dist/index.js +122 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28794,6 +28794,7 @@ var WORKER_TOOL_CATALOG = [
28794
28794
  var WORKER_TOOL_PREREQUISITE_IDS = WORKER_TOOL_CATALOG.filter(
28795
28795
  (t) => t.installPackage === null
28796
28796
  ).map((t) => t.id);
28797
+ var WORKER_TOOL_BUNDLED_GLOBALS = ["npm", "corepack"];
28797
28798
 
28798
28799
  // vendor/@4pm/constants/ws.ts
28799
28800
  var WsChannel = {
@@ -33308,9 +33309,47 @@ async function readCgroup(file) {
33308
33309
  return null;
33309
33310
  }
33310
33311
  }
33312
+ function parseKv(text) {
33313
+ const map = /* @__PURE__ */ new Map();
33314
+ if (!text) return map;
33315
+ for (const line of text.split("\n")) {
33316
+ const [k, v] = line.trim().split(/\s+/);
33317
+ if (k && v !== void 0) {
33318
+ const n = Number(v);
33319
+ if (Number.isFinite(n)) map.set(k, n);
33320
+ }
33321
+ }
33322
+ return map;
33323
+ }
33324
+ function parsePressureSome(text) {
33325
+ if (!text) return void 0;
33326
+ const some = text.split("\n").find((l) => l.startsWith("some "));
33327
+ const m = some?.match(/avg10=([\d.]+)/);
33328
+ const n = m ? Number(m[1]) : NaN;
33329
+ return Number.isFinite(n) ? clampPct(n) : void 0;
33330
+ }
33331
+ function parseIoStat(text) {
33332
+ if (!text) return null;
33333
+ let rBytes = 0;
33334
+ let wBytes = 0;
33335
+ for (const line of text.split("\n")) {
33336
+ const r = line.match(/\brbytes=(\d+)/);
33337
+ const w = line.match(/\bwbytes=(\d+)/);
33338
+ if (r) rBytes += Number(r[1]);
33339
+ if (w) wBytes += Number(w[1]);
33340
+ }
33341
+ return { rBytes, wBytes };
33342
+ }
33343
+ function parseMaybeMax(text) {
33344
+ if (text === null) return null;
33345
+ if (text === "max") return null;
33346
+ const n = Number(text);
33347
+ return Number.isFinite(n) ? n : null;
33348
+ }
33311
33349
  function createWorkerMetricsSampler(diskPath) {
33312
33350
  const cgroupV2 = existsSync4(`${CGROUP}/cpu.stat`) && existsSync4(`${CGROUP}/memory.current`);
33313
- let prev = null;
33351
+ let cgPrev = null;
33352
+ let osPrev = null;
33314
33353
  async function cgroupCpuCount() {
33315
33354
  const raw = await readCgroup("cpu.max");
33316
33355
  if (raw) {
@@ -33322,22 +33361,6 @@ function createWorkerMetricsSampler(diskPath) {
33322
33361
  }
33323
33362
  return cpus().length;
33324
33363
  }
33325
- async function cgroupCpuPct(cpuCount) {
33326
- const stat2 = await readCgroup("cpu.stat");
33327
- const m = stat2?.match(/usage_usec\s+(\d+)/);
33328
- const usageUsec = m ? Number(m[1]) : NaN;
33329
- const now = Date.now();
33330
- if (!Number.isFinite(usageUsec)) return 0;
33331
- if (!prev) {
33332
- prev = { busy: usageUsec, ref: now };
33333
- return 0;
33334
- }
33335
- const dUsage = usageUsec - prev.busy;
33336
- const dWall = (now - prev.ref) * 1e3;
33337
- prev = { busy: usageUsec, ref: now };
33338
- if (dWall <= 0) return 0;
33339
- return clampPct(dUsage / (dWall * cpuCount) * 100);
33340
- }
33341
33364
  function osCpuPct() {
33342
33365
  let busy = 0;
33343
33366
  let total = 0;
@@ -33346,13 +33369,13 @@ function createWorkerMetricsSampler(diskPath) {
33346
33369
  busy += t.user + t.nice + t.sys + t.irq;
33347
33370
  total += t.user + t.nice + t.sys + t.irq + t.idle;
33348
33371
  }
33349
- if (!prev) {
33350
- prev = { busy, ref: total };
33372
+ if (!osPrev) {
33373
+ osPrev = { busy, total };
33351
33374
  return 0;
33352
33375
  }
33353
- const dBusy = busy - prev.busy;
33354
- const dTotal = total - prev.ref;
33355
- prev = { busy, ref: total };
33376
+ const dBusy = busy - osPrev.busy;
33377
+ const dTotal = total - osPrev.total;
33378
+ osPrev = { busy, total };
33356
33379
  if (dTotal <= 0) return 0;
33357
33380
  return clampPct(dBusy / dTotal * 100);
33358
33381
  }
@@ -33366,9 +33389,74 @@ function createWorkerMetricsSampler(diskPath) {
33366
33389
  return { usedGb: 0, totalGb: 0 };
33367
33390
  }
33368
33391
  }
33392
+ async function cgroupExtended(cpuCount, now) {
33393
+ const [cpuStatRaw, memStatRaw, ioStatRaw, cpuPsi, memPsi, ioPsi, swapCurRaw, swapMaxRaw, memEventsRaw, pidsCurRaw, pidsMaxRaw] = await Promise.all([
33394
+ readCgroup("cpu.stat"),
33395
+ readCgroup("memory.stat"),
33396
+ readCgroup("io.stat"),
33397
+ readCgroup("cpu.pressure"),
33398
+ readCgroup("memory.pressure"),
33399
+ readCgroup("io.pressure"),
33400
+ readCgroup("memory.swap.current"),
33401
+ readCgroup("memory.swap.max"),
33402
+ readCgroup("memory.events"),
33403
+ readCgroup("pids.current"),
33404
+ readCgroup("pids.max")
33405
+ ]);
33406
+ const cpuStat = parseKv(cpuStatRaw);
33407
+ const usageUsec = cpuStat.get("usage_usec") ?? NaN;
33408
+ const nrThrottled = cpuStat.get("nr_throttled") ?? 0;
33409
+ const nrPeriods = cpuStat.get("nr_periods") ?? 0;
33410
+ const io = parseIoStat(ioStatRaw);
33411
+ const extra = {};
33412
+ extra.cpuPressurePct = parsePressureSome(cpuPsi);
33413
+ extra.memPressurePct = parsePressureSome(memPsi);
33414
+ extra.ioPressurePct = parsePressureSome(ioPsi);
33415
+ const memStat = parseKv(memStatRaw);
33416
+ if (memStat.has("anon")) extra.memAnonMb = Math.round(memStat.get("anon") / MB);
33417
+ if (memStat.has("file")) extra.memCacheMb = Math.round(memStat.get("file") / MB);
33418
+ if (swapCurRaw !== null) extra.swapUsedMb = Math.round(Number(swapCurRaw) / MB);
33419
+ if (swapMaxRaw !== null) {
33420
+ const max = parseMaybeMax(swapMaxRaw);
33421
+ extra.swapTotalMb = max === null ? null : Math.round(max / MB);
33422
+ }
33423
+ const memEvents = parseKv(memEventsRaw);
33424
+ if (memEvents.has("oom_kill")) extra.oomKills = memEvents.get("oom_kill");
33425
+ if (pidsCurRaw !== null) {
33426
+ const n = Number(pidsCurRaw);
33427
+ if (Number.isFinite(n)) extra.pidsCurrent = n;
33428
+ }
33429
+ if (pidsMaxRaw !== null) extra.pidsMax = parseMaybeMax(pidsMaxRaw);
33430
+ let cpuPct = 0;
33431
+ if (Number.isFinite(usageUsec)) {
33432
+ if (cgPrev) {
33433
+ const dWallUsec = (now - cgPrev.wallMs) * 1e3;
33434
+ if (dWallUsec > 0) cpuPct = clampPct((usageUsec - cgPrev.usageUsec) / (dWallUsec * cpuCount) * 100);
33435
+ const dPeriods = nrPeriods - cgPrev.nrPeriods;
33436
+ if (dPeriods > 0) extra.cpuThrottledPct = clampPct((nrThrottled - cgPrev.nrThrottled) / dPeriods * 100);
33437
+ if (io) {
33438
+ const dSec = (now - cgPrev.wallMs) / 1e3;
33439
+ if (dSec > 0) {
33440
+ extra.diskReadMbps = round1((io.rBytes - cgPrev.ioR) / MB / dSec);
33441
+ extra.diskWriteMbps = round1((io.wBytes - cgPrev.ioW) / MB / dSec);
33442
+ }
33443
+ }
33444
+ }
33445
+ cgPrev = {
33446
+ usageUsec,
33447
+ nrThrottled,
33448
+ nrPeriods,
33449
+ ioR: io?.rBytes ?? 0,
33450
+ ioW: io?.wBytes ?? 0,
33451
+ wallMs: now
33452
+ };
33453
+ }
33454
+ return { cpuPct, extra };
33455
+ }
33369
33456
  return {
33370
33457
  async sample() {
33371
- const at = (/* @__PURE__ */ new Date()).toISOString();
33458
+ const now = Date.now();
33459
+ const at = new Date(now).toISOString();
33372
33460
  const { usedGb: diskUsedGb, totalGb: diskTotalGb } = await disk();
33373
33461
  const base = { platform: platform(), arch: arch(), diskUsedGb, diskTotalGb, at };
33374
33462
  if (cgroupV2) {
@@ -33377,14 +33465,15 @@ function createWorkerMetricsSampler(diskPath) {
33377
33465
  const memTotalBytes = memMaxRaw && memMaxRaw !== "max" ? Number(memMaxRaw) : totalmem();
33378
33466
  const memUsedBytes = memCurRaw ? Number(memCurRaw) : totalmem() - freemem();
33379
33467
  const cpuCount = await cgroupCpuCount();
33380
- const cpuPct = await cgroupCpuPct(cpuCount);
33468
+ const { cpuPct, extra } = await cgroupExtended(cpuCount, now);
33381
33469
  return {
33382
33470
  ...base,
33383
33471
  source: "cgroup",
33384
33472
  cpuCount,
33385
33473
  cpuPct,
33386
33474
  memUsedMb: Math.round(memUsedBytes / MB),
33387
- memTotalMb: Math.round(memTotalBytes / MB)
33475
+ memTotalMb: Math.round(memTotalBytes / MB),
33476
+ ...extra
33388
33477
  };
33389
33478
  }
33390
33479
  const total = totalmem();
@@ -36413,9 +36502,11 @@ async function listExtras() {
36413
36502
  } catch {
36414
36503
  return [];
36415
36504
  }
36416
- const catalogPkgs = new Set(
36417
- WORKER_TOOL_CATALOG.map((t) => t.installPackage).filter((p) => !!p)
36418
- );
36505
+ const catalogPkgs = new Set(WORKER_TOOL_BUNDLED_GLOBALS);
36506
+ for (const t of WORKER_TOOL_CATALOG) {
36507
+ catalogPkgs.add(t.id);
36508
+ if (t.installPackage) catalogPkgs.add(t.installPackage);
36509
+ }
36419
36510
  return Object.entries(deps).filter(([name]) => !catalogPkgs.has(name)).map(([name, info]) => ({
36420
36511
  id: name,
36421
36512
  label: name,
@@ -36453,6 +36544,9 @@ function resolvePackage(name, op) {
36453
36544
  if (WORKER_TOOL_PREREQUISITE_IDS.includes(name)) {
36454
36545
  return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
36455
36546
  }
36547
+ if (WORKER_TOOL_BUNDLED_GLOBALS.includes(name)) {
36548
+ return { error: `"${name}" ships with Node and cannot be ${op}ed from here.` };
36549
+ }
36456
36550
  if (!NPM_NAME.test(name)) return { error: `Invalid package name: "${name}".` };
36457
36551
  return { pkg: name };
36458
36552
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",