@hasna/machines 0.0.14 → 0.0.15

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/dist/index.js CHANGED
@@ -6515,8 +6515,11 @@ var require_dist = __commonJS((exports, module) => {
6515
6515
  Object.defineProperty(exports, "__esModule", { value: true });
6516
6516
  exports.default = formatsPlugin;
6517
6517
  });
6518
- // src/cross-project-types.ts
6519
- var CROSSREFS_KEY = "_crossRefs";
6518
+
6519
+ // src/db.ts
6520
+ import { Database } from "bun:sqlite";
6521
+ import { hostname } from "os";
6522
+
6520
6523
  // src/paths.ts
6521
6524
  import { existsSync, mkdirSync } from "fs";
6522
6525
  import { dirname, join, resolve } from "path";
@@ -6554,9 +6557,8 @@ function ensureDataDir() {
6554
6557
  ensureParentDir(join(dir, ".keep"));
6555
6558
  return dir;
6556
6559
  }
6560
+
6557
6561
  // src/db.ts
6558
- import { Database } from "bun:sqlite";
6559
- import { hostname } from "os";
6560
6562
  class SqliteAdapter {
6561
6563
  raw;
6562
6564
  constructor(path) {
@@ -6620,6 +6622,12 @@ function getAdapter(path = getDbPath()) {
6620
6622
  function getDb(path = getDbPath()) {
6621
6623
  return getAdapter(path).raw;
6622
6624
  }
6625
+ function closeDb() {
6626
+ if (adapter) {
6627
+ adapter.close();
6628
+ adapter = null;
6629
+ }
6630
+ }
6623
6631
  function upsertHeartbeat(machineId, pid = process.pid, status = "online") {
6624
6632
  const db = getDb();
6625
6633
  db.query(`INSERT INTO agent_heartbeats (machine_id, pid, status, updated_at)
@@ -6666,6 +6674,336 @@ function recordSyncRun(machineId, status, actions) {
6666
6674
  db.query(`INSERT INTO sync_runs (id, machine_id, status, actions_json, created_at, updated_at)
6667
6675
  VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(actions), now, now);
6668
6676
  }
6677
+
6678
+ // src/pg-migrations.ts
6679
+ var PG_MIGRATIONS = [
6680
+ `
6681
+ CREATE TABLE IF NOT EXISTS agent_heartbeats (
6682
+ machine_id TEXT NOT NULL,
6683
+ pid INTEGER NOT NULL,
6684
+ status TEXT NOT NULL,
6685
+ updated_at TIMESTAMPTZ NOT NULL,
6686
+ PRIMARY KEY (machine_id, pid)
6687
+ );
6688
+
6689
+ CREATE TABLE IF NOT EXISTS setup_runs (
6690
+ id TEXT PRIMARY KEY,
6691
+ machine_id TEXT NOT NULL,
6692
+ status TEXT NOT NULL,
6693
+ details_json TEXT NOT NULL DEFAULT '[]',
6694
+ created_at TIMESTAMPTZ NOT NULL,
6695
+ updated_at TIMESTAMPTZ NOT NULL
6696
+ );
6697
+
6698
+ CREATE TABLE IF NOT EXISTS sync_runs (
6699
+ id TEXT PRIMARY KEY,
6700
+ machine_id TEXT NOT NULL,
6701
+ status TEXT NOT NULL,
6702
+ actions_json TEXT NOT NULL DEFAULT '[]',
6703
+ created_at TIMESTAMPTZ NOT NULL,
6704
+ updated_at TIMESTAMPTZ NOT NULL
6705
+ );
6706
+ `
6707
+ ];
6708
+
6709
+ // src/remote-storage.ts
6710
+ import pg from "pg";
6711
+ function translatePlaceholders(sql) {
6712
+ let index = 0;
6713
+ return sql.replace(/\?/g, () => `$${++index}`);
6714
+ }
6715
+ function normalizeParams(params) {
6716
+ const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
6717
+ return flat.map((value) => value === undefined ? null : value);
6718
+ }
6719
+ function sslConfigFor(connectionString) {
6720
+ return connectionString.includes("sslmode=require") || connectionString.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
6721
+ }
6722
+
6723
+ class PgAdapterAsync {
6724
+ pool;
6725
+ constructor(connectionString) {
6726
+ this.pool = new pg.Pool({ connectionString, ssl: sslConfigFor(connectionString) });
6727
+ }
6728
+ async run(sql, ...params) {
6729
+ const result = await this.pool.query(translatePlaceholders(sql), normalizeParams(params));
6730
+ return { changes: result.rowCount ?? 0 };
6731
+ }
6732
+ async all(sql, ...params) {
6733
+ const result = await this.pool.query(translatePlaceholders(sql), normalizeParams(params));
6734
+ return result.rows;
6735
+ }
6736
+ async close() {
6737
+ await this.pool.end();
6738
+ }
6739
+ }
6740
+
6741
+ // src/storage-sync.ts
6742
+ var STORAGE_TABLES = [
6743
+ "agent_heartbeats",
6744
+ "setup_runs",
6745
+ "sync_runs"
6746
+ ];
6747
+ var MACHINES_STORAGE_TABLES = STORAGE_TABLES;
6748
+ var MACHINES_STORAGE_ENV = "HASNA_MACHINES_DATABASE_URL";
6749
+ var MACHINES_STORAGE_FALLBACK_ENV = "MACHINES_DATABASE_URL";
6750
+ var MACHINES_STORAGE_MODE_ENV = "HASNA_MACHINES_STORAGE_MODE";
6751
+ var MACHINES_STORAGE_MODE_FALLBACK_ENV = "MACHINES_STORAGE_MODE";
6752
+ var STORAGE_DATABASE_ENV = [MACHINES_STORAGE_ENV, MACHINES_STORAGE_FALLBACK_ENV];
6753
+ var STORAGE_MODE_ENV = [MACHINES_STORAGE_MODE_ENV, MACHINES_STORAGE_MODE_FALLBACK_ENV];
6754
+ var PRIMARY_KEYS = {
6755
+ agent_heartbeats: ["machine_id", "pid"],
6756
+ setup_runs: ["id"],
6757
+ sync_runs: ["id"]
6758
+ };
6759
+ function readEnv(name) {
6760
+ const value = process.env[name]?.trim();
6761
+ return value || undefined;
6762
+ }
6763
+ function normalizeStorageMode(value) {
6764
+ const normalized = value?.trim().toLowerCase();
6765
+ if (normalized === "local" || normalized === "hybrid" || normalized === "remote")
6766
+ return normalized;
6767
+ return;
6768
+ }
6769
+ function getStorageDatabaseEnvName() {
6770
+ for (const name of STORAGE_DATABASE_ENV) {
6771
+ if (readEnv(name))
6772
+ return name;
6773
+ }
6774
+ return null;
6775
+ }
6776
+ function getStorageDatabaseEnv() {
6777
+ const name = getStorageDatabaseEnvName();
6778
+ return name ? { name } : null;
6779
+ }
6780
+ function getStorageDatabaseUrl() {
6781
+ const env = getStorageDatabaseEnv();
6782
+ return env ? readEnv(env.name) ?? null : null;
6783
+ }
6784
+ function getStorageMode() {
6785
+ const mode = normalizeStorageMode(readEnv(MACHINES_STORAGE_MODE_ENV)) ?? normalizeStorageMode(readEnv(MACHINES_STORAGE_MODE_FALLBACK_ENV));
6786
+ if (mode)
6787
+ return mode;
6788
+ return getStorageDatabaseUrl() ? "hybrid" : "local";
6789
+ }
6790
+ async function getStoragePg() {
6791
+ const url = getStorageDatabaseUrl();
6792
+ if (!url) {
6793
+ throw new Error("Missing HASNA_MACHINES_DATABASE_URL or MACHINES_DATABASE_URL");
6794
+ }
6795
+ return new PgAdapterAsync(url);
6796
+ }
6797
+ async function runStorageMigrations(remote) {
6798
+ for (const sql of PG_MIGRATIONS)
6799
+ await remote.run(sql);
6800
+ }
6801
+ async function storagePush(options) {
6802
+ const remote = await getStoragePg();
6803
+ const db = getDb();
6804
+ try {
6805
+ await runStorageMigrations(remote);
6806
+ const results = [];
6807
+ for (const table of resolveTables(options?.tables)) {
6808
+ results.push(await pushTable(db, remote, table));
6809
+ }
6810
+ recordSyncMeta(db, "push", results);
6811
+ return results;
6812
+ } finally {
6813
+ await remote.close();
6814
+ }
6815
+ }
6816
+ async function storagePull(options) {
6817
+ const remote = await getStoragePg();
6818
+ const db = getDb();
6819
+ try {
6820
+ await runStorageMigrations(remote);
6821
+ const results = [];
6822
+ for (const table of resolveTables(options?.tables)) {
6823
+ results.push(await pullTable(remote, db, table));
6824
+ }
6825
+ recordSyncMeta(db, "pull", results);
6826
+ return results;
6827
+ } finally {
6828
+ await remote.close();
6829
+ }
6830
+ }
6831
+ async function storageSync(options) {
6832
+ const pull = await storagePull(options);
6833
+ const push = await storagePush(options);
6834
+ return { pull, push };
6835
+ }
6836
+ function getSyncMetaAll() {
6837
+ const db = getDb();
6838
+ ensureSyncMetaTable(db);
6839
+ return db.query("SELECT table_name, last_synced_at, direction FROM _machines_sync_meta ORDER BY table_name, direction").all();
6840
+ }
6841
+ function getStorageStatus() {
6842
+ const activeEnv = getStorageDatabaseEnv();
6843
+ return {
6844
+ configured: Boolean(activeEnv),
6845
+ mode: getStorageMode(),
6846
+ env: STORAGE_DATABASE_ENV,
6847
+ activeEnv: activeEnv?.name ?? null,
6848
+ service: "machines",
6849
+ tables: STORAGE_TABLES,
6850
+ sync: getSyncMetaAll()
6851
+ };
6852
+ }
6853
+ function resolveTables(tables) {
6854
+ if (!tables || tables.length === 0)
6855
+ return [...STORAGE_TABLES];
6856
+ const allowed = new Set(STORAGE_TABLES);
6857
+ const requested = tables.map((table) => table.trim()).filter(Boolean);
6858
+ const invalid = requested.filter((table) => !allowed.has(table));
6859
+ if (invalid.length > 0)
6860
+ throw new Error(`Unknown machines storage table(s): ${invalid.join(", ")}`);
6861
+ return requested;
6862
+ }
6863
+ function parseStorageTables(value) {
6864
+ if (!value)
6865
+ return;
6866
+ return resolveTables(Array.isArray(value) ? value : value.split(","));
6867
+ }
6868
+ async function pushTable(db, remote, table) {
6869
+ const result = { table, rowsRead: 0, rowsWritten: 0, errors: [] };
6870
+ try {
6871
+ if (!tableExists(db, table))
6872
+ return result;
6873
+ const rows = db.query(`SELECT * FROM ${quoteIdent(table)}`).all();
6874
+ result.rowsRead = rows.length;
6875
+ if (rows.length === 0)
6876
+ return result;
6877
+ const remoteColumns = await getRemoteColumns(remote, table);
6878
+ const columns = filterRemoteColumns(remoteColumns, Object.keys(rows[0]));
6879
+ result.rowsWritten = await upsertPg(remote, table, columns, rows);
6880
+ } catch (error) {
6881
+ result.errors.push(error instanceof Error ? error.message : String(error));
6882
+ }
6883
+ return result;
6884
+ }
6885
+ async function pullTable(remote, db, table) {
6886
+ const result = { table, rowsRead: 0, rowsWritten: 0, errors: [] };
6887
+ try {
6888
+ if (!tableExists(db, table))
6889
+ return result;
6890
+ const rows = await remote.all(`SELECT * FROM ${quoteIdent(table)}`);
6891
+ result.rowsRead = rows.length;
6892
+ if (rows.length === 0)
6893
+ return result;
6894
+ const columns = filterLocalColumns(db, table, Object.keys(rows[0]));
6895
+ result.rowsWritten = upsertSqlite(db, table, columns, rows);
6896
+ } catch (error) {
6897
+ result.errors.push(error instanceof Error ? error.message : String(error));
6898
+ }
6899
+ return result;
6900
+ }
6901
+ async function getRemoteColumns(remote, table) {
6902
+ const rows = await remote.all("SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ?", table);
6903
+ return new Set(rows.map((row) => row.column_name));
6904
+ }
6905
+ function filterRemoteColumns(remoteColumns, columns) {
6906
+ if (remoteColumns.size === 0)
6907
+ return columns;
6908
+ return columns.filter((column) => remoteColumns.has(column));
6909
+ }
6910
+ function filterLocalColumns(db, table, columns) {
6911
+ const rows = db.query(`PRAGMA table_info(${quoteIdent(table)})`).all();
6912
+ const allowed = new Set(rows.map((row) => row.name));
6913
+ return columns.filter((column) => allowed.has(column));
6914
+ }
6915
+ async function upsertPg(remote, table, columns, rows) {
6916
+ if (columns.length === 0)
6917
+ return 0;
6918
+ const primaryKeys = PRIMARY_KEYS[table];
6919
+ const columnList = columns.map(quoteIdent).join(", ");
6920
+ const placeholders = columns.map(() => "?").join(", ");
6921
+ const keyList = primaryKeys.map(quoteIdent).join(", ");
6922
+ const updateColumns = columns.filter((column) => !primaryKeys.includes(column));
6923
+ const fallbackKey = primaryKeys[0];
6924
+ const setClause = updateColumns.length > 0 ? updateColumns.map((column) => `${quoteIdent(column)} = EXCLUDED.${quoteIdent(column)}`).join(", ") : `${quoteIdent(fallbackKey)} = EXCLUDED.${quoteIdent(fallbackKey)}`;
6925
+ for (const row of rows) {
6926
+ await remote.run(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES (${placeholders})
6927
+ ON CONFLICT (${keyList}) DO UPDATE SET ${setClause}`, ...columns.map((column) => coerceForPg(row[column])));
6928
+ }
6929
+ return rows.length;
6930
+ }
6931
+ function upsertSqlite(db, table, columns, rows) {
6932
+ if (columns.length === 0)
6933
+ return 0;
6934
+ const primaryKeys = PRIMARY_KEYS[table];
6935
+ const columnList = columns.map(quoteIdent).join(", ");
6936
+ const placeholders = columns.map(() => "?").join(", ");
6937
+ const keyList = primaryKeys.map(quoteIdent).join(", ");
6938
+ const updateColumns = columns.filter((column) => !primaryKeys.includes(column));
6939
+ const fallbackKey = primaryKeys[0];
6940
+ const setClause = updateColumns.length > 0 ? updateColumns.map((column) => `${quoteIdent(column)} = excluded.${quoteIdent(column)}`).join(", ") : `${quoteIdent(fallbackKey)} = excluded.${quoteIdent(fallbackKey)}`;
6941
+ const statement = db.query(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES (${placeholders})
6942
+ ON CONFLICT (${keyList}) DO UPDATE SET ${setClause}`);
6943
+ const insert = db.transaction((batch) => {
6944
+ for (const row of batch)
6945
+ statement.run(...columns.map((column) => coerceForSqlite(row[column])));
6946
+ });
6947
+ insert(rows);
6948
+ return rows.length;
6949
+ }
6950
+ function recordSyncMeta(db, direction, results) {
6951
+ ensureSyncMetaTable(db);
6952
+ const now = new Date().toISOString();
6953
+ const statement = db.query(`
6954
+ INSERT INTO _machines_sync_meta (table_name, last_synced_at, direction)
6955
+ VALUES (?, ?, ?)
6956
+ ON CONFLICT(table_name, direction) DO UPDATE SET last_synced_at = excluded.last_synced_at
6957
+ `);
6958
+ for (const result of results) {
6959
+ if (result.errors.length > 0)
6960
+ continue;
6961
+ statement.run(result.table, now, direction);
6962
+ }
6963
+ }
6964
+ function ensureSyncMetaTable(db) {
6965
+ db.exec(`
6966
+ CREATE TABLE IF NOT EXISTS _machines_sync_meta (
6967
+ table_name TEXT NOT NULL,
6968
+ last_synced_at TEXT,
6969
+ direction TEXT NOT NULL CHECK(direction IN ('push', 'pull')),
6970
+ PRIMARY KEY (table_name, direction)
6971
+ )
6972
+ `);
6973
+ }
6974
+ function tableExists(db, table) {
6975
+ const row = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
6976
+ return Boolean(row);
6977
+ }
6978
+ function quoteIdent(identifier) {
6979
+ return `"${identifier.replace(/"/g, '""')}"`;
6980
+ }
6981
+ function coerceForPg(value) {
6982
+ if (value === undefined || value === null)
6983
+ return null;
6984
+ if (value instanceof Date)
6985
+ return value.toISOString();
6986
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array)
6987
+ return value;
6988
+ if (typeof value === "object")
6989
+ return JSON.stringify(value);
6990
+ return value;
6991
+ }
6992
+ function coerceForSqlite(value) {
6993
+ if (value === undefined || value === null)
6994
+ return null;
6995
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean")
6996
+ return value;
6997
+ if (value instanceof Date)
6998
+ return value.toISOString();
6999
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array)
7000
+ return value;
7001
+ if (typeof value === "object")
7002
+ return JSON.stringify(value);
7003
+ return String(value);
7004
+ }
7005
+ // src/cross-project-types.ts
7006
+ var CROSSREFS_KEY = "_crossRefs";
6669
7007
  // src/manifests.ts
6670
7008
  import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
6671
7009
  import { arch, homedir, hostname as hostname2, platform, userInfo } from "os";
@@ -10746,6 +11084,518 @@ function detectCurrentMachineManifest() {
10746
11084
  files: []
10747
11085
  };
10748
11086
  }
11087
+ // src/topology.ts
11088
+ import { existsSync as existsSync3 } from "fs";
11089
+ import { arch as arch2, hostname as hostname3, platform as platform2, userInfo as userInfo2 } from "os";
11090
+ import { spawnSync } from "child_process";
11091
+ function normalizePlatform2(value = platform2()) {
11092
+ const normalized = value.toLowerCase();
11093
+ if (normalized === "darwin" || normalized === "macos")
11094
+ return "macos";
11095
+ if (normalized === "win32" || normalized === "windows")
11096
+ return "windows";
11097
+ if (normalized === "linux")
11098
+ return "linux";
11099
+ return value;
11100
+ }
11101
+ function defaultRunner(command) {
11102
+ const result = spawnSync("bash", ["-c", command], {
11103
+ encoding: "utf8",
11104
+ env: process.env
11105
+ });
11106
+ return {
11107
+ stdout: result.stdout || "",
11108
+ stderr: result.stderr || "",
11109
+ exitCode: result.status ?? 1
11110
+ };
11111
+ }
11112
+ function hasCommand(command, runner) {
11113
+ return runner(`command -v ${command} >/dev/null 2>&1`).exitCode === 0;
11114
+ }
11115
+ function parseTailscaleStatus(raw) {
11116
+ try {
11117
+ const parsed = JSON.parse(raw);
11118
+ if (!parsed || typeof parsed !== "object")
11119
+ return null;
11120
+ return parsed;
11121
+ } catch {
11122
+ return null;
11123
+ }
11124
+ }
11125
+ function loadTailscalePeers(runner, warnings) {
11126
+ const peers = new Map;
11127
+ if (!hasCommand("tailscale", runner)) {
11128
+ warnings.push("tailscale_not_available");
11129
+ return peers;
11130
+ }
11131
+ const result = runner("tailscale status --json");
11132
+ if (result.exitCode !== 0) {
11133
+ warnings.push(`tailscale_status_failed:${result.stderr.trim() || result.exitCode}`);
11134
+ return peers;
11135
+ }
11136
+ const status = parseTailscaleStatus(result.stdout);
11137
+ if (!status) {
11138
+ warnings.push("tailscale_status_invalid_json");
11139
+ return peers;
11140
+ }
11141
+ const addPeer = (peer) => {
11142
+ if (!peer)
11143
+ return;
11144
+ const id = peer.HostName || peer.DNSName?.split(".")[0];
11145
+ if (id)
11146
+ peers.set(id, peer);
11147
+ };
11148
+ addPeer(status.Self);
11149
+ for (const peer of Object.values(status.Peer ?? {}))
11150
+ addPeer(peer);
11151
+ return peers;
11152
+ }
11153
+ function machineKeys(machine) {
11154
+ return [
11155
+ machine.id,
11156
+ machine.hostname,
11157
+ machine.tailscaleName?.split(".")[0],
11158
+ machine.tailscaleName,
11159
+ machine.sshAddress?.split("@").pop()
11160
+ ].filter((value) => Boolean(value));
11161
+ }
11162
+ function findTailscalePeer(machine, machineId, peers) {
11163
+ if (machine) {
11164
+ for (const key of machineKeys(machine)) {
11165
+ const peer = peers.get(key) ?? peers.get(key.replace(/\.$/, ""));
11166
+ if (peer)
11167
+ return peer;
11168
+ }
11169
+ }
11170
+ return peers.get(machineId) ?? null;
11171
+ }
11172
+ function routeHints(input) {
11173
+ const hints = [];
11174
+ if (input.machineId === input.localMachineId) {
11175
+ hints.push({ kind: "local", target: "localhost", reachable: true });
11176
+ }
11177
+ if (input.manifest?.sshAddress) {
11178
+ hints.push({ kind: "ssh", target: input.manifest.sshAddress, reachable: null });
11179
+ }
11180
+ if (input.manifest?.hostname) {
11181
+ hints.push({ kind: "lan", target: input.manifest.hostname, reachable: null });
11182
+ }
11183
+ const tailscaleTarget = input.manifest?.tailscaleName ?? input.peer?.DNSName ?? input.peer?.TailscaleIPs?.[0];
11184
+ if (tailscaleTarget) {
11185
+ hints.push({ kind: "tailscale", target: tailscaleTarget.replace(/\.$/, ""), reachable: input.peer?.Online ?? null });
11186
+ }
11187
+ return hints;
11188
+ }
11189
+ function buildEntry(input) {
11190
+ const manifest = input.manifest;
11191
+ const peer = input.peer;
11192
+ const hints = routeHints({
11193
+ machineId: input.machineId,
11194
+ localMachineId: input.localMachineId,
11195
+ manifest,
11196
+ peer
11197
+ });
11198
+ const selectedRoute = hints.find((hint) => hint.kind === "local") ?? hints.find((hint) => hint.kind === "ssh") ?? hints.find((hint) => hint.kind === "lan") ?? hints.find((hint) => hint.kind === "tailscale");
11199
+ const route = selectedRoute?.kind === "ssh" ? "lan" : selectedRoute?.kind ?? "unknown";
11200
+ return {
11201
+ machine_id: input.machineId,
11202
+ hostname: manifest?.hostname ?? peer?.HostName ?? null,
11203
+ platform: manifest?.platform ?? (peer?.OS ? normalizePlatform2(peer.OS) : null),
11204
+ os: peer?.OS ?? null,
11205
+ user: typeof manifest?.metadata?.user === "string" ? manifest.metadata.user : null,
11206
+ workspace_path: manifest?.workspacePath ?? null,
11207
+ manifest_declared: Boolean(manifest),
11208
+ heartbeat_status: input.heartbeat?.status ?? "unknown",
11209
+ last_heartbeat_at: input.heartbeat?.updated_at ?? null,
11210
+ tailscale: {
11211
+ dns_name: manifest?.tailscaleName ?? peer?.DNSName?.replace(/\.$/, "") ?? null,
11212
+ ips: peer?.TailscaleIPs ?? [],
11213
+ online: peer?.Online ?? null,
11214
+ active: peer?.Active ?? null,
11215
+ last_seen: peer?.LastSeen ?? null
11216
+ },
11217
+ ssh: {
11218
+ address: manifest?.sshAddress ?? null,
11219
+ route,
11220
+ command_target: selectedRoute?.target ?? null
11221
+ },
11222
+ route_hints: hints,
11223
+ tags: manifest?.tags ?? [],
11224
+ metadata: manifest?.metadata ?? {}
11225
+ };
11226
+ }
11227
+ function discoverMachineTopology(options = {}) {
11228
+ const now = options.now ?? new Date;
11229
+ const runner = options.runner ?? defaultRunner;
11230
+ const warnings = [];
11231
+ const manifest = readManifest();
11232
+ const heartbeats = listHeartbeats();
11233
+ const heartbeatByMachine = new Map(heartbeats.map((heartbeat) => [heartbeat.machine_id, heartbeat]));
11234
+ const localMachineId = getLocalMachineId();
11235
+ const peers = options.includeTailscale === false ? new Map : loadTailscalePeers(runner, warnings);
11236
+ const machineIds = new Set([
11237
+ localMachineId,
11238
+ ...manifest.machines.map((machine) => machine.id),
11239
+ ...heartbeats.map((heartbeat) => heartbeat.machine_id),
11240
+ ...peers.keys()
11241
+ ]);
11242
+ const manifestById = new Map(manifest.machines.map((machine) => [machine.id, machine]));
11243
+ const machines = [...machineIds].sort().map((machineId) => {
11244
+ const manifestMachine = manifestById.get(machineId);
11245
+ return buildEntry({
11246
+ machineId,
11247
+ localMachineId,
11248
+ manifest: manifestMachine,
11249
+ peer: findTailscalePeer(manifestMachine ?? null, machineId, peers),
11250
+ heartbeat: heartbeatByMachine.get(machineId)
11251
+ });
11252
+ });
11253
+ return {
11254
+ generated_at: now.toISOString(),
11255
+ local_machine_id: localMachineId,
11256
+ local_hostname: hostname3(),
11257
+ current_platform: normalizePlatform2(),
11258
+ manifest_path_known: existsSync3(getManifestPath()),
11259
+ machines,
11260
+ warnings
11261
+ };
11262
+ }
11263
+ function getLocalMachineTopology(options = {}) {
11264
+ const topology = discoverMachineTopology(options);
11265
+ return topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? {
11266
+ machine_id: topology.local_machine_id,
11267
+ hostname: hostname3(),
11268
+ platform: normalizePlatform2(),
11269
+ os: platform2(),
11270
+ user: userInfo2().username,
11271
+ workspace_path: null,
11272
+ manifest_declared: false,
11273
+ heartbeat_status: "unknown",
11274
+ last_heartbeat_at: null,
11275
+ tailscale: { dns_name: null, ips: [], online: null, active: null, last_seen: null },
11276
+ ssh: { address: null, route: "local", command_target: "localhost" },
11277
+ route_hints: [{ kind: "local", target: "localhost", reachable: true }],
11278
+ tags: [`arch:${arch2()}`],
11279
+ metadata: {}
11280
+ };
11281
+ }
11282
+ // src/remote.ts
11283
+ import { spawnSync as spawnSync3 } from "child_process";
11284
+
11285
+ // src/commands/ssh.ts
11286
+ import { spawnSync as spawnSync2 } from "child_process";
11287
+ function envReachableHosts() {
11288
+ const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
11289
+ return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
11290
+ }
11291
+ function isReachable(host) {
11292
+ const overrides = envReachableHosts();
11293
+ if (overrides.size > 0) {
11294
+ return overrides.has(host);
11295
+ }
11296
+ const probe = spawnSync2("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
11297
+ stdio: "ignore"
11298
+ });
11299
+ return probe.status === 0;
11300
+ }
11301
+ function resolveSshTarget(machineId) {
11302
+ const machine = getManifestMachine(machineId);
11303
+ if (!machine) {
11304
+ throw new Error(`Machine not found in manifest: ${machineId}`);
11305
+ }
11306
+ const current = detectCurrentMachineManifest();
11307
+ if (machine.id === current.id) {
11308
+ return {
11309
+ machineId,
11310
+ target: "localhost",
11311
+ route: "local"
11312
+ };
11313
+ }
11314
+ const lanTarget = machine.sshAddress || machine.hostname || machine.id;
11315
+ const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
11316
+ const route = isReachable(lanTarget) ? "lan" : "tailscale";
11317
+ return {
11318
+ machineId,
11319
+ target: route === "lan" ? lanTarget : tailscaleTarget,
11320
+ route
11321
+ };
11322
+ }
11323
+ function buildSshCommand(machineId, remoteCommand) {
11324
+ const resolved = resolveSshTarget(machineId);
11325
+ return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
11326
+ }
11327
+
11328
+ // src/remote.ts
11329
+ function runMachineCommand(machineId, command) {
11330
+ const localMachineId = getLocalMachineId();
11331
+ const isLocal = machineId === localMachineId;
11332
+ const route = isLocal ? "local" : resolveSshTarget(machineId).route;
11333
+ const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
11334
+ const result = spawnSync3("bash", ["-c", shellCommand], {
11335
+ encoding: "utf8",
11336
+ env: process.env
11337
+ });
11338
+ return {
11339
+ machineId,
11340
+ source: route,
11341
+ stdout: result.stdout || "",
11342
+ stderr: result.stderr || "",
11343
+ exitCode: result.status ?? 1
11344
+ };
11345
+ }
11346
+
11347
+ // src/version.ts
11348
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
11349
+ import { dirname as dirname3, join as join2 } from "path";
11350
+ import { fileURLToPath } from "url";
11351
+ function getPackageVersion() {
11352
+ try {
11353
+ const here = dirname3(fileURLToPath(import.meta.url));
11354
+ const candidates = [join2(here, "..", "package.json"), join2(here, "..", "..", "package.json")];
11355
+ const pkgPath = candidates.find((candidate) => existsSync4(candidate));
11356
+ if (!pkgPath) {
11357
+ return "0.0.0";
11358
+ }
11359
+ return JSON.parse(readFileSync2(pkgPath, "utf8")).version || "0.0.0";
11360
+ } catch {
11361
+ return "0.0.0";
11362
+ }
11363
+ }
11364
+
11365
+ // src/compatibility.ts
11366
+ var DEFAULT_COMMANDS = [
11367
+ { command: "bun", required: true },
11368
+ { command: "machines", required: true }
11369
+ ];
11370
+ function defaultPackages() {
11371
+ return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
11372
+ }
11373
+ function shellQuote(value) {
11374
+ return `'${value.replace(/'/g, "'\\''")}'`;
11375
+ }
11376
+ function commandId(value) {
11377
+ return value.replace(/[^a-zA-Z0-9_.@/-]+/g, "-").replace(/^-+|-+$/g, "");
11378
+ }
11379
+ function packageCommand(name) {
11380
+ if (name === "@hasna/knowledge")
11381
+ return "knowledge";
11382
+ if (name === "@hasna/machines")
11383
+ return "machines";
11384
+ return name.split("/").pop() ?? name;
11385
+ }
11386
+ function firstLine(value) {
11387
+ return value.trim().split(/\r?\n/).find(Boolean) ?? "";
11388
+ }
11389
+ function extractVersion(value) {
11390
+ const match = value.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
11391
+ return match?.[0] ?? null;
11392
+ }
11393
+ function statusFor(required, ok) {
11394
+ if (ok)
11395
+ return "ok";
11396
+ return required === false ? "warn" : "fail";
11397
+ }
11398
+ function makeCheck(input) {
11399
+ return {
11400
+ id: input.id,
11401
+ kind: input.kind,
11402
+ status: input.status,
11403
+ target: input.target,
11404
+ expected: input.expected ?? null,
11405
+ actual: input.actual ?? null,
11406
+ detail: input.detail,
11407
+ source: input.source
11408
+ };
11409
+ }
11410
+ function parseKeyValue(stdout) {
11411
+ const result = {};
11412
+ for (const line of stdout.split(/\r?\n/)) {
11413
+ const idx = line.indexOf("=");
11414
+ if (idx <= 0)
11415
+ continue;
11416
+ result[line.slice(0, idx)] = line.slice(idx + 1);
11417
+ }
11418
+ return result;
11419
+ }
11420
+ function defaultRunner2(machineId, command) {
11421
+ return runMachineCommand(machineId, command);
11422
+ }
11423
+ function inspectCommand(machineId, spec, runner) {
11424
+ const command = shellQuote(spec.command);
11425
+ const versionArgs = spec.versionArgs ?? "--version";
11426
+ const script = [
11427
+ `cmd=${command}`,
11428
+ 'path="$(command -v "$cmd" 2>/dev/null || true)"',
11429
+ 'printf "path=%s\\n" "$path"',
11430
+ 'if [ -n "$path" ]; then version="$("$cmd" ' + versionArgs + ' 2>/dev/null || true)"; printf "version=%s\\n" "$version"; fi'
11431
+ ].join("; ");
11432
+ const result = runner(machineId, script);
11433
+ const parsed = parseKeyValue(result.stdout);
11434
+ return {
11435
+ path: parsed.path || null,
11436
+ version: parsed.version ? firstLine(parsed.version) : null,
11437
+ exitCode: result.exitCode,
11438
+ source: result.source,
11439
+ stderr: result.stderr
11440
+ };
11441
+ }
11442
+ function fieldCommand(field) {
11443
+ const regex = field === "name" ? String.raw`s/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p` : String.raw`s/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p`;
11444
+ return [
11445
+ `if command -v bun >/dev/null 2>&1; then bun -e "const p=JSON.parse(await Bun.file(process.argv[1]).text()); console.log(p.${field} ?? '')" "$pkg" 2>/dev/null`,
11446
+ `elif command -v node >/dev/null 2>&1; then node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); console.log(p.${field} || '')" "$pkg" 2>/dev/null`,
11447
+ `else sed -n '${regex}' "$pkg" | head -n 1`,
11448
+ "fi"
11449
+ ].join("; ");
11450
+ }
11451
+ function inspectWorkspace(machineId, spec, runner) {
11452
+ const script = [
11453
+ `path=${shellQuote(spec.path)}`,
11454
+ 'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
11455
+ 'pkg="$path/package.json"',
11456
+ 'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
11457
+ `if [ -f "$pkg" ]; then printf "package_name=%s\\n" "$(${fieldCommand("name")})"; printf "version=%s\\n" "$(${fieldCommand("version")})"; fi`
11458
+ ].join("; ");
11459
+ const result = runner(machineId, script);
11460
+ const parsed = parseKeyValue(result.stdout);
11461
+ return {
11462
+ exists: parsed.exists === "yes",
11463
+ packageJson: parsed.package_json === "yes",
11464
+ packageName: parsed.package_name || null,
11465
+ version: parsed.version || null,
11466
+ source: result.source,
11467
+ stderr: result.stderr
11468
+ };
11469
+ }
11470
+ function commandCheck(machineId, spec, runner) {
11471
+ const inspection = inspectCommand(machineId, spec, runner);
11472
+ const found = Boolean(inspection.path);
11473
+ const checks = [
11474
+ makeCheck({
11475
+ id: `command:${commandId(spec.command)}:path`,
11476
+ kind: "command",
11477
+ status: statusFor(spec.required, found),
11478
+ target: spec.command,
11479
+ expected: "available",
11480
+ actual: inspection.path ?? "missing",
11481
+ detail: found ? `found at ${inspection.path}` : inspection.stderr || "command missing",
11482
+ source: inspection.source
11483
+ })
11484
+ ];
11485
+ if (spec.expectedVersion) {
11486
+ const actualVersion = extractVersion(inspection.version ?? "");
11487
+ checks.push(makeCheck({
11488
+ id: `command:${commandId(spec.command)}:version`,
11489
+ kind: "command",
11490
+ status: actualVersion === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11491
+ target: spec.command,
11492
+ expected: spec.expectedVersion,
11493
+ actual: actualVersion ?? inspection.version ?? "missing",
11494
+ detail: actualVersion ? `version output: ${inspection.version}` : "version unavailable",
11495
+ source: inspection.source
11496
+ }));
11497
+ }
11498
+ return checks;
11499
+ }
11500
+ function packageCheck(machineId, spec, runner) {
11501
+ const command = spec.command ?? packageCommand(spec.name);
11502
+ const inspection = inspectCommand(machineId, { command, expectedVersion: spec.expectedVersion, required: spec.required }, runner);
11503
+ const found = Boolean(inspection.path);
11504
+ const checks = [
11505
+ makeCheck({
11506
+ id: `package:${commandId(spec.name)}:command`,
11507
+ kind: "package",
11508
+ status: statusFor(spec.required, found),
11509
+ target: spec.name,
11510
+ expected: command,
11511
+ actual: inspection.path ?? "missing",
11512
+ detail: found ? `${command} found at ${inspection.path}` : `${command} command missing`,
11513
+ source: inspection.source
11514
+ })
11515
+ ];
11516
+ if (spec.expectedVersion) {
11517
+ const actualVersion = extractVersion(inspection.version ?? "");
11518
+ checks.push(makeCheck({
11519
+ id: `package:${commandId(spec.name)}:version`,
11520
+ kind: "package",
11521
+ status: actualVersion === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11522
+ target: spec.name,
11523
+ expected: spec.expectedVersion,
11524
+ actual: actualVersion ?? inspection.version ?? "missing",
11525
+ detail: actualVersion ? `version output: ${inspection.version}` : "version unavailable",
11526
+ source: inspection.source
11527
+ }));
11528
+ }
11529
+ return checks;
11530
+ }
11531
+ function workspaceCheck(machineId, spec, runner) {
11532
+ const inspection = inspectWorkspace(machineId, spec, runner);
11533
+ const target = spec.label ?? spec.path;
11534
+ const checks = [
11535
+ makeCheck({
11536
+ id: `workspace:${commandId(target)}:path`,
11537
+ kind: "workspace",
11538
+ status: statusFor(spec.required, inspection.exists),
11539
+ target,
11540
+ expected: spec.path,
11541
+ actual: inspection.exists ? "exists" : "missing",
11542
+ detail: inspection.exists ? `workspace exists at ${spec.path}` : inspection.stderr || `workspace missing at ${spec.path}`,
11543
+ source: inspection.source
11544
+ })
11545
+ ];
11546
+ if (spec.expectedPackageName) {
11547
+ checks.push(makeCheck({
11548
+ id: `workspace:${commandId(target)}:package-name`,
11549
+ kind: "workspace",
11550
+ status: inspection.packageName === spec.expectedPackageName ? "ok" : statusFor(spec.required, false),
11551
+ target,
11552
+ expected: spec.expectedPackageName,
11553
+ actual: inspection.packageName ?? (inspection.packageJson ? "missing-name" : "missing-package-json"),
11554
+ detail: inspection.packageJson ? "package.json inspected" : "package.json missing",
11555
+ source: inspection.source
11556
+ }));
11557
+ }
11558
+ if (spec.expectedVersion) {
11559
+ checks.push(makeCheck({
11560
+ id: `workspace:${commandId(target)}:version`,
11561
+ kind: "workspace",
11562
+ status: inspection.version === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11563
+ target,
11564
+ expected: spec.expectedVersion,
11565
+ actual: inspection.version ?? (inspection.packageJson ? "missing-version" : "missing-package-json"),
11566
+ detail: inspection.packageJson ? "package.json inspected" : "package.json missing",
11567
+ source: inspection.source
11568
+ }));
11569
+ }
11570
+ return checks;
11571
+ }
11572
+ function checkMachineCompatibility(options = {}) {
11573
+ const machineId = options.machineId ?? getLocalMachineId();
11574
+ const runner = options.runner ?? defaultRunner2;
11575
+ const commands = options.commands ?? DEFAULT_COMMANDS;
11576
+ const packages = options.packages ?? defaultPackages();
11577
+ const workspaces = options.workspaces ?? [];
11578
+ const checks = [];
11579
+ for (const spec of commands)
11580
+ checks.push(...commandCheck(machineId, spec, runner));
11581
+ for (const spec of packages)
11582
+ checks.push(...packageCheck(machineId, spec, runner));
11583
+ for (const spec of workspaces)
11584
+ checks.push(...workspaceCheck(machineId, spec, runner));
11585
+ const summary = {
11586
+ ok: checks.filter((check) => check.status === "ok").length,
11587
+ warn: checks.filter((check) => check.status === "warn").length,
11588
+ fail: checks.filter((check) => check.status === "fail").length
11589
+ };
11590
+ return {
11591
+ ok: summary.fail === 0,
11592
+ machine_id: machineId,
11593
+ source: checks[0]?.source ?? "local",
11594
+ generated_at: (options.now ?? new Date).toISOString(),
11595
+ checks,
11596
+ summary
11597
+ };
11598
+ }
10749
11599
  // src/agent/runtime.ts
10750
11600
  function writeHeartbeat(status = "online") {
10751
11601
  const machineId = getLocalMachineId();
@@ -10770,20 +11620,20 @@ function getAgentStatus(machineId = getLocalMachineId()) {
10770
11620
  }
10771
11621
  // src/commands/backup.ts
10772
11622
  import { homedir as homedir2 } from "os";
10773
- import { join as join2 } from "path";
11623
+ import { join as join3 } from "path";
10774
11624
  function quote(value) {
10775
11625
  return `'${value.replace(/'/g, `'\\''`)}'`;
10776
11626
  }
10777
11627
  function defaultBackupSources() {
10778
11628
  const home = homedir2();
10779
11629
  return [
10780
- join2(home, ".hasna"),
10781
- join2(home, ".ssh"),
10782
- join2(home, ".secrets")
11630
+ join3(home, ".hasna"),
11631
+ join3(home, ".ssh"),
11632
+ join3(home, ".secrets")
10783
11633
  ];
10784
11634
  }
10785
11635
  function buildBackupPlan(bucket, prefix = "machines") {
10786
- const archivePath = join2(homedir2(), ".hasna", "machines", "backup.tgz");
11636
+ const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
10787
11637
  const sources = defaultBackupSources();
10788
11638
  const steps = [
10789
11639
  {
@@ -10832,71 +11682,6 @@ function runBackup(bucket, prefix = "machines", options = {}) {
10832
11682
  executed
10833
11683
  };
10834
11684
  }
10835
- // src/remote.ts
10836
- import { spawnSync as spawnSync2 } from "child_process";
10837
-
10838
- // src/commands/ssh.ts
10839
- import { spawnSync } from "child_process";
10840
- function envReachableHosts() {
10841
- const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
10842
- return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
10843
- }
10844
- function isReachable(host) {
10845
- const overrides = envReachableHosts();
10846
- if (overrides.size > 0) {
10847
- return overrides.has(host);
10848
- }
10849
- const probe = spawnSync("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
10850
- stdio: "ignore"
10851
- });
10852
- return probe.status === 0;
10853
- }
10854
- function resolveSshTarget(machineId) {
10855
- const machine = getManifestMachine(machineId);
10856
- if (!machine) {
10857
- throw new Error(`Machine not found in manifest: ${machineId}`);
10858
- }
10859
- const current = detectCurrentMachineManifest();
10860
- if (machine.id === current.id) {
10861
- return {
10862
- machineId,
10863
- target: "localhost",
10864
- route: "local"
10865
- };
10866
- }
10867
- const lanTarget = machine.sshAddress || machine.hostname || machine.id;
10868
- const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
10869
- const route = isReachable(lanTarget) ? "lan" : "tailscale";
10870
- return {
10871
- machineId,
10872
- target: route === "lan" ? lanTarget : tailscaleTarget,
10873
- route
10874
- };
10875
- }
10876
- function buildSshCommand(machineId, remoteCommand) {
10877
- const resolved = resolveSshTarget(machineId);
10878
- return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
10879
- }
10880
-
10881
- // src/remote.ts
10882
- function runMachineCommand(machineId, command) {
10883
- const localMachineId = getLocalMachineId();
10884
- const isLocal = machineId === localMachineId;
10885
- const route = isLocal ? "local" : resolveSshTarget(machineId).route;
10886
- const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
10887
- const result = spawnSync2("bash", ["-lc", shellCommand], {
10888
- encoding: "utf8",
10889
- env: process.env
10890
- });
10891
- return {
10892
- machineId,
10893
- source: route,
10894
- stdout: result.stdout || "",
10895
- stderr: result.stderr || "",
10896
- exitCode: result.status ?? 1
10897
- };
10898
- }
10899
-
10900
11685
  // src/commands/apps.ts
10901
11686
  function getPackageName(app) {
10902
11687
  return app.packageName || app.name;
@@ -10910,7 +11695,7 @@ function getAppManager(machine, app) {
10910
11695
  return "winget";
10911
11696
  return "apt";
10912
11697
  }
10913
- function shellQuote(value) {
11698
+ function shellQuote2(value) {
10914
11699
  return `'${value.replace(/'/g, `'\\''`)}'`;
10915
11700
  }
10916
11701
  function buildAppCommand(machine, app) {
@@ -10931,7 +11716,7 @@ function buildAppCommand(machine, app) {
10931
11716
  return `sudo apt-get install -y ${packageName}`;
10932
11717
  }
10933
11718
  function buildAppProbeCommand(machine, app) {
10934
- const packageName = shellQuote(getPackageName(app));
11719
+ const packageName = shellQuote2(getPackageName(app));
10935
11720
  const manager = getAppManager(machine, app);
10936
11721
  if (manager === "custom") {
10937
11722
  return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
@@ -11035,23 +11820,23 @@ function runAppsInstall(machineId, options = {}) {
11035
11820
  };
11036
11821
  }
11037
11822
  // src/commands/cert.ts
11038
- import { homedir as homedir3, platform as platform2 } from "os";
11039
- import { join as join3 } from "path";
11823
+ import { homedir as homedir3, platform as platform3 } from "os";
11824
+ import { join as join4 } from "path";
11040
11825
  function quote2(value) {
11041
11826
  return `'${value.replace(/'/g, `'\\''`)}'`;
11042
11827
  }
11043
11828
  function certDir() {
11044
- return join3(homedir3(), ".hasna", "machines", "certs");
11829
+ return join4(homedir3(), ".hasna", "machines", "certs");
11045
11830
  }
11046
11831
  function buildCertPlan(domains) {
11047
11832
  if (domains.length === 0) {
11048
11833
  throw new Error("At least one domain is required.");
11049
11834
  }
11050
11835
  const primary = domains[0];
11051
- const certPath = join3(certDir(), `${primary}.pem`);
11052
- const keyPath = join3(certDir(), `${primary}-key.pem`);
11836
+ const certPath = join4(certDir(), `${primary}.pem`);
11837
+ const keyPath = join4(certDir(), `${primary}-key.pem`);
11053
11838
  const steps = [];
11054
- if (platform2() === "darwin") {
11839
+ if (platform3() === "darwin") {
11055
11840
  steps.push({
11056
11841
  id: "mkcert-install-macos",
11057
11842
  title: "Install mkcert on macOS",
@@ -11112,16 +11897,16 @@ function runCertPlan(domains, options = {}) {
11112
11897
  };
11113
11898
  }
11114
11899
  // src/commands/dns.ts
11115
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
11116
- import { join as join4 } from "path";
11900
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
11901
+ import { join as join5 } from "path";
11117
11902
  function getDnsPath() {
11118
- return join4(getDataDir(), "dns.json");
11903
+ return join5(getDataDir(), "dns.json");
11119
11904
  }
11120
11905
  function readMappings() {
11121
11906
  const path = getDnsPath();
11122
- if (!existsSync3(path))
11907
+ if (!existsSync5(path))
11123
11908
  return [];
11124
- return JSON.parse(readFileSync2(path, "utf8"));
11909
+ return JSON.parse(readFileSync3(path, "utf8"));
11125
11910
  }
11126
11911
  function writeMappings(mappings) {
11127
11912
  const path = getDnsPath();
@@ -11148,14 +11933,14 @@ function renderDomainMapping(domain) {
11148
11933
  hostsEntry: `${entry.targetHost} ${entry.domain}`,
11149
11934
  caddySnippet: `${entry.domain} {
11150
11935
  reverse_proxy 127.0.0.1:${entry.port}
11151
- tls ${join4(getDataDir(), "certs", `${entry.domain}.pem`)} ${join4(getDataDir(), "certs", `${entry.domain}-key.pem`)}
11936
+ tls ${join5(getDataDir(), "certs", `${entry.domain}.pem`)} ${join5(getDataDir(), "certs", `${entry.domain}-key.pem`)}
11152
11937
  }`,
11153
- certPath: join4(getDataDir(), "certs", `${entry.domain}.pem`),
11154
- keyPath: join4(getDataDir(), "certs", `${entry.domain}-key.pem`)
11938
+ certPath: join5(getDataDir(), "certs", `${entry.domain}.pem`),
11939
+ keyPath: join5(getDataDir(), "certs", `${entry.domain}-key.pem`)
11155
11940
  };
11156
11941
  }
11157
11942
  // src/commands/doctor.ts
11158
- function makeCheck(id, status, summary, detail) {
11943
+ function makeCheck2(id, status, summary, detail) {
11159
11944
  return { id, status, summary, detail };
11160
11945
  }
11161
11946
  function parseKeyValueOutput(stdout) {
@@ -11187,15 +11972,15 @@ function runDoctor(machineId = getLocalMachineId()) {
11187
11972
  const details = parseKeyValueOutput(commandChecks.stdout);
11188
11973
  const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
11189
11974
  const checks = [
11190
- makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
11191
- makeCheck("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
11192
- makeCheck("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
11193
- makeCheck("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
11194
- makeCheck("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
11195
- makeCheck("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
11196
- makeCheck("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
11197
- makeCheck("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
11198
- makeCheck("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
11975
+ makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
11976
+ makeCheck2("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
11977
+ makeCheck2("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
11978
+ makeCheck2("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
11979
+ makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
11980
+ makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
11981
+ makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
11982
+ makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
11983
+ makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
11199
11984
  ];
11200
11985
  return {
11201
11986
  machineId,
@@ -11451,7 +12236,7 @@ function runTailscaleInstall(machineId, options = {}) {
11451
12236
  };
11452
12237
  }
11453
12238
  // src/commands/notifications.ts
11454
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
12239
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
11455
12240
  var notificationChannelSchema = exports_external.object({
11456
12241
  id: exports_external.string(),
11457
12242
  type: exports_external.enum(["email", "webhook", "command"]),
@@ -11467,10 +12252,10 @@ var notificationConfigSchema = exports_external.object({
11467
12252
  function sortChannels(channels) {
11468
12253
  return [...channels].sort((left, right) => left.id.localeCompare(right.id));
11469
12254
  }
11470
- function shellQuote2(value) {
12255
+ function shellQuote3(value) {
11471
12256
  return `'${value.replace(/'/g, `'\\''`)}'`;
11472
12257
  }
11473
- function hasCommand(binary) {
12258
+ function hasCommand2(binary) {
11474
12259
  const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
11475
12260
  stdout: "ignore",
11476
12261
  stderr: "ignore",
@@ -11495,7 +12280,7 @@ Content-Type: text/plain; charset=utf-8
11495
12280
 
11496
12281
  ${message}
11497
12282
  `;
11498
- if (hasCommand("sendmail")) {
12283
+ if (hasCommand2("sendmail")) {
11499
12284
  const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
11500
12285
  stdin: new TextEncoder().encode(body),
11501
12286
  stdout: "pipe",
@@ -11513,8 +12298,8 @@ ${message}
11513
12298
  detail: `Delivered via sendmail to ${channel.target}`
11514
12299
  };
11515
12300
  }
11516
- if (hasCommand("mail")) {
11517
- const command = `printf %s ${shellQuote2(message)} | mail -s ${shellQuote2(subject)} ${shellQuote2(channel.target)}`;
12301
+ if (hasCommand2("mail")) {
12302
+ const command = `printf %s ${shellQuote3(message)} | mail -s ${shellQuote3(subject)} ${shellQuote3(channel.target)}`;
11518
12303
  const result = Bun.spawnSync(["bash", "-lc", command], {
11519
12304
  stdout: "pipe",
11520
12305
  stderr: "pipe",
@@ -11607,10 +12392,10 @@ function getDefaultNotificationConfig() {
11607
12392
  };
11608
12393
  }
11609
12394
  function readNotificationConfig(path = getNotificationsPath()) {
11610
- if (!existsSync4(path)) {
12395
+ if (!existsSync6(path)) {
11611
12396
  return getDefaultNotificationConfig();
11612
12397
  }
11613
- return notificationConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
12398
+ return notificationConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
11614
12399
  }
11615
12400
  function writeNotificationConfig(config, path = getNotificationsPath()) {
11616
12401
  ensureParentDir(path);
@@ -11697,7 +12482,7 @@ async function testNotificationChannel(channelId, event = "manual.test", message
11697
12482
  };
11698
12483
  }
11699
12484
  // src/commands/ports.ts
11700
- import { spawnSync as spawnSync3 } from "child_process";
12485
+ import { spawnSync as spawnSync4 } from "child_process";
11701
12486
  function parseSsOutput(output) {
11702
12487
  return output.trim().split(`
11703
12488
  `).map((line) => line.trim()).filter(Boolean).map((line) => {
@@ -11739,7 +12524,7 @@ function listPorts(machineId) {
11739
12524
  const isLocal = targetMachineId === getLocalMachineId();
11740
12525
  const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
11741
12526
  const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
11742
- const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
12527
+ const result = spawnSync4("bash", ["-lc", command], { encoding: "utf8" });
11743
12528
  if (result.status !== 0) {
11744
12529
  throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
11745
12530
  }
@@ -11749,24 +12534,6 @@ function listPorts(machineId) {
11749
12534
  listeners: parsePortOutput(result.stdout, format)
11750
12535
  };
11751
12536
  }
11752
- // src/version.ts
11753
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
11754
- import { dirname as dirname3, join as join5 } from "path";
11755
- import { fileURLToPath } from "url";
11756
- function getPackageVersion() {
11757
- try {
11758
- const here = dirname3(fileURLToPath(import.meta.url));
11759
- const candidates = [join5(here, "..", "package.json"), join5(here, "..", "..", "package.json")];
11760
- const pkgPath = candidates.find((candidate) => existsSync5(candidate));
11761
- if (!pkgPath) {
11762
- return "0.0.0";
11763
- }
11764
- return JSON.parse(readFileSync4(pkgPath, "utf8")).version || "0.0.0";
11765
- } catch {
11766
- return "0.0.0";
11767
- }
11768
- }
11769
-
11770
12537
  // src/commands/status.ts
11771
12538
  function getStatus() {
11772
12539
  const manifest = readManifest();
@@ -12198,7 +12965,7 @@ function runSetup(machineId, options = {}) {
12198
12965
  return summary;
12199
12966
  }
12200
12967
  // src/commands/sync.ts
12201
- import { existsSync as existsSync6, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
12968
+ import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
12202
12969
  function quote4(value) {
12203
12970
  return `'${value.replace(/'/g, `'\\''`)}'`;
12204
12971
  }
@@ -12246,8 +13013,8 @@ function detectPackageActions(machine) {
12246
13013
  }
12247
13014
  function detectFileActions(machine) {
12248
13015
  return (machine.files || []).map((file, index) => {
12249
- const sourceExists = existsSync6(file.source);
12250
- const targetExists = existsSync6(file.target);
13016
+ const sourceExists = existsSync7(file.source);
13017
+ const targetExists = existsSync7(file.target);
12251
13018
  let status = "missing";
12252
13019
  if (sourceExists && targetExists) {
12253
13020
  if (file.mode === "symlink") {
@@ -12425,7 +13192,7 @@ __export(exports_util, {
12425
13192
  omit: () => omit,
12426
13193
  numKeys: () => numKeys,
12427
13194
  nullish: () => nullish,
12428
- normalizeParams: () => normalizeParams,
13195
+ normalizeParams: () => normalizeParams2,
12429
13196
  merge: () => merge,
12430
13197
  jsonStringifyReplacer: () => jsonStringifyReplacer,
12431
13198
  joinValues: () => joinValues,
@@ -12662,7 +13429,7 @@ function clone(inst, def, params) {
12662
13429
  cl._zod.parent = inst;
12663
13430
  return cl;
12664
13431
  }
12665
- function normalizeParams(_params) {
13432
+ function normalizeParams2(_params) {
12666
13433
  const params = _params;
12667
13434
  if (!params)
12668
13435
  return {};
@@ -13072,7 +13839,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
13072
13839
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
13073
13840
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
13074
13841
  var base64url = /^[A-Za-z0-9_-]*$/;
13075
- var hostname3 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
13842
+ var hostname4 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
13076
13843
  var e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
13077
13844
  var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
13078
13845
  var date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
@@ -13684,7 +14451,7 @@ var $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
13684
14451
  code: "invalid_format",
13685
14452
  format: "url",
13686
14453
  note: "Invalid hostname",
13687
- pattern: hostname3.source,
14454
+ pattern: hostname4.source,
13688
14455
  input: payload.value,
13689
14456
  inst,
13690
14457
  continue: !def.abort
@@ -14936,7 +15703,7 @@ var globalRegistry = /* @__PURE__ */ registry();
14936
15703
  function _string(Class2, params) {
14937
15704
  return new Class2({
14938
15705
  type: "string",
14939
- ...normalizeParams(params)
15706
+ ...normalizeParams2(params)
14940
15707
  });
14941
15708
  }
14942
15709
  function _email(Class2, params) {
@@ -14945,7 +15712,7 @@ function _email(Class2, params) {
14945
15712
  format: "email",
14946
15713
  check: "string_format",
14947
15714
  abort: false,
14948
- ...normalizeParams(params)
15715
+ ...normalizeParams2(params)
14949
15716
  });
14950
15717
  }
14951
15718
  function _guid(Class2, params) {
@@ -14954,7 +15721,7 @@ function _guid(Class2, params) {
14954
15721
  format: "guid",
14955
15722
  check: "string_format",
14956
15723
  abort: false,
14957
- ...normalizeParams(params)
15724
+ ...normalizeParams2(params)
14958
15725
  });
14959
15726
  }
14960
15727
  function _uuid(Class2, params) {
@@ -14963,7 +15730,7 @@ function _uuid(Class2, params) {
14963
15730
  format: "uuid",
14964
15731
  check: "string_format",
14965
15732
  abort: false,
14966
- ...normalizeParams(params)
15733
+ ...normalizeParams2(params)
14967
15734
  });
14968
15735
  }
14969
15736
  function _uuidv4(Class2, params) {
@@ -14973,7 +15740,7 @@ function _uuidv4(Class2, params) {
14973
15740
  check: "string_format",
14974
15741
  abort: false,
14975
15742
  version: "v4",
14976
- ...normalizeParams(params)
15743
+ ...normalizeParams2(params)
14977
15744
  });
14978
15745
  }
14979
15746
  function _uuidv6(Class2, params) {
@@ -14983,7 +15750,7 @@ function _uuidv6(Class2, params) {
14983
15750
  check: "string_format",
14984
15751
  abort: false,
14985
15752
  version: "v6",
14986
- ...normalizeParams(params)
15753
+ ...normalizeParams2(params)
14987
15754
  });
14988
15755
  }
14989
15756
  function _uuidv7(Class2, params) {
@@ -14993,7 +15760,7 @@ function _uuidv7(Class2, params) {
14993
15760
  check: "string_format",
14994
15761
  abort: false,
14995
15762
  version: "v7",
14996
- ...normalizeParams(params)
15763
+ ...normalizeParams2(params)
14997
15764
  });
14998
15765
  }
14999
15766
  function _url(Class2, params) {
@@ -15002,7 +15769,7 @@ function _url(Class2, params) {
15002
15769
  format: "url",
15003
15770
  check: "string_format",
15004
15771
  abort: false,
15005
- ...normalizeParams(params)
15772
+ ...normalizeParams2(params)
15006
15773
  });
15007
15774
  }
15008
15775
  function _emoji2(Class2, params) {
@@ -15011,7 +15778,7 @@ function _emoji2(Class2, params) {
15011
15778
  format: "emoji",
15012
15779
  check: "string_format",
15013
15780
  abort: false,
15014
- ...normalizeParams(params)
15781
+ ...normalizeParams2(params)
15015
15782
  });
15016
15783
  }
15017
15784
  function _nanoid(Class2, params) {
@@ -15020,7 +15787,7 @@ function _nanoid(Class2, params) {
15020
15787
  format: "nanoid",
15021
15788
  check: "string_format",
15022
15789
  abort: false,
15023
- ...normalizeParams(params)
15790
+ ...normalizeParams2(params)
15024
15791
  });
15025
15792
  }
15026
15793
  function _cuid(Class2, params) {
@@ -15029,7 +15796,7 @@ function _cuid(Class2, params) {
15029
15796
  format: "cuid",
15030
15797
  check: "string_format",
15031
15798
  abort: false,
15032
- ...normalizeParams(params)
15799
+ ...normalizeParams2(params)
15033
15800
  });
15034
15801
  }
15035
15802
  function _cuid2(Class2, params) {
@@ -15038,7 +15805,7 @@ function _cuid2(Class2, params) {
15038
15805
  format: "cuid2",
15039
15806
  check: "string_format",
15040
15807
  abort: false,
15041
- ...normalizeParams(params)
15808
+ ...normalizeParams2(params)
15042
15809
  });
15043
15810
  }
15044
15811
  function _ulid(Class2, params) {
@@ -15047,7 +15814,7 @@ function _ulid(Class2, params) {
15047
15814
  format: "ulid",
15048
15815
  check: "string_format",
15049
15816
  abort: false,
15050
- ...normalizeParams(params)
15817
+ ...normalizeParams2(params)
15051
15818
  });
15052
15819
  }
15053
15820
  function _xid(Class2, params) {
@@ -15056,7 +15823,7 @@ function _xid(Class2, params) {
15056
15823
  format: "xid",
15057
15824
  check: "string_format",
15058
15825
  abort: false,
15059
- ...normalizeParams(params)
15826
+ ...normalizeParams2(params)
15060
15827
  });
15061
15828
  }
15062
15829
  function _ksuid(Class2, params) {
@@ -15065,7 +15832,7 @@ function _ksuid(Class2, params) {
15065
15832
  format: "ksuid",
15066
15833
  check: "string_format",
15067
15834
  abort: false,
15068
- ...normalizeParams(params)
15835
+ ...normalizeParams2(params)
15069
15836
  });
15070
15837
  }
15071
15838
  function _ipv4(Class2, params) {
@@ -15074,7 +15841,7 @@ function _ipv4(Class2, params) {
15074
15841
  format: "ipv4",
15075
15842
  check: "string_format",
15076
15843
  abort: false,
15077
- ...normalizeParams(params)
15844
+ ...normalizeParams2(params)
15078
15845
  });
15079
15846
  }
15080
15847
  function _ipv6(Class2, params) {
@@ -15083,7 +15850,7 @@ function _ipv6(Class2, params) {
15083
15850
  format: "ipv6",
15084
15851
  check: "string_format",
15085
15852
  abort: false,
15086
- ...normalizeParams(params)
15853
+ ...normalizeParams2(params)
15087
15854
  });
15088
15855
  }
15089
15856
  function _cidrv4(Class2, params) {
@@ -15092,7 +15859,7 @@ function _cidrv4(Class2, params) {
15092
15859
  format: "cidrv4",
15093
15860
  check: "string_format",
15094
15861
  abort: false,
15095
- ...normalizeParams(params)
15862
+ ...normalizeParams2(params)
15096
15863
  });
15097
15864
  }
15098
15865
  function _cidrv6(Class2, params) {
@@ -15101,7 +15868,7 @@ function _cidrv6(Class2, params) {
15101
15868
  format: "cidrv6",
15102
15869
  check: "string_format",
15103
15870
  abort: false,
15104
- ...normalizeParams(params)
15871
+ ...normalizeParams2(params)
15105
15872
  });
15106
15873
  }
15107
15874
  function _base64(Class2, params) {
@@ -15110,7 +15877,7 @@ function _base64(Class2, params) {
15110
15877
  format: "base64",
15111
15878
  check: "string_format",
15112
15879
  abort: false,
15113
- ...normalizeParams(params)
15880
+ ...normalizeParams2(params)
15114
15881
  });
15115
15882
  }
15116
15883
  function _base64url(Class2, params) {
@@ -15119,7 +15886,7 @@ function _base64url(Class2, params) {
15119
15886
  format: "base64url",
15120
15887
  check: "string_format",
15121
15888
  abort: false,
15122
- ...normalizeParams(params)
15889
+ ...normalizeParams2(params)
15123
15890
  });
15124
15891
  }
15125
15892
  function _e164(Class2, params) {
@@ -15128,7 +15895,7 @@ function _e164(Class2, params) {
15128
15895
  format: "e164",
15129
15896
  check: "string_format",
15130
15897
  abort: false,
15131
- ...normalizeParams(params)
15898
+ ...normalizeParams2(params)
15132
15899
  });
15133
15900
  }
15134
15901
  function _jwt(Class2, params) {
@@ -15137,7 +15904,7 @@ function _jwt(Class2, params) {
15137
15904
  format: "jwt",
15138
15905
  check: "string_format",
15139
15906
  abort: false,
15140
- ...normalizeParams(params)
15907
+ ...normalizeParams2(params)
15141
15908
  });
15142
15909
  }
15143
15910
  function _isoDateTime(Class2, params) {
@@ -15148,7 +15915,7 @@ function _isoDateTime(Class2, params) {
15148
15915
  offset: false,
15149
15916
  local: false,
15150
15917
  precision: null,
15151
- ...normalizeParams(params)
15918
+ ...normalizeParams2(params)
15152
15919
  });
15153
15920
  }
15154
15921
  function _isoDate(Class2, params) {
@@ -15156,7 +15923,7 @@ function _isoDate(Class2, params) {
15156
15923
  type: "string",
15157
15924
  format: "date",
15158
15925
  check: "string_format",
15159
- ...normalizeParams(params)
15926
+ ...normalizeParams2(params)
15160
15927
  });
15161
15928
  }
15162
15929
  function _isoTime(Class2, params) {
@@ -15165,7 +15932,7 @@ function _isoTime(Class2, params) {
15165
15932
  format: "time",
15166
15933
  check: "string_format",
15167
15934
  precision: null,
15168
- ...normalizeParams(params)
15935
+ ...normalizeParams2(params)
15169
15936
  });
15170
15937
  }
15171
15938
  function _isoDuration(Class2, params) {
@@ -15173,14 +15940,14 @@ function _isoDuration(Class2, params) {
15173
15940
  type: "string",
15174
15941
  format: "duration",
15175
15942
  check: "string_format",
15176
- ...normalizeParams(params)
15943
+ ...normalizeParams2(params)
15177
15944
  });
15178
15945
  }
15179
15946
  function _number(Class2, params) {
15180
15947
  return new Class2({
15181
15948
  type: "number",
15182
15949
  checks: [],
15183
- ...normalizeParams(params)
15950
+ ...normalizeParams2(params)
15184
15951
  });
15185
15952
  }
15186
15953
  function _int(Class2, params) {
@@ -15189,19 +15956,19 @@ function _int(Class2, params) {
15189
15956
  check: "number_format",
15190
15957
  abort: false,
15191
15958
  format: "safeint",
15192
- ...normalizeParams(params)
15959
+ ...normalizeParams2(params)
15193
15960
  });
15194
15961
  }
15195
15962
  function _boolean(Class2, params) {
15196
15963
  return new Class2({
15197
15964
  type: "boolean",
15198
- ...normalizeParams(params)
15965
+ ...normalizeParams2(params)
15199
15966
  });
15200
15967
  }
15201
15968
  function _null2(Class2, params) {
15202
15969
  return new Class2({
15203
15970
  type: "null",
15204
- ...normalizeParams(params)
15971
+ ...normalizeParams2(params)
15205
15972
  });
15206
15973
  }
15207
15974
  function _unknown(Class2) {
@@ -15212,13 +15979,13 @@ function _unknown(Class2) {
15212
15979
  function _never(Class2, params) {
15213
15980
  return new Class2({
15214
15981
  type: "never",
15215
- ...normalizeParams(params)
15982
+ ...normalizeParams2(params)
15216
15983
  });
15217
15984
  }
15218
15985
  function _lt(value, params) {
15219
15986
  return new $ZodCheckLessThan({
15220
15987
  check: "less_than",
15221
- ...normalizeParams(params),
15988
+ ...normalizeParams2(params),
15222
15989
  value,
15223
15990
  inclusive: false
15224
15991
  });
@@ -15226,7 +15993,7 @@ function _lt(value, params) {
15226
15993
  function _lte(value, params) {
15227
15994
  return new $ZodCheckLessThan({
15228
15995
  check: "less_than",
15229
- ...normalizeParams(params),
15996
+ ...normalizeParams2(params),
15230
15997
  value,
15231
15998
  inclusive: true
15232
15999
  });
@@ -15234,7 +16001,7 @@ function _lte(value, params) {
15234
16001
  function _gt(value, params) {
15235
16002
  return new $ZodCheckGreaterThan({
15236
16003
  check: "greater_than",
15237
- ...normalizeParams(params),
16004
+ ...normalizeParams2(params),
15238
16005
  value,
15239
16006
  inclusive: false
15240
16007
  });
@@ -15242,7 +16009,7 @@ function _gt(value, params) {
15242
16009
  function _gte(value, params) {
15243
16010
  return new $ZodCheckGreaterThan({
15244
16011
  check: "greater_than",
15245
- ...normalizeParams(params),
16012
+ ...normalizeParams2(params),
15246
16013
  value,
15247
16014
  inclusive: true
15248
16015
  });
@@ -15250,14 +16017,14 @@ function _gte(value, params) {
15250
16017
  function _multipleOf(value, params) {
15251
16018
  return new $ZodCheckMultipleOf({
15252
16019
  check: "multiple_of",
15253
- ...normalizeParams(params),
16020
+ ...normalizeParams2(params),
15254
16021
  value
15255
16022
  });
15256
16023
  }
15257
16024
  function _maxLength(maximum, params) {
15258
16025
  const ch = new $ZodCheckMaxLength({
15259
16026
  check: "max_length",
15260
- ...normalizeParams(params),
16027
+ ...normalizeParams2(params),
15261
16028
  maximum
15262
16029
  });
15263
16030
  return ch;
@@ -15265,14 +16032,14 @@ function _maxLength(maximum, params) {
15265
16032
  function _minLength(minimum, params) {
15266
16033
  return new $ZodCheckMinLength({
15267
16034
  check: "min_length",
15268
- ...normalizeParams(params),
16035
+ ...normalizeParams2(params),
15269
16036
  minimum
15270
16037
  });
15271
16038
  }
15272
16039
  function _length(length, params) {
15273
16040
  return new $ZodCheckLengthEquals({
15274
16041
  check: "length_equals",
15275
- ...normalizeParams(params),
16042
+ ...normalizeParams2(params),
15276
16043
  length
15277
16044
  });
15278
16045
  }
@@ -15280,7 +16047,7 @@ function _regex(pattern, params) {
15280
16047
  return new $ZodCheckRegex({
15281
16048
  check: "string_format",
15282
16049
  format: "regex",
15283
- ...normalizeParams(params),
16050
+ ...normalizeParams2(params),
15284
16051
  pattern
15285
16052
  });
15286
16053
  }
@@ -15288,21 +16055,21 @@ function _lowercase(params) {
15288
16055
  return new $ZodCheckLowerCase({
15289
16056
  check: "string_format",
15290
16057
  format: "lowercase",
15291
- ...normalizeParams(params)
16058
+ ...normalizeParams2(params)
15292
16059
  });
15293
16060
  }
15294
16061
  function _uppercase(params) {
15295
16062
  return new $ZodCheckUpperCase({
15296
16063
  check: "string_format",
15297
16064
  format: "uppercase",
15298
- ...normalizeParams(params)
16065
+ ...normalizeParams2(params)
15299
16066
  });
15300
16067
  }
15301
16068
  function _includes(includes, params) {
15302
16069
  return new $ZodCheckIncludes({
15303
16070
  check: "string_format",
15304
16071
  format: "includes",
15305
- ...normalizeParams(params),
16072
+ ...normalizeParams2(params),
15306
16073
  includes
15307
16074
  });
15308
16075
  }
@@ -15310,7 +16077,7 @@ function _startsWith(prefix, params) {
15310
16077
  return new $ZodCheckStartsWith({
15311
16078
  check: "string_format",
15312
16079
  format: "starts_with",
15313
- ...normalizeParams(params),
16080
+ ...normalizeParams2(params),
15314
16081
  prefix
15315
16082
  });
15316
16083
  }
@@ -15318,7 +16085,7 @@ function _endsWith(suffix, params) {
15318
16085
  return new $ZodCheckEndsWith({
15319
16086
  check: "string_format",
15320
16087
  format: "ends_with",
15321
- ...normalizeParams(params),
16088
+ ...normalizeParams2(params),
15322
16089
  suffix
15323
16090
  });
15324
16091
  }
@@ -15344,11 +16111,11 @@ function _array(Class2, element, params) {
15344
16111
  return new Class2({
15345
16112
  type: "array",
15346
16113
  element,
15347
- ...normalizeParams(params)
16114
+ ...normalizeParams2(params)
15348
16115
  });
15349
16116
  }
15350
16117
  function _custom(Class2, fn, _params) {
15351
- const norm = normalizeParams(_params);
16118
+ const norm = normalizeParams2(_params);
15352
16119
  norm.abort ?? (norm.abort = true);
15353
16120
  const schema = new Class2({
15354
16121
  type: "custom",
@@ -15363,7 +16130,7 @@ function _refine(Class2, fn, _params) {
15363
16130
  type: "custom",
15364
16131
  check: "custom",
15365
16132
  fn,
15366
- ...normalizeParams(_params)
16133
+ ...normalizeParams2(_params)
15367
16134
  });
15368
16135
  return schema;
15369
16136
  }
@@ -21276,6 +22043,8 @@ var MACHINE_MCP_TOOL_NAMES = [
21276
22043
  "machines_setup_apply",
21277
22044
  "machines_sync_preview",
21278
22045
  "machines_sync_apply",
22046
+ "machines_topology",
22047
+ "machines_compatibility",
21279
22048
  "machines_diff",
21280
22049
  "machines_install_tailscale_preview",
21281
22050
  "machines_install_tailscale_apply",
@@ -21298,8 +22067,15 @@ var MACHINE_MCP_TOOL_NAMES = [
21298
22067
  "machines_notifications_dispatch",
21299
22068
  "machines_notifications_remove",
21300
22069
  "machines_serve_info",
21301
- "machines_serve_dashboard"
22070
+ "machines_serve_dashboard",
22071
+ "storage_status",
22072
+ "storage_push",
22073
+ "storage_pull",
22074
+ "storage_sync"
21302
22075
  ];
22076
+ function buildServer(version2 = getPackageVersion()) {
22077
+ return createMcpServer(version2);
22078
+ }
21303
22079
  function createMcpServer(version2) {
21304
22080
  const server = new McpServer({ name: "machines", version: version2 });
21305
22081
  server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
@@ -21332,6 +22108,33 @@ function createMcpServer(version2) {
21332
22108
  server.tool("machines_setup_apply", "Execute setup actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSetup(machine_id, { apply: true, yes }), null, 2) }] }));
21333
22109
  server.tool("machines_sync_preview", "Preview sync actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildSyncPlan(machine_id), null, 2) }] }));
21334
22110
  server.tool("machines_sync_apply", "Execute sync actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSync(machine_id, { apply: true, yes }), null, 2) }] }));
22111
+ server.tool("machines_topology", "Discover local, manifest, heartbeat, SSH, and Tailscale machine topology.", { include_tailscale: exports_external.boolean().optional().describe("Whether to probe tailscale status --json") }, async ({ include_tailscale }) => ({
22112
+ content: [{ type: "text", text: JSON.stringify(discoverMachineTopology({ includeTailscale: include_tailscale !== false }), null, 2) }]
22113
+ }));
22114
+ server.tool("machines_compatibility", "Check remote package, command, and workspace compatibility for open-* consumers.", {
22115
+ machine_id: exports_external.string().optional().describe("Machine identifier"),
22116
+ commands: exports_external.array(exports_external.object({
22117
+ command: exports_external.string(),
22118
+ expectedVersion: exports_external.string().optional(),
22119
+ versionArgs: exports_external.string().optional(),
22120
+ required: exports_external.boolean().optional()
22121
+ })).optional().describe("Commands to check"),
22122
+ packages: exports_external.array(exports_external.object({
22123
+ name: exports_external.string(),
22124
+ command: exports_external.string().optional(),
22125
+ expectedVersion: exports_external.string().optional(),
22126
+ required: exports_external.boolean().optional()
22127
+ })).optional().describe("Package-backed CLI checks"),
22128
+ workspaces: exports_external.array(exports_external.object({
22129
+ path: exports_external.string(),
22130
+ label: exports_external.string().optional(),
22131
+ expectedPackageName: exports_external.string().optional(),
22132
+ expectedVersion: exports_external.string().optional(),
22133
+ required: exports_external.boolean().optional()
22134
+ })).optional().describe("Workspace paths and package metadata to check")
22135
+ }, async ({ machine_id, commands, packages, workspaces }) => ({
22136
+ content: [{ type: "text", text: JSON.stringify(checkMachineCompatibility({ machineId: machine_id, commands, packages, workspaces }), null, 2) }]
22137
+ }));
21335
22138
  server.tool("machines_diff", "Show manifest differences between two machines.", {
21336
22139
  left_machine_id: exports_external.string().describe("Left machine identifier"),
21337
22140
  right_machine_id: exports_external.string().optional().describe("Right machine identifier")
@@ -21393,6 +22196,12 @@ function createMcpServer(version2) {
21393
22196
  server.tool("machines_serve_dashboard", "Render the current dashboard HTML.", {}, async () => ({
21394
22197
  content: [{ type: "text", text: renderDashboardHtml() }]
21395
22198
  }));
22199
+ server.tool("storage_status", "Show machines storage sync configuration and local sync history.", {}, async () => ({
22200
+ content: [{ type: "text", text: JSON.stringify(getStorageStatus(), null, 2) }]
22201
+ }));
22202
+ server.tool("storage_push", "Push local machine runtime data to storage PostgreSQL.", { tables: exports_external.array(exports_external.string()).optional().describe("Optional table list to push") }, async ({ tables }) => ({ content: [{ type: "text", text: JSON.stringify(await storagePush(tables ? { tables } : undefined), null, 2) }] }));
22203
+ server.tool("storage_pull", "Pull machine runtime data from storage PostgreSQL to local SQLite.", { tables: exports_external.array(exports_external.string()).optional().describe("Optional table list to pull") }, async ({ tables }) => ({ content: [{ type: "text", text: JSON.stringify(await storagePull(tables ? { tables } : undefined), null, 2) }] }));
22204
+ server.tool("storage_sync", "Bidirectional machines storage sync: pull then push.", { tables: exports_external.array(exports_external.string()).optional().describe("Optional table list to sync") }, async ({ tables }) => ({ content: [{ type: "text", text: JSON.stringify(await storageSync(tables ? { tables } : undefined), null, 2) }] }));
21396
22205
  return server;
21397
22206
  }
21398
22207
  export {
@@ -21402,10 +22211,14 @@ export {
21402
22211
  validateManifest,
21403
22212
  upsertHeartbeat,
21404
22213
  testNotificationChannel,
22214
+ storageSync,
22215
+ storagePush,
22216
+ storagePull,
21405
22217
  startDashboardServer,
21406
22218
  setHeartbeatStatus,
21407
22219
  runTailscaleInstall,
21408
22220
  runSync,
22221
+ runStorageMigrations,
21409
22222
  runSetup,
21410
22223
  runSelfTest,
21411
22224
  runDoctor,
@@ -21413,6 +22226,7 @@ export {
21413
22226
  runCertPlan,
21414
22227
  runBackup,
21415
22228
  runAppsInstall,
22229
+ resolveTables,
21416
22230
  resolveSshTarget,
21417
22231
  renderDomainMapping,
21418
22232
  renderDashboardHtml,
@@ -21421,6 +22235,7 @@ export {
21421
22235
  recordSetupRun,
21422
22236
  readNotificationConfig,
21423
22237
  readManifest,
22238
+ parseStorageTables,
21424
22239
  parsePortOutput,
21425
22240
  markOffline,
21426
22241
  manifestValidate,
@@ -21436,12 +22251,20 @@ export {
21436
22251
  listHeartbeats,
21437
22252
  listDomainMappings,
21438
22253
  listApps,
22254
+ getSyncMetaAll,
22255
+ getStorageStatus,
22256
+ getStoragePg,
22257
+ getStorageMode,
22258
+ getStorageDatabaseUrl,
22259
+ getStorageDatabaseEnvName,
22260
+ getStorageDatabaseEnv,
21439
22261
  getStatus,
21440
22262
  getServeInfo,
21441
22263
  getPackageVersion,
21442
22264
  getNotificationsPath,
21443
22265
  getManifestPath,
21444
22266
  getManifestMachine,
22267
+ getLocalMachineTopology,
21445
22268
  getLocalMachineId,
21446
22269
  getDefaultNotificationConfig,
21447
22270
  getDefaultManifest,
@@ -21458,16 +22281,20 @@ export {
21458
22281
  ensureParentDir,
21459
22282
  ensureDataDir,
21460
22283
  dispatchNotificationEvent,
22284
+ discoverMachineTopology,
21461
22285
  diffMachines,
21462
22286
  diffClaudeCli,
21463
22287
  diffApps,
21464
22288
  detectCurrentMachineManifest,
21465
22289
  createMcpServer,
21466
22290
  countRuns,
22291
+ closeDb,
22292
+ checkMachineCompatibility,
21467
22293
  buildTailscaleInstallPlan,
21468
22294
  buildSyncPlan,
21469
22295
  buildSshCommand,
21470
22296
  buildSetupPlan,
22297
+ buildServer,
21471
22298
  buildClaudeInstallPlan,
21472
22299
  buildCertPlan,
21473
22300
  buildBackupPlan,
@@ -21475,6 +22302,14 @@ export {
21475
22302
  addNotificationChannel,
21476
22303
  addDomainMapping,
21477
22304
  SqliteAdapter,
22305
+ STORAGE_TABLES,
22306
+ STORAGE_MODE_ENV,
22307
+ STORAGE_DATABASE_ENV,
21478
22308
  MACHINE_MCP_TOOL_NAMES,
22309
+ MACHINES_STORAGE_TABLES,
22310
+ MACHINES_STORAGE_MODE_FALLBACK_ENV,
22311
+ MACHINES_STORAGE_MODE_ENV,
22312
+ MACHINES_STORAGE_FALLBACK_ENV,
22313
+ MACHINES_STORAGE_ENV,
21479
22314
  CROSSREFS_KEY
21480
22315
  };