@hasna/machines 0.0.10 → 0.0.11

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.
@@ -5,29 +5,15 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- function __accessProp(key) {
9
- return this[key];
10
- }
11
- var __toESMCache_node;
12
- var __toESMCache_esm;
13
8
  var __toESM = (mod, isNodeMode, target) => {
14
- var canCache = mod != null && typeof mod === "object";
15
- if (canCache) {
16
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
- var cached = cache.get(mod);
18
- if (cached)
19
- return cached;
20
- }
21
9
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
10
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
11
  for (let key of __getOwnPropNames(mod))
24
12
  if (!__hasOwnProp.call(to, key))
25
13
  __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
14
+ get: () => mod[key],
27
15
  enumerable: true
28
16
  });
29
- if (canCache)
30
- cache.set(mod, to);
31
17
  return to;
32
18
  };
33
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -2130,15 +2116,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
2130
2116
  var __defProp2 = Object.defineProperty;
2131
2117
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
2132
2118
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
2133
- function __accessProp2(key) {
2119
+ function __accessProp(key) {
2134
2120
  return this[key];
2135
2121
  }
2136
- var __toESMCache_node2;
2137
- var __toESMCache_esm2;
2122
+ var __toESMCache_node;
2123
+ var __toESMCache_esm;
2138
2124
  var __toESM2 = (mod, isNodeMode, target) => {
2139
2125
  var canCache = mod != null && typeof mod === "object";
2140
2126
  if (canCache) {
2141
- var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
2127
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
2142
2128
  var cached = cache.get(mod);
2143
2129
  if (cached)
2144
2130
  return cached;
@@ -2148,7 +2134,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
2148
2134
  for (let key of __getOwnPropNames2(mod))
2149
2135
  if (!__hasOwnProp2.call(to, key))
2150
2136
  __defProp2(to, key, {
2151
- get: __accessProp2.bind(mod, key),
2137
+ get: __accessProp.bind(mod, key),
2152
2138
  enumerable: true
2153
2139
  });
2154
2140
  if (canCache)
package/dist/cli/index.js CHANGED
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- function __accessProp(key) {
9
- return this[key];
10
- }
11
- var __toESMCache_node;
12
- var __toESMCache_esm;
13
8
  var __toESM = (mod, isNodeMode, target) => {
14
- var canCache = mod != null && typeof mod === "object";
15
- if (canCache) {
16
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
- var cached = cache.get(mod);
18
- if (cached)
19
- return cached;
20
- }
21
9
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
10
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
11
  for (let key of __getOwnPropNames(mod))
24
12
  if (!__hasOwnProp.call(to, key))
25
13
  __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
14
+ get: () => mod[key],
27
15
  enumerable: true
28
16
  });
29
- if (canCache)
30
- cache.set(mod, to);
31
17
  return to;
32
18
  };
33
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
- var __returnValue = (v) => v;
35
- function __exportSetter(name, newValue) {
36
- this[name] = __returnValue.bind(null, newValue);
37
- }
38
20
  var __export = (target, all) => {
39
21
  for (var name in all)
40
22
  __defProp(target, name, {
41
23
  get: all[name],
42
24
  enumerable: true,
43
25
  configurable: true,
44
- set: __exportSetter.bind(all, name)
26
+ set: (newValue) => all[name] = () => newValue
45
27
  });
46
28
  };
47
29
  var __require = import.meta.require;
@@ -6780,15 +6762,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
6780
6762
  var __defProp2 = Object.defineProperty;
6781
6763
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
6782
6764
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
6783
- function __accessProp2(key) {
6765
+ function __accessProp(key) {
6784
6766
  return this[key];
6785
6767
  }
6786
- var __toESMCache_node2;
6787
- var __toESMCache_esm2;
6768
+ var __toESMCache_node;
6769
+ var __toESMCache_esm;
6788
6770
  var __toESM2 = (mod, isNodeMode, target) => {
6789
6771
  var canCache = mod != null && typeof mod === "object";
6790
6772
  if (canCache) {
6791
- var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
6773
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
6792
6774
  var cached = cache.get(mod);
6793
6775
  if (cached)
6794
6776
  return cached;
@@ -6798,7 +6780,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
6798
6780
  for (let key of __getOwnPropNames2(mod))
6799
6781
  if (!__hasOwnProp2.call(to, key))
6800
6782
  __defProp2(to, key, {
6801
- get: __accessProp2.bind(mod, key),
6783
+ get: __accessProp.bind(mod, key),
6802
6784
  enumerable: true
6803
6785
  });
6804
6786
  if (canCache)
@@ -6806,9 +6788,9 @@ var __toESM2 = (mod, isNodeMode, target) => {
6806
6788
  return to;
6807
6789
  };
6808
6790
  var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
6809
- var __returnValue2 = (v) => v;
6810
- function __exportSetter2(name, newValue) {
6811
- this[name] = __returnValue2.bind(null, newValue);
6791
+ var __returnValue = (v) => v;
6792
+ function __exportSetter(name, newValue) {
6793
+ this[name] = __returnValue.bind(null, newValue);
6812
6794
  }
6813
6795
  var __export2 = (target, all) => {
6814
6796
  for (var name in all)
@@ -6816,7 +6798,7 @@ var __export2 = (target, all) => {
6816
6798
  get: all[name],
6817
6799
  enumerable: true,
6818
6800
  configurable: true,
6819
- set: __exportSetter2.bind(all, name)
6801
+ set: __exportSetter.bind(all, name)
6820
6802
  });
6821
6803
  };
6822
6804
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -18368,6 +18350,482 @@ async function discoverPeers() {
18368
18350
  return peers;
18369
18351
  }
18370
18352
 
18353
+ // src/commands/heal.ts
18354
+ import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
18355
+ import { join as join11 } from "path";
18356
+ var DEFAULT_THRESHOLDS = {
18357
+ reconnect: 3,
18358
+ nmRestart: 7,
18359
+ fallback: 12,
18360
+ reboot: 15
18361
+ };
18362
+ var DEFAULT_HEAL_CONFIG = {
18363
+ version: 1,
18364
+ enabled: true,
18365
+ wifiInterface: "",
18366
+ preferredSsid: "",
18367
+ fallbackSsid: "",
18368
+ internetUrl: "https://1.1.1.1",
18369
+ tailscaleAnchors: [],
18370
+ quorumRequired: 2,
18371
+ intervalSec: 60,
18372
+ thresholds: { ...DEFAULT_THRESHOLDS },
18373
+ rebootMinIntervalSec: 1800,
18374
+ nmRestartMinIntervalSec: 1800,
18375
+ reconnectMinIntervalSec: 120,
18376
+ healthyWindowSec: 300,
18377
+ maxFailedBootRecoveries: 2,
18378
+ bootBackoffSec: 21600,
18379
+ fallbackWindowSec: 600,
18380
+ gpuJobGuard: true,
18381
+ allowReboot: true
18382
+ };
18383
+ function defaultHealState() {
18384
+ return {
18385
+ failCount: 0,
18386
+ bootId: "",
18387
+ bootHealthySince: null,
18388
+ lastRebootAttempt: 0,
18389
+ lastNmRestart: 0,
18390
+ lastReconnect: 0,
18391
+ lastFallback: 0,
18392
+ degradedUntil: 0,
18393
+ pendingRebootRecovery: false,
18394
+ failedBootRecoveries: 0,
18395
+ rebootSuppressUntil: 0
18396
+ };
18397
+ }
18398
+ function getHealConfigPath() {
18399
+ return process.env["HASNA_MACHINES_HEAL_CONFIG_PATH"] || join11(getDataDir(), "heal-config.json");
18400
+ }
18401
+ function getHealStatePath() {
18402
+ return process.env["HASNA_MACHINES_HEAL_STATE_PATH"] || join11(getDataDir(), "heal-state.json");
18403
+ }
18404
+ function readHealConfig(path) {
18405
+ const p = path || getHealConfigPath();
18406
+ if (!existsSync9(p))
18407
+ return { ...DEFAULT_HEAL_CONFIG, thresholds: { ...DEFAULT_THRESHOLDS } };
18408
+ const parsed = JSON.parse(readFileSync10(p, "utf8"));
18409
+ return {
18410
+ ...DEFAULT_HEAL_CONFIG,
18411
+ ...parsed,
18412
+ thresholds: { ...DEFAULT_THRESHOLDS, ...parsed.thresholds || {} },
18413
+ tailscaleAnchors: parsed.tailscaleAnchors ?? []
18414
+ };
18415
+ }
18416
+ function writeHealConfig(config, path) {
18417
+ const p = path || getHealConfigPath();
18418
+ ensureParentDir(p);
18419
+ writeFileSync7(p, `${JSON.stringify(config, null, 2)}
18420
+ `, "utf8");
18421
+ }
18422
+ function readHealState(path) {
18423
+ const p = path || getHealStatePath();
18424
+ if (!existsSync9(p))
18425
+ return defaultHealState();
18426
+ try {
18427
+ return { ...defaultHealState(), ...JSON.parse(readFileSync10(p, "utf8")) };
18428
+ } catch {
18429
+ return defaultHealState();
18430
+ }
18431
+ }
18432
+ function writeHealState(state, path) {
18433
+ const p = path || getHealStatePath();
18434
+ ensureParentDir(p);
18435
+ writeFileSync7(p, `${JSON.stringify(state, null, 2)}
18436
+ `, "utf8");
18437
+ }
18438
+ function evaluateHealth(probe, config, state) {
18439
+ const reasons = [];
18440
+ const inDegraded = state.degradedUntil > 0;
18441
+ const acceptableSsid = probe.associatedSsid === config.preferredSsid || config.fallbackSsid !== "" && inDegraded && probe.associatedSsid === config.fallbackSsid;
18442
+ if (!acceptableSsid)
18443
+ reasons.push(`wrong-ssid:${probe.associatedSsid ?? "none"}`);
18444
+ if (!probe.gatewayReachable)
18445
+ reasons.push("gateway-unreachable");
18446
+ let remoteScore = 0;
18447
+ for (const [anchor, ok] of Object.entries(probe.anchorsReachable)) {
18448
+ if (ok)
18449
+ remoteScore += 1;
18450
+ else
18451
+ reasons.push(`anchor-down:${anchor}`);
18452
+ }
18453
+ if (probe.internetReachable)
18454
+ remoteScore += 1;
18455
+ else
18456
+ reasons.push("internet-down");
18457
+ const localOk = acceptableSsid && probe.gatewayReachable;
18458
+ const quorumOk = remoteScore >= config.quorumRequired;
18459
+ if (!quorumOk)
18460
+ reasons.push(`quorum:${remoteScore}/${config.quorumRequired}`);
18461
+ return { healthy: localOk && quorumOk, remoteScore, reasons };
18462
+ }
18463
+ function decideAction(input) {
18464
+ const { healthy, now, gpuBusy, config, currentBootId } = input;
18465
+ const s = { ...input.state };
18466
+ const t = config.thresholds;
18467
+ if (s.bootId !== currentBootId) {
18468
+ s.bootId = currentBootId;
18469
+ s.bootHealthySince = null;
18470
+ s.failCount = 0;
18471
+ }
18472
+ if (healthy) {
18473
+ s.failCount = 0;
18474
+ if (s.bootHealthySince === null)
18475
+ s.bootHealthySince = now;
18476
+ if (now - s.bootHealthySince >= config.healthyWindowSec) {
18477
+ s.failedBootRecoveries = 0;
18478
+ s.rebootSuppressUntil = 0;
18479
+ s.pendingRebootRecovery = false;
18480
+ }
18481
+ if (s.degradedUntil > 0 && now >= s.degradedUntil) {
18482
+ s.degradedUntil = 0;
18483
+ return { action: "restore_preferred", state: s };
18484
+ }
18485
+ return { action: "none", state: s };
18486
+ }
18487
+ s.failCount += 1;
18488
+ s.bootHealthySince = null;
18489
+ let tier = "none";
18490
+ if (s.failCount >= t.reboot)
18491
+ tier = "reboot";
18492
+ else if (s.failCount >= t.fallback && config.fallbackSsid !== "")
18493
+ tier = "fallback";
18494
+ else if (s.failCount >= t.nmRestart)
18495
+ tier = "nmRestart";
18496
+ else if (s.failCount >= t.reconnect)
18497
+ tier = "reconnect";
18498
+ const tryReconnect = (reason) => {
18499
+ if (now - s.lastReconnect >= config.reconnectMinIntervalSec) {
18500
+ s.lastReconnect = now;
18501
+ return { action: "reconnect_wifi", suppressedReason: reason, state: s };
18502
+ }
18503
+ return { action: "none", suppressedReason: reason, state: s };
18504
+ };
18505
+ switch (tier) {
18506
+ case "reconnect":
18507
+ return tryReconnect();
18508
+ case "nmRestart":
18509
+ if (now - s.lastNmRestart >= config.nmRestartMinIntervalSec) {
18510
+ s.lastNmRestart = now;
18511
+ return { action: "restart_nm", state: s };
18512
+ }
18513
+ return tryReconnect();
18514
+ case "fallback":
18515
+ if (now - s.lastFallback >= config.fallbackWindowSec) {
18516
+ s.lastFallback = now;
18517
+ s.degradedUntil = now + config.fallbackWindowSec;
18518
+ return { action: "fallback_ssid", state: s };
18519
+ }
18520
+ return tryReconnect();
18521
+ case "reboot": {
18522
+ let reason = null;
18523
+ if (!config.allowReboot)
18524
+ reason = "disabled";
18525
+ else if (now < s.rebootSuppressUntil)
18526
+ reason = "loop";
18527
+ else if (config.gpuJobGuard && gpuBusy)
18528
+ reason = "gpu";
18529
+ else if (now - s.lastRebootAttempt < config.rebootMinIntervalSec)
18530
+ reason = "rate";
18531
+ if (reason)
18532
+ return tryReconnect(reason);
18533
+ if (s.pendingRebootRecovery) {
18534
+ s.failedBootRecoveries += 1;
18535
+ if (s.failedBootRecoveries >= config.maxFailedBootRecoveries) {
18536
+ s.rebootSuppressUntil = now + config.bootBackoffSec;
18537
+ return tryReconnect("loop");
18538
+ }
18539
+ }
18540
+ s.lastRebootAttempt = now;
18541
+ s.pendingRebootRecovery = true;
18542
+ return { action: "reboot", state: s };
18543
+ }
18544
+ default:
18545
+ return { action: "none", state: s };
18546
+ }
18547
+ }
18548
+ function sh(cmd, timeoutMs = 8000) {
18549
+ const r = Bun.spawnSync(["bash", "-lc", cmd], { stdout: "pipe", stderr: "pipe", env: process.env, timeout: timeoutMs });
18550
+ return { ok: r.exitCode === 0, out: r.stdout.toString("utf8").trim() };
18551
+ }
18552
+ function getCurrentBootId() {
18553
+ try {
18554
+ return readFileSync10("/proc/sys/kernel/random/boot_id", "utf8").trim();
18555
+ } catch {
18556
+ return "";
18557
+ }
18558
+ }
18559
+ function detectWifiInterface() {
18560
+ const r = sh(`nmcli -t -f DEVICE,TYPE device status 2>/dev/null | awk -F: '$2=="wifi"{print $1; exit}'`);
18561
+ return r.ok ? r.out : "";
18562
+ }
18563
+ function detectGateway() {
18564
+ const r = sh(`ip route 2>/dev/null | awk '/^default/{print $3; exit}'`);
18565
+ return r.ok ? r.out : "";
18566
+ }
18567
+ function getAssociatedSsid() {
18568
+ const r = sh(`iwgetid -r 2>/dev/null || nmcli -t -f active,ssid dev wifi 2>/dev/null | awk -F: '/^yes/{print $2; exit}'`);
18569
+ return r.ok && r.out ? r.out : null;
18570
+ }
18571
+ function pingHost(host) {
18572
+ if (!host)
18573
+ return false;
18574
+ return sh(`ping -c1 -W2 ${host} >/dev/null 2>&1 && echo ok`, 5000).out === "ok";
18575
+ }
18576
+ function internetReachable(url) {
18577
+ return sh(`curl -sf -m5 -o /dev/null ${url} && echo ok`, 8000).out === "ok";
18578
+ }
18579
+ function tailscalePing(host) {
18580
+ return sh(`timeout 8 tailscale ping --until-direct=false ${host} 2>/dev/null | grep -q pong && echo ok`, 1e4).out === "ok";
18581
+ }
18582
+ function gpuBusy() {
18583
+ const r = sh(`command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi --query-compute-apps=pid --format=csv,noheader 2>/dev/null | grep -q . && echo busy`, 6000);
18584
+ return r.out === "busy";
18585
+ }
18586
+ function discoverAnchors() {
18587
+ const r = sh(`tailscale status --json 2>/dev/null`);
18588
+ if (!r.ok)
18589
+ return [];
18590
+ try {
18591
+ const status = JSON.parse(r.out);
18592
+ const anchors = [];
18593
+ for (const peer of Object.values(status.Peer || {})) {
18594
+ const name = peer.HostName || (peer.DNSName || "").split(".")[0];
18595
+ if (name)
18596
+ anchors.push(name);
18597
+ }
18598
+ return anchors;
18599
+ } catch {
18600
+ return [];
18601
+ }
18602
+ }
18603
+ function probeHealth(config) {
18604
+ const gw = config.wifiInterface ? detectGateway() : detectGateway();
18605
+ const anchors = config.tailscaleAnchors.length > 0 ? config.tailscaleAnchors : discoverAnchors().slice(0, 3);
18606
+ const anchorsReachable = {};
18607
+ for (const a of anchors)
18608
+ anchorsReachable[a] = tailscalePing(a);
18609
+ return {
18610
+ associatedSsid: getAssociatedSsid(),
18611
+ gatewayReachable: pingHost(gw),
18612
+ anchorsReachable,
18613
+ internetReachable: internetReachable(config.internetUrl)
18614
+ };
18615
+ }
18616
+ function executeAction(action, config) {
18617
+ const iface = config.wifiInterface || detectWifiInterface();
18618
+ switch (action) {
18619
+ case "reconnect_wifi":
18620
+ sh(`nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
18621
+ return `reconnected wifi to ${config.preferredSsid}`;
18622
+ case "restart_nm":
18623
+ sh(`systemctl restart NetworkManager 2>&1; sleep 5; nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 40000);
18624
+ return "restarted NetworkManager";
18625
+ case "fallback_ssid":
18626
+ sh(`nmcli connection modify "${config.fallbackSsid}" connection.autoconnect yes 2>&1; nmcli connection up "${config.fallbackSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
18627
+ return `switched to degraded fallback ${config.fallbackSsid}`;
18628
+ case "restore_preferred":
18629
+ sh(`nmcli connection modify "${config.fallbackSsid}" connection.autoconnect no 2>&1; nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
18630
+ return `restored preferred ${config.preferredSsid}`;
18631
+ case "reboot":
18632
+ sh(`systemctl reboot 2>&1 || reboot 2>&1`, 1e4);
18633
+ return "reboot issued";
18634
+ default:
18635
+ return "no action";
18636
+ }
18637
+ }
18638
+
18639
+ // src/commands/heal-daemon.ts
18640
+ import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
18641
+ import { join as join12 } from "path";
18642
+ var DAEMON_PID_PATH2 = join12(getDataDir(), "heal-daemon.pid");
18643
+ var SERVICE_PATH = "/etc/systemd/system/machines-heal.service";
18644
+ var SYSTEM_CONF = "/etc/systemd/system.conf";
18645
+ function log(msg) {
18646
+ console.log(`${new Date().toISOString()} [machines-heal] ${msg}`);
18647
+ }
18648
+ function runHealOnce(config, opts = {}) {
18649
+ const state = readHealState();
18650
+ const probe = probeHealth(config);
18651
+ const health = evaluateHealth(probe, config, state);
18652
+ const busy = config.gpuJobGuard ? gpuBusy() : false;
18653
+ const decision = decideAction({
18654
+ state,
18655
+ healthy: health.healthy,
18656
+ now: Math.floor(Date.now() / 1000),
18657
+ gpuBusy: busy,
18658
+ config,
18659
+ currentBootId: getCurrentBootId()
18660
+ });
18661
+ let executed = "skipped (dry-run)";
18662
+ if (!opts.dryRun) {
18663
+ writeHealState(decision.state);
18664
+ if (decision.action !== "none")
18665
+ executed = executeAction(decision.action, config);
18666
+ else
18667
+ executed = "no action";
18668
+ }
18669
+ const result = {
18670
+ healthy: health.healthy,
18671
+ action: decision.action,
18672
+ suppressedReason: decision.suppressedReason,
18673
+ reasons: health.reasons,
18674
+ remoteScore: health.remoteScore,
18675
+ failCount: decision.state.failCount,
18676
+ executed
18677
+ };
18678
+ const sup = decision.suppressedReason ? ` suppressed=${decision.suppressedReason}` : "";
18679
+ log(health.healthy ? `healthy (quorum ${health.remoteScore}) action=${decision.action} ${executed}` : `UNHEALTHY [${health.reasons.join(",")}] fails=${decision.state.failCount} action=${decision.action}${sup} -> ${executed}`);
18680
+ return result;
18681
+ }
18682
+ function writePid2(pid) {
18683
+ writeFileSync8(DAEMON_PID_PATH2, `${pid}
18684
+ `);
18685
+ }
18686
+ function readPid2() {
18687
+ try {
18688
+ const pid = Number.parseInt(readFileSync11(DAEMON_PID_PATH2, "utf8").trim());
18689
+ return Number.isFinite(pid) ? pid : null;
18690
+ } catch {
18691
+ return null;
18692
+ }
18693
+ }
18694
+ function isProcessRunning2(pid) {
18695
+ try {
18696
+ process.kill(pid, 0);
18697
+ return true;
18698
+ } catch {
18699
+ return false;
18700
+ }
18701
+ }
18702
+ function stopHealDaemon() {
18703
+ const pid = readPid2();
18704
+ if (pid && isProcessRunning2(pid)) {
18705
+ process.kill(pid, "SIGTERM");
18706
+ return { stopped: true, pid };
18707
+ }
18708
+ return { stopped: false, pid };
18709
+ }
18710
+ function startHealDaemon() {
18711
+ const config = readHealConfig();
18712
+ if (!config.preferredSsid) {
18713
+ log("refusing to start: preferredSsid is not configured (run `machines heal config --set ...`)");
18714
+ process.exit(1);
18715
+ }
18716
+ writePid2(process.pid);
18717
+ log(`daemon started (pid ${process.pid}) interval=${config.intervalSec}s preferred=${config.preferredSsid}`);
18718
+ const tick = () => {
18719
+ try {
18720
+ runHealOnce(config);
18721
+ } catch (err) {
18722
+ log(`tick error: ${err.message}`);
18723
+ }
18724
+ };
18725
+ tick();
18726
+ setInterval(tick, Math.max(10, config.intervalSec) * 1000);
18727
+ }
18728
+ function sh2(cmd, timeoutMs = 15000) {
18729
+ const r = Bun.spawnSync(["bash", "-lc", cmd], { stdout: "pipe", stderr: "pipe", env: process.env, timeout: timeoutMs });
18730
+ return { ok: r.exitCode === 0, out: `${r.stdout.toString("utf8")}${r.stderr.toString("utf8")}`.trim() };
18731
+ }
18732
+ function applyDeterminism(config) {
18733
+ const iface = config.wifiInterface || detectWifiInterface();
18734
+ const log2 = [];
18735
+ if (!config.preferredSsid)
18736
+ return ["no preferredSsid configured; skipping determinism"];
18737
+ sh2(`nmcli connection modify "${config.preferredSsid}" connection.autoconnect yes connection.autoconnect-priority 10 802-11-wireless.powersave 2`);
18738
+ log2.push(`pinned ${config.preferredSsid} (autoconnect, priority 10, powersave off)`);
18739
+ const profiles = sh2(`nmcli -t -f NAME,TYPE connection show 2>/dev/null | awk -F: '$2 ~ /wireless/{print $1}'`).out.split(`
18740
+ `).filter(Boolean);
18741
+ for (const p of profiles) {
18742
+ if (p === config.preferredSsid)
18743
+ continue;
18744
+ if (p === config.fallbackSsid) {
18745
+ sh2(`nmcli connection modify "${p}" connection.autoconnect no`);
18746
+ log2.push(`disabled autoconnect on fallback ${p}`);
18747
+ continue;
18748
+ }
18749
+ sh2(`nmcli connection modify "${p}" connection.autoconnect no`);
18750
+ log2.push(`disabled autoconnect on ${p}`);
18751
+ }
18752
+ if (iface) {
18753
+ sh2(`iw dev ${iface} set power_save off 2>/dev/null || true`);
18754
+ log2.push(`power_save off on ${iface}`);
18755
+ }
18756
+ return log2;
18757
+ }
18758
+ function enableHardwareWatchdog() {
18759
+ const log2 = [];
18760
+ if (!existsSync10(SYSTEM_CONF))
18761
+ return ["/etc/systemd/system.conf not found; skipping hardware watchdog"];
18762
+ let conf = readFileSync11(SYSTEM_CONF, "utf8");
18763
+ const set = (key, value) => {
18764
+ const re = new RegExp(`^#?\\s*${key}=.*$`, "m");
18765
+ if (re.test(conf))
18766
+ conf = conf.replace(re, `${key}=${value}`);
18767
+ else
18768
+ conf += `
18769
+ ${key}=${value}
18770
+ `;
18771
+ };
18772
+ set("RuntimeWatchdogSec", "20s");
18773
+ set("RebootWatchdogSec", "2min");
18774
+ writeFileSync8(SYSTEM_CONF, conf);
18775
+ sh2("systemctl daemon-reexec");
18776
+ log2.push("hardware watchdog: RuntimeWatchdogSec=20s RebootWatchdogSec=2min");
18777
+ return log2;
18778
+ }
18779
+ function binPath() {
18780
+ const r = sh2("command -v machines");
18781
+ return r.ok && r.out ? r.out.split(`
18782
+ `)[0].trim() : "machines";
18783
+ }
18784
+ function installHealService() {
18785
+ const log2 = [];
18786
+ const exec = binPath();
18787
+ const unit = `[Unit]
18788
+ Description=Hasna machines self-healing network watchdog
18789
+ After=network.target NetworkManager.service tailscaled.service
18790
+ Wants=network.target
18791
+
18792
+ [Service]
18793
+ Type=simple
18794
+ ExecStart=${exec} heal daemon
18795
+ Restart=always
18796
+ RestartSec=10
18797
+ # Persisted state/config live in root's data dir.
18798
+ Environment=HOME=/root
18799
+
18800
+ [Install]
18801
+ WantedBy=multi-user.target
18802
+ `;
18803
+ writeFileSync8(SERVICE_PATH, unit);
18804
+ sh2("systemctl daemon-reload");
18805
+ sh2("systemctl enable --now machines-heal.service");
18806
+ log2.push(`installed + enabled ${SERVICE_PATH} (ExecStart=${exec} heal daemon)`);
18807
+ return log2;
18808
+ }
18809
+ function uninstallHealService() {
18810
+ const log2 = [];
18811
+ sh2("systemctl disable --now machines-heal.service 2>/dev/null || true");
18812
+ if (existsSync10(SERVICE_PATH)) {
18813
+ sh2(`rm -f ${SERVICE_PATH}`);
18814
+ sh2("systemctl daemon-reload");
18815
+ log2.push(`removed ${SERVICE_PATH}`);
18816
+ } else {
18817
+ log2.push("service not installed");
18818
+ }
18819
+ return log2;
18820
+ }
18821
+ function healServiceStatus() {
18822
+ return {
18823
+ installed: existsSync10(SERVICE_PATH),
18824
+ active: sh2("systemctl is-active machines-heal.service").out === "active",
18825
+ enabled: sh2("systemctl is-enabled machines-heal.service 2>/dev/null").out === "enabled"
18826
+ };
18827
+ }
18828
+
18371
18829
  // src/cli-utils.ts
18372
18830
  function parseIntegerOption(value, label, constraints = {}) {
18373
18831
  const parsed = Number.parseInt(value, 10);
@@ -18399,7 +18857,7 @@ ${items.map((item) => `- ${item}`).join(`
18399
18857
 
18400
18858
  // src/cli/index.ts
18401
18859
  import { rmSync as rmSync2 } from "fs";
18402
- import { readFileSync as readFileSync10 } from "fs";
18860
+ import { readFileSync as readFileSync12 } from "fs";
18403
18861
  var program2 = new Command;
18404
18862
  function printJsonOrText(data, text, json = false) {
18405
18863
  if (json || program2.opts().quiet) {
@@ -18546,7 +19004,7 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
18546
19004
  console.error("error: --from-stdin requires piped input");
18547
19005
  process.exit(1);
18548
19006
  }
18549
- const input = readFileSync10(0, "utf8");
19007
+ const input = readFileSync12(0, "utf8");
18550
19008
  const machine2 = JSON.parse(input);
18551
19009
  console.log(JSON.stringify(manifestAdd(machine2), null, 2));
18552
19010
  return;
@@ -18785,4 +19243,98 @@ program2.command("serve").description("Serve a local fleet dashboard and JSON AP
18785
19243
  const server = startDashboardServer({ host: info.host, port: info.port });
18786
19244
  console.log(source_default.green(`machines dashboard listening on http://${server.hostname}:${server.port}`));
18787
19245
  });
19246
+ var healCommand = program2.command("heal").description("Self-healing network watchdog: keeps a Wi-Fi node reachable (SSID pinning + peer-reachability + gated reboot)");
19247
+ function requireRoot() {
19248
+ const uid = process.getuid ? process.getuid() : 1;
19249
+ if (uid !== 0) {
19250
+ console.error(source_default.red("error: this command must run as root (try: sudo machines heal install)"));
19251
+ return false;
19252
+ }
19253
+ return true;
19254
+ }
19255
+ healCommand.command("config").description(`View or update self-healing config (e.g. --set '{"preferredSsid":"X81ND","fallbackSsid":"DIGI-s2N5"}')`).option("--set <json>", "Merge a JSON object into the config").option("-j, --json", "Print JSON output", false).action((options) => {
19256
+ if (options.set) {
19257
+ const current = readHealConfig();
19258
+ const partial = JSON.parse(options.set);
19259
+ writeHealConfig({
19260
+ ...current,
19261
+ ...partial,
19262
+ thresholds: { ...current.thresholds, ...partial.thresholds || {} }
19263
+ });
19264
+ }
19265
+ const config = readHealConfig();
19266
+ printJsonOrText(config, renderKeyValueTable([
19267
+ ["enabled", String(config.enabled)],
19268
+ ["preferredSsid", config.preferredSsid || source_default.yellow("(unset)")],
19269
+ ["fallbackSsid", config.fallbackSsid || "(none)"],
19270
+ ["anchors", config.tailscaleAnchors.length ? config.tailscaleAnchors.join(", ") : "(auto-discover)"],
19271
+ ["quorumRequired", String(config.quorumRequired)],
19272
+ ["intervalSec", String(config.intervalSec)],
19273
+ ["thresholds", `reconnect=${config.thresholds.reconnect} nm=${config.thresholds.nmRestart} fallback=${config.thresholds.fallback} reboot=${config.thresholds.reboot}`],
19274
+ ["allowReboot", String(config.allowReboot)],
19275
+ ["gpuJobGuard", String(config.gpuJobGuard)]
19276
+ ]), options.json);
19277
+ });
19278
+ healCommand.command("check").description("Run one health + decision tick read-only (no side effects)").option("-j, --json", "Print JSON output", false).action((options) => {
19279
+ const result = runHealOnce(readHealConfig(), { dryRun: true });
19280
+ printJsonOrText(result, renderList("heal check", [
19281
+ `health: ${result.healthy ? source_default.green("HEALTHY") : source_default.red("UNHEALTHY")} (remote quorum ${result.remoteScore})`,
19282
+ `reasons: ${result.reasons.length ? result.reasons.join(", ") : "none"}`,
19283
+ `would do: ${result.action}${result.suppressedReason ? ` (reboot suppressed: ${result.suppressedReason})` : ""}`,
19284
+ `consecutive fails: ${result.failCount}`
19285
+ ]), options.json);
19286
+ });
19287
+ healCommand.command("status").description("Show watchdog service status and last persisted state").option("-j, --json", "Print JSON output", false).action((options) => {
19288
+ const svc = healServiceStatus();
19289
+ const state = readHealState();
19290
+ const config = readHealConfig();
19291
+ printJsonOrText({ service: svc, state, config }, renderKeyValueTable([
19292
+ ["service installed", svc.installed ? source_default.green("yes") : "no"],
19293
+ ["service active", svc.active ? source_default.green("yes") : source_default.yellow("no")],
19294
+ ["service enabled", svc.enabled ? "yes" : "no"],
19295
+ ["preferredSsid", config.preferredSsid || source_default.yellow("(unset)")],
19296
+ ["consecutive fails", String(state.failCount)],
19297
+ ["pending reboot recovery", String(state.pendingRebootRecovery)],
19298
+ ["failed boot recoveries", String(state.failedBootRecoveries)]
19299
+ ]), options.json);
19300
+ });
19301
+ healCommand.command("daemon").description("Run the watchdog loop in the foreground (used by systemd)").action(() => {
19302
+ startHealDaemon();
19303
+ });
19304
+ healCommand.command("stop").description("Stop a foreground daemon started via `heal daemon`").action(() => {
19305
+ const r = stopHealDaemon();
19306
+ console.log(r.stopped ? `stopped heal daemon (pid ${r.pid})` : "heal daemon not running");
19307
+ });
19308
+ healCommand.command("determinism").description("Pin the preferred SSID, disable other autoconnects, turn off Wi-Fi power save").action(() => {
19309
+ const log2 = applyDeterminism(readHealConfig());
19310
+ console.log(renderList("determinism", log2));
19311
+ });
19312
+ healCommand.command("install").description("Install the watchdog: determinism + hardware watchdog + systemd service (requires root)").option("--no-determinism", "Skip SSID pinning / power-save changes").option("--no-watchdog", "Skip enabling the systemd hardware watchdog").option("--no-service", "Skip installing the systemd service").action((options) => {
19313
+ if (!requireRoot()) {
19314
+ process.exitCode = 1;
19315
+ return;
19316
+ }
19317
+ const config = readHealConfig();
19318
+ if (!config.preferredSsid) {
19319
+ console.error(source_default.red(`error: set preferredSsid first: machines heal config --set '{"preferredSsid":"X81ND"}'`));
19320
+ process.exitCode = 1;
19321
+ return;
19322
+ }
19323
+ const out = [];
19324
+ if (options.determinism !== false)
19325
+ out.push(...applyDeterminism(config));
19326
+ if (options.watchdog !== false)
19327
+ out.push(...enableHardwareWatchdog());
19328
+ if (options.service !== false)
19329
+ out.push(...installHealService());
19330
+ console.log(renderList("install", out));
19331
+ console.log(source_default.green("self-healing watchdog installed"));
19332
+ });
19333
+ healCommand.command("uninstall").description("Remove the systemd watchdog service (requires root)").action(() => {
19334
+ if (!requireRoot()) {
19335
+ process.exitCode = 1;
19336
+ return;
19337
+ }
19338
+ console.log(renderList("uninstall", uninstallHealService()));
19339
+ });
18788
19340
  await program2.parseAsync(process.argv);
@@ -0,0 +1,36 @@
1
+ import { type HealConfig } from "./heal.js";
2
+ export interface HealTickResult {
3
+ healthy: boolean;
4
+ action: string;
5
+ suppressedReason?: string;
6
+ reasons: string[];
7
+ remoteScore: number;
8
+ failCount: number;
9
+ executed: string;
10
+ }
11
+ /** Run a single health/decision tick. With dryRun=true, never executes side effects. */
12
+ export declare function runHealOnce(config: HealConfig, opts?: {
13
+ dryRun?: boolean;
14
+ }): HealTickResult;
15
+ export declare function stopHealDaemon(): {
16
+ stopped: boolean;
17
+ pid: number | null;
18
+ };
19
+ export declare function startHealDaemon(): void;
20
+ /**
21
+ * SSID determinism: pin the preferred profile (autoconnect + high priority, power
22
+ * save off) and disable autoconnect on every other Wi-Fi profile so the node
23
+ * cannot silently roam onto an isolated network.
24
+ */
25
+ export declare function applyDeterminism(config: HealConfig): string[];
26
+ /** Enable the systemd hardware watchdog for true freezes (idempotent). */
27
+ export declare function enableHardwareWatchdog(): string[];
28
+ /** Install + enable the systemd service that runs the daemon as root. */
29
+ export declare function installHealService(): string[];
30
+ export declare function uninstallHealService(): string[];
31
+ export declare function healServiceStatus(): {
32
+ installed: boolean;
33
+ active: boolean;
34
+ enabled: boolean;
35
+ };
36
+ //# sourceMappingURL=heal-daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heal-daemon.d.ts","sourceRoot":"","sources":["../../src/commands/heal-daemon.ts"],"names":[],"mappings":"AAUA,OAAO,EAWL,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAsC/F;AAwBD,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAOzE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAiBtC;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAyB7D;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAejD;AAQD,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAwB7C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAW/C;AAED,wBAAgB,iBAAiB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAM7F"}
@@ -0,0 +1,122 @@
1
+ export interface HealThresholds {
2
+ /** consecutive failed checks before reconnecting Wi-Fi */
3
+ reconnect: number;
4
+ /** before restarting NetworkManager */
5
+ nmRestart: number;
6
+ /** before trying the degraded fallback SSID */
7
+ fallback: number;
8
+ /** before rebooting (last resort) */
9
+ reboot: number;
10
+ }
11
+ export interface HealConfig {
12
+ version: number;
13
+ enabled: boolean;
14
+ /** Wi-Fi interface (empty = auto-detect) */
15
+ wifiInterface: string;
16
+ /** the SSID this node must stay on */
17
+ preferredSsid: string;
18
+ /** one-shot degraded fallback SSID, restored to preferred after fallbackWindowSec */
19
+ fallbackSsid: string;
20
+ /** HTTPS URL used as the internet anchor */
21
+ internetUrl: string;
22
+ /** Tailscale hostnames used as peer anchors (empty = auto-discover online peers) */
23
+ tailscaleAnchors: string[];
24
+ /** how many of {anchors..., internet} must be reachable to count as healthy */
25
+ quorumRequired: number;
26
+ /** seconds between checks (daemon loop / timer) */
27
+ intervalSec: number;
28
+ thresholds: HealThresholds;
29
+ /** min seconds between reboots */
30
+ rebootMinIntervalSec: number;
31
+ /** min seconds between NetworkManager restarts */
32
+ nmRestartMinIntervalSec: number;
33
+ /** min seconds between Wi-Fi reconnect attempts */
34
+ reconnectMinIntervalSec: number;
35
+ /** continuous healthy seconds after boot before a watchdog reboot is allowed again */
36
+ healthyWindowSec: number;
37
+ /** after this many reboots that never reached a healthy window, stop rebooting */
38
+ maxFailedBootRecoveries: number;
39
+ /** how long to suppress reboots once a reboot loop is detected */
40
+ bootBackoffSec: number;
41
+ /** how long to stay on the fallback SSID before restoring preferred */
42
+ fallbackWindowSec: number;
43
+ /** skip reboot while a GPU compute job is running (alert instead) */
44
+ gpuJobGuard: boolean;
45
+ /** master switch for the reboot tier */
46
+ allowReboot: boolean;
47
+ }
48
+ export interface HealState {
49
+ failCount: number;
50
+ bootId: string;
51
+ bootHealthySince: number | null;
52
+ lastRebootAttempt: number;
53
+ lastNmRestart: number;
54
+ lastReconnect: number;
55
+ lastFallback: number;
56
+ degradedUntil: number;
57
+ pendingRebootRecovery: boolean;
58
+ failedBootRecoveries: number;
59
+ rebootSuppressUntil: number;
60
+ }
61
+ export interface HealthProbe {
62
+ associatedSsid: string | null;
63
+ gatewayReachable: boolean;
64
+ /** anchor hostname -> reachable via tailscale ping */
65
+ anchorsReachable: Record<string, boolean>;
66
+ internetReachable: boolean;
67
+ }
68
+ export interface HealthResult {
69
+ healthy: boolean;
70
+ remoteScore: number;
71
+ reasons: string[];
72
+ }
73
+ export type HealAction = "none" | "reconnect_wifi" | "restart_nm" | "fallback_ssid" | "restore_preferred" | "reboot";
74
+ export type SuppressedReason = "disabled" | "gpu" | "rate" | "loop";
75
+ export interface HealDecision {
76
+ action: HealAction;
77
+ /** set when a reboot was wanted but withheld */
78
+ suppressedReason?: SuppressedReason;
79
+ state: HealState;
80
+ }
81
+ export declare const DEFAULT_THRESHOLDS: HealThresholds;
82
+ export declare const DEFAULT_HEAL_CONFIG: HealConfig;
83
+ export declare function defaultHealState(): HealState;
84
+ export declare function getHealConfigPath(): string;
85
+ export declare function getHealStatePath(): string;
86
+ export declare function readHealConfig(path?: string): HealConfig;
87
+ export declare function writeHealConfig(config: HealConfig, path?: string): void;
88
+ export declare function readHealState(path?: string): HealState;
89
+ export declare function writeHealState(state: HealState, path?: string): void;
90
+ /**
91
+ * Pure health evaluation. Healthy requires the local invariants (associated to an
92
+ * acceptable SSID + gateway reachable) AND a remote quorum of reachable anchors,
93
+ * so a node that is locally fine but isolated from its peers is correctly unhealthy.
94
+ */
95
+ export declare function evaluateHealth(probe: HealthProbe, config: HealConfig, state: HealState): HealthResult;
96
+ /**
97
+ * Pure escalation state machine. Given the current persisted state, whether this
98
+ * tick is healthy, the clock, GPU activity, and config, decide the single action
99
+ * to take and return the updated state. No side effects.
100
+ */
101
+ export declare function decideAction(input: {
102
+ state: HealState;
103
+ healthy: boolean;
104
+ now: number;
105
+ gpuBusy: boolean;
106
+ config: HealConfig;
107
+ currentBootId: string;
108
+ }): HealDecision;
109
+ export declare function getCurrentBootId(): string;
110
+ export declare function detectWifiInterface(): string;
111
+ export declare function detectGateway(): string;
112
+ export declare function getAssociatedSsid(): string | null;
113
+ export declare function pingHost(host: string): boolean;
114
+ export declare function internetReachable(url: string): boolean;
115
+ export declare function tailscalePing(host: string): boolean;
116
+ export declare function gpuBusy(): boolean;
117
+ /** Auto-discover online tailscale peers (excluding self) as anchors. */
118
+ export declare function discoverAnchors(): string[];
119
+ export declare function probeHealth(config: HealConfig): HealthProbe;
120
+ /** Apply the action's side effects. Returns a human-readable description. */
121
+ export declare function executeAction(action: HealAction, config: HealConfig): string;
122
+ //# sourceMappingURL=heal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heal.d.ts","sourceRoot":"","sources":["../../src/commands/heal.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4,43 +4,25 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
7
  var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
8
  target = mod != null ? __create(__getProtoOf(mod)) : {};
21
9
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
10
  for (let key of __getOwnPropNames(mod))
23
11
  if (!__hasOwnProp.call(to, key))
24
12
  __defProp(to, key, {
25
- get: __accessProp.bind(mod, key),
13
+ get: () => mod[key],
26
14
  enumerable: true
27
15
  });
28
- if (canCache)
29
- cache.set(mod, to);
30
16
  return to;
31
17
  };
32
18
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
- var __returnValue = (v) => v;
34
- function __exportSetter(name, newValue) {
35
- this[name] = __returnValue.bind(null, newValue);
36
- }
37
19
  var __export = (target, all) => {
38
20
  for (var name in all)
39
21
  __defProp(target, name, {
40
22
  get: all[name],
41
23
  enumerable: true,
42
24
  configurable: true,
43
- set: __exportSetter.bind(all, name)
25
+ set: (newValue) => all[name] = () => newValue
44
26
  });
45
27
  };
46
28
 
@@ -6583,15 +6565,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
6583
6565
  var __defProp2 = Object.defineProperty;
6584
6566
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
6585
6567
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
6586
- function __accessProp2(key) {
6568
+ function __accessProp(key) {
6587
6569
  return this[key];
6588
6570
  }
6589
- var __toESMCache_node2;
6590
- var __toESMCache_esm2;
6571
+ var __toESMCache_node;
6572
+ var __toESMCache_esm;
6591
6573
  var __toESM2 = (mod, isNodeMode, target) => {
6592
6574
  var canCache = mod != null && typeof mod === "object";
6593
6575
  if (canCache) {
6594
- var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
6576
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
6595
6577
  var cached = cache.get(mod);
6596
6578
  if (cached)
6597
6579
  return cached;
@@ -6601,7 +6583,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
6601
6583
  for (let key of __getOwnPropNames2(mod))
6602
6584
  if (!__hasOwnProp2.call(to, key))
6603
6585
  __defProp2(to, key, {
6604
- get: __accessProp2.bind(mod, key),
6586
+ get: __accessProp.bind(mod, key),
6605
6587
  enumerable: true
6606
6588
  });
6607
6589
  if (canCache)
@@ -6609,9 +6591,9 @@ var __toESM2 = (mod, isNodeMode, target) => {
6609
6591
  return to;
6610
6592
  };
6611
6593
  var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
6612
- var __returnValue2 = (v) => v;
6613
- function __exportSetter2(name, newValue) {
6614
- this[name] = __returnValue2.bind(null, newValue);
6594
+ var __returnValue = (v) => v;
6595
+ function __exportSetter(name, newValue) {
6596
+ this[name] = __returnValue.bind(null, newValue);
6615
6597
  }
6616
6598
  var __export2 = (target, all) => {
6617
6599
  for (var name in all)
@@ -6619,7 +6601,7 @@ var __export2 = (target, all) => {
6619
6601
  get: all[name],
6620
6602
  enumerable: true,
6621
6603
  configurable: true,
6622
- set: __exportSetter2.bind(all, name)
6604
+ set: __exportSetter.bind(all, name)
6623
6605
  });
6624
6606
  };
6625
6607
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
package/dist/mcp/index.js CHANGED
@@ -1,17 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  var __defProp = Object.defineProperty;
4
- var __returnValue = (v) => v;
5
- function __exportSetter(name, newValue) {
6
- this[name] = __returnValue.bind(null, newValue);
7
- }
8
4
  var __export = (target, all) => {
9
5
  for (var name in all)
10
6
  __defProp(target, name, {
11
7
  get: all[name],
12
8
  enumerable: true,
13
9
  configurable: true,
14
- set: __exportSetter.bind(all, name)
10
+ set: (newValue) => all[name] = () => newValue
15
11
  });
16
12
  };
17
13
 
@@ -4274,9 +4270,9 @@ var __toESM = (mod, isNodeMode, target) => {
4274
4270
  return to;
4275
4271
  };
4276
4272
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
4277
- var __returnValue2 = (v) => v;
4278
- function __exportSetter2(name, newValue) {
4279
- this[name] = __returnValue2.bind(null, newValue);
4273
+ var __returnValue = (v) => v;
4274
+ function __exportSetter(name, newValue) {
4275
+ this[name] = __returnValue.bind(null, newValue);
4280
4276
  }
4281
4277
  var __export2 = (target, all) => {
4282
4278
  for (var name in all)
@@ -4284,7 +4280,7 @@ var __export2 = (target, all) => {
4284
4280
  get: all[name],
4285
4281
  enumerable: true,
4286
4282
  configurable: true,
4287
- set: __exportSetter2.bind(all, name)
4283
+ set: __exportSetter.bind(all, name)
4288
4284
  });
4289
4285
  };
4290
4286
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",