@hasna/machines 0.0.14 → 0.0.16

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,538 @@ 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
+ import { hostname as hostname4 } from "os";
11285
+
11286
+ // src/commands/ssh.ts
11287
+ import { spawnSync as spawnSync2 } from "child_process";
11288
+ function envReachableHosts() {
11289
+ const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
11290
+ return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
11291
+ }
11292
+ function isReachable(host) {
11293
+ const overrides = envReachableHosts();
11294
+ if (overrides.size > 0) {
11295
+ return overrides.has(host);
11296
+ }
11297
+ const probe = spawnSync2("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
11298
+ stdio: "ignore"
11299
+ });
11300
+ return probe.status === 0;
11301
+ }
11302
+ function resolveSshTarget(machineId) {
11303
+ const machine = getManifestMachine(machineId);
11304
+ if (!machine) {
11305
+ throw new Error(`Machine not found in manifest: ${machineId}`);
11306
+ }
11307
+ const current = detectCurrentMachineManifest();
11308
+ if (machine.id === current.id) {
11309
+ return {
11310
+ machineId,
11311
+ target: "localhost",
11312
+ route: "local"
11313
+ };
11314
+ }
11315
+ const lanTarget = machine.sshAddress || machine.hostname || machine.id;
11316
+ const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
11317
+ const route = isReachable(lanTarget) ? "lan" : "tailscale";
11318
+ return {
11319
+ machineId,
11320
+ target: route === "lan" ? lanTarget : tailscaleTarget,
11321
+ route
11322
+ };
11323
+ }
11324
+ function buildSshCommand(machineId, remoteCommand) {
11325
+ const resolved = resolveSshTarget(machineId);
11326
+ return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
11327
+ }
11328
+
11329
+ // src/remote.ts
11330
+ function shellQuote(value) {
11331
+ return `'${value.replace(/'/g, "'\\''")}'`;
11332
+ }
11333
+ function machineIsLocal(machineId, localMachineId) {
11334
+ return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
11335
+ }
11336
+ function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
11337
+ if (machineIsLocal(machineId, localMachineId)) {
11338
+ return { source: "local", shellCommand: command };
11339
+ }
11340
+ try {
11341
+ return {
11342
+ source: resolveSshTarget(machineId).route,
11343
+ shellCommand: buildSshCommand(machineId, command)
11344
+ };
11345
+ } catch (error) {
11346
+ if (String(error.message ?? error).includes("Machine not found in manifest")) {
11347
+ return { source: "ssh", shellCommand: `ssh ${shellQuote(machineId)} ${shellQuote(command)}` };
11348
+ }
11349
+ throw error;
11350
+ }
11351
+ }
11352
+ function runMachineCommand(machineId, command) {
11353
+ const resolved = resolveMachineCommand(machineId, command);
11354
+ const result = spawnSync3("bash", ["-c", resolved.shellCommand], {
11355
+ encoding: "utf8",
11356
+ env: process.env
11357
+ });
11358
+ return {
11359
+ machineId,
11360
+ source: resolved.source,
11361
+ stdout: result.stdout || "",
11362
+ stderr: result.stderr || "",
11363
+ exitCode: result.status ?? 1
11364
+ };
11365
+ }
11366
+
11367
+ // src/version.ts
11368
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
11369
+ import { dirname as dirname3, join as join2 } from "path";
11370
+ import { fileURLToPath } from "url";
11371
+ function getPackageVersion() {
11372
+ try {
11373
+ const here = dirname3(fileURLToPath(import.meta.url));
11374
+ const candidates = [join2(here, "..", "package.json"), join2(here, "..", "..", "package.json")];
11375
+ const pkgPath = candidates.find((candidate) => existsSync4(candidate));
11376
+ if (!pkgPath) {
11377
+ return "0.0.0";
11378
+ }
11379
+ return JSON.parse(readFileSync2(pkgPath, "utf8")).version || "0.0.0";
11380
+ } catch {
11381
+ return "0.0.0";
11382
+ }
11383
+ }
11384
+
11385
+ // src/compatibility.ts
11386
+ var DEFAULT_COMMANDS = [
11387
+ { command: "bun", required: true },
11388
+ { command: "machines", required: true }
11389
+ ];
11390
+ function defaultPackages() {
11391
+ return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
11392
+ }
11393
+ function shellQuote2(value) {
11394
+ return `'${value.replace(/'/g, "'\\''")}'`;
11395
+ }
11396
+ function commandId(value) {
11397
+ return value.replace(/[^a-zA-Z0-9_.@/-]+/g, "-").replace(/^-+|-+$/g, "");
11398
+ }
11399
+ function packageCommand(name) {
11400
+ if (name === "@hasna/knowledge")
11401
+ return "knowledge";
11402
+ if (name === "@hasna/machines")
11403
+ return "machines";
11404
+ return name.split("/").pop() ?? name;
11405
+ }
11406
+ function firstLine(value) {
11407
+ return value.trim().split(/\r?\n/).find(Boolean) ?? "";
11408
+ }
11409
+ function extractVersion(value) {
11410
+ const match = value.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
11411
+ return match?.[0] ?? null;
11412
+ }
11413
+ function statusFor(required, ok) {
11414
+ if (ok)
11415
+ return "ok";
11416
+ return required === false ? "warn" : "fail";
11417
+ }
11418
+ function makeCheck(input) {
11419
+ return {
11420
+ id: input.id,
11421
+ kind: input.kind,
11422
+ status: input.status,
11423
+ target: input.target,
11424
+ expected: input.expected ?? null,
11425
+ actual: input.actual ?? null,
11426
+ detail: input.detail,
11427
+ source: input.source
11428
+ };
11429
+ }
11430
+ function parseKeyValue(stdout) {
11431
+ const result = {};
11432
+ for (const line of stdout.split(/\r?\n/)) {
11433
+ const idx = line.indexOf("=");
11434
+ if (idx <= 0)
11435
+ continue;
11436
+ result[line.slice(0, idx)] = line.slice(idx + 1);
11437
+ }
11438
+ return result;
11439
+ }
11440
+ function defaultRunner2(machineId, command) {
11441
+ return runMachineCommand(machineId, command);
11442
+ }
11443
+ function inspectCommand(machineId, spec, runner) {
11444
+ const command = shellQuote2(spec.command);
11445
+ const versionArgs = spec.versionArgs ?? "--version";
11446
+ const script = [
11447
+ `cmd=${command}`,
11448
+ 'path="$(command -v "$cmd" 2>/dev/null || true)"',
11449
+ 'printf "path=%s\\n" "$path"',
11450
+ 'if [ -n "$path" ]; then version="$("$cmd" ' + versionArgs + ' 2>/dev/null || true)"; printf "version=%s\\n" "$version"; fi'
11451
+ ].join("; ");
11452
+ const result = runner(machineId, script);
11453
+ const parsed = parseKeyValue(result.stdout);
11454
+ return {
11455
+ path: parsed.path || null,
11456
+ version: parsed.version ? firstLine(parsed.version) : null,
11457
+ exitCode: result.exitCode,
11458
+ source: result.source,
11459
+ stderr: result.stderr
11460
+ };
11461
+ }
11462
+ function fieldCommand(field) {
11463
+ const regex = field === "name" ? String.raw`s/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p` : String.raw`s/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p`;
11464
+ return [
11465
+ `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`,
11466
+ `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`,
11467
+ `else sed -n '${regex}' "$pkg" | head -n 1`,
11468
+ "fi"
11469
+ ].join("; ");
11470
+ }
11471
+ function inspectWorkspace(machineId, spec, runner) {
11472
+ const script = [
11473
+ `path=${shellQuote2(spec.path)}`,
11474
+ 'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
11475
+ 'pkg="$path/package.json"',
11476
+ 'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
11477
+ `if [ -f "$pkg" ]; then printf "package_name=%s\\n" "$(${fieldCommand("name")})"; printf "version=%s\\n" "$(${fieldCommand("version")})"; fi`
11478
+ ].join("; ");
11479
+ const result = runner(machineId, script);
11480
+ const parsed = parseKeyValue(result.stdout);
11481
+ return {
11482
+ exists: parsed.exists === "yes",
11483
+ packageJson: parsed.package_json === "yes",
11484
+ packageName: parsed.package_name || null,
11485
+ version: parsed.version || null,
11486
+ source: result.source,
11487
+ stderr: result.stderr
11488
+ };
11489
+ }
11490
+ function commandCheck(machineId, spec, runner) {
11491
+ const inspection = inspectCommand(machineId, spec, runner);
11492
+ const found = Boolean(inspection.path);
11493
+ const checks = [
11494
+ makeCheck({
11495
+ id: `command:${commandId(spec.command)}:path`,
11496
+ kind: "command",
11497
+ status: statusFor(spec.required, found),
11498
+ target: spec.command,
11499
+ expected: "available",
11500
+ actual: inspection.path ?? "missing",
11501
+ detail: found ? `found at ${inspection.path}` : inspection.stderr || "command missing",
11502
+ source: inspection.source
11503
+ })
11504
+ ];
11505
+ if (spec.expectedVersion) {
11506
+ const actualVersion = extractVersion(inspection.version ?? "");
11507
+ checks.push(makeCheck({
11508
+ id: `command:${commandId(spec.command)}:version`,
11509
+ kind: "command",
11510
+ status: actualVersion === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11511
+ target: spec.command,
11512
+ expected: spec.expectedVersion,
11513
+ actual: actualVersion ?? inspection.version ?? "missing",
11514
+ detail: actualVersion ? `version output: ${inspection.version}` : "version unavailable",
11515
+ source: inspection.source
11516
+ }));
11517
+ }
11518
+ return checks;
11519
+ }
11520
+ function packageCheck(machineId, spec, runner) {
11521
+ const command = spec.command ?? packageCommand(spec.name);
11522
+ const inspection = inspectCommand(machineId, { command, expectedVersion: spec.expectedVersion, required: spec.required }, runner);
11523
+ const found = Boolean(inspection.path);
11524
+ const checks = [
11525
+ makeCheck({
11526
+ id: `package:${commandId(spec.name)}:command`,
11527
+ kind: "package",
11528
+ status: statusFor(spec.required, found),
11529
+ target: spec.name,
11530
+ expected: command,
11531
+ actual: inspection.path ?? "missing",
11532
+ detail: found ? `${command} found at ${inspection.path}` : `${command} command missing`,
11533
+ source: inspection.source
11534
+ })
11535
+ ];
11536
+ if (spec.expectedVersion) {
11537
+ const actualVersion = extractVersion(inspection.version ?? "");
11538
+ checks.push(makeCheck({
11539
+ id: `package:${commandId(spec.name)}:version`,
11540
+ kind: "package",
11541
+ status: actualVersion === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11542
+ target: spec.name,
11543
+ expected: spec.expectedVersion,
11544
+ actual: actualVersion ?? inspection.version ?? "missing",
11545
+ detail: actualVersion ? `version output: ${inspection.version}` : "version unavailable",
11546
+ source: inspection.source
11547
+ }));
11548
+ }
11549
+ return checks;
11550
+ }
11551
+ function workspaceCheck(machineId, spec, runner) {
11552
+ const inspection = inspectWorkspace(machineId, spec, runner);
11553
+ const target = spec.label ?? spec.path;
11554
+ const checks = [
11555
+ makeCheck({
11556
+ id: `workspace:${commandId(target)}:path`,
11557
+ kind: "workspace",
11558
+ status: statusFor(spec.required, inspection.exists),
11559
+ target,
11560
+ expected: spec.path,
11561
+ actual: inspection.exists ? "exists" : "missing",
11562
+ detail: inspection.exists ? `workspace exists at ${spec.path}` : inspection.stderr || `workspace missing at ${spec.path}`,
11563
+ source: inspection.source
11564
+ })
11565
+ ];
11566
+ if (spec.expectedPackageName) {
11567
+ checks.push(makeCheck({
11568
+ id: `workspace:${commandId(target)}:package-name`,
11569
+ kind: "workspace",
11570
+ status: inspection.packageName === spec.expectedPackageName ? "ok" : statusFor(spec.required, false),
11571
+ target,
11572
+ expected: spec.expectedPackageName,
11573
+ actual: inspection.packageName ?? (inspection.packageJson ? "missing-name" : "missing-package-json"),
11574
+ detail: inspection.packageJson ? "package.json inspected" : "package.json missing",
11575
+ source: inspection.source
11576
+ }));
11577
+ }
11578
+ if (spec.expectedVersion) {
11579
+ checks.push(makeCheck({
11580
+ id: `workspace:${commandId(target)}:version`,
11581
+ kind: "workspace",
11582
+ status: inspection.version === spec.expectedVersion ? "ok" : statusFor(spec.required, false),
11583
+ target,
11584
+ expected: spec.expectedVersion,
11585
+ actual: inspection.version ?? (inspection.packageJson ? "missing-version" : "missing-package-json"),
11586
+ detail: inspection.packageJson ? "package.json inspected" : "package.json missing",
11587
+ source: inspection.source
11588
+ }));
11589
+ }
11590
+ return checks;
11591
+ }
11592
+ function checkMachineCompatibility(options = {}) {
11593
+ const machineId = options.machineId ?? getLocalMachineId();
11594
+ const runner = options.runner ?? defaultRunner2;
11595
+ const commands = options.commands ?? DEFAULT_COMMANDS;
11596
+ const packages = options.packages ?? defaultPackages();
11597
+ const workspaces = options.workspaces ?? [];
11598
+ const checks = [];
11599
+ for (const spec of commands)
11600
+ checks.push(...commandCheck(machineId, spec, runner));
11601
+ for (const spec of packages)
11602
+ checks.push(...packageCheck(machineId, spec, runner));
11603
+ for (const spec of workspaces)
11604
+ checks.push(...workspaceCheck(machineId, spec, runner));
11605
+ const summary = {
11606
+ ok: checks.filter((check) => check.status === "ok").length,
11607
+ warn: checks.filter((check) => check.status === "warn").length,
11608
+ fail: checks.filter((check) => check.status === "fail").length
11609
+ };
11610
+ return {
11611
+ ok: summary.fail === 0,
11612
+ machine_id: machineId,
11613
+ source: checks[0]?.source ?? "local",
11614
+ generated_at: (options.now ?? new Date).toISOString(),
11615
+ checks,
11616
+ summary
11617
+ };
11618
+ }
10749
11619
  // src/agent/runtime.ts
10750
11620
  function writeHeartbeat(status = "online") {
10751
11621
  const machineId = getLocalMachineId();
@@ -10770,20 +11640,20 @@ function getAgentStatus(machineId = getLocalMachineId()) {
10770
11640
  }
10771
11641
  // src/commands/backup.ts
10772
11642
  import { homedir as homedir2 } from "os";
10773
- import { join as join2 } from "path";
11643
+ import { join as join3 } from "path";
10774
11644
  function quote(value) {
10775
11645
  return `'${value.replace(/'/g, `'\\''`)}'`;
10776
11646
  }
10777
11647
  function defaultBackupSources() {
10778
11648
  const home = homedir2();
10779
11649
  return [
10780
- join2(home, ".hasna"),
10781
- join2(home, ".ssh"),
10782
- join2(home, ".secrets")
11650
+ join3(home, ".hasna"),
11651
+ join3(home, ".ssh"),
11652
+ join3(home, ".secrets")
10783
11653
  ];
10784
11654
  }
10785
11655
  function buildBackupPlan(bucket, prefix = "machines") {
10786
- const archivePath = join2(homedir2(), ".hasna", "machines", "backup.tgz");
11656
+ const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
10787
11657
  const sources = defaultBackupSources();
10788
11658
  const steps = [
10789
11659
  {
@@ -10832,71 +11702,6 @@ function runBackup(bucket, prefix = "machines", options = {}) {
10832
11702
  executed
10833
11703
  };
10834
11704
  }
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
11705
  // src/commands/apps.ts
10901
11706
  function getPackageName(app) {
10902
11707
  return app.packageName || app.name;
@@ -10910,7 +11715,7 @@ function getAppManager(machine, app) {
10910
11715
  return "winget";
10911
11716
  return "apt";
10912
11717
  }
10913
- function shellQuote(value) {
11718
+ function shellQuote3(value) {
10914
11719
  return `'${value.replace(/'/g, `'\\''`)}'`;
10915
11720
  }
10916
11721
  function buildAppCommand(machine, app) {
@@ -10931,7 +11736,7 @@ function buildAppCommand(machine, app) {
10931
11736
  return `sudo apt-get install -y ${packageName}`;
10932
11737
  }
10933
11738
  function buildAppProbeCommand(machine, app) {
10934
- const packageName = shellQuote(getPackageName(app));
11739
+ const packageName = shellQuote3(getPackageName(app));
10935
11740
  const manager = getAppManager(machine, app);
10936
11741
  if (manager === "custom") {
10937
11742
  return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
@@ -11035,23 +11840,23 @@ function runAppsInstall(machineId, options = {}) {
11035
11840
  };
11036
11841
  }
11037
11842
  // src/commands/cert.ts
11038
- import { homedir as homedir3, platform as platform2 } from "os";
11039
- import { join as join3 } from "path";
11843
+ import { homedir as homedir3, platform as platform3 } from "os";
11844
+ import { join as join4 } from "path";
11040
11845
  function quote2(value) {
11041
11846
  return `'${value.replace(/'/g, `'\\''`)}'`;
11042
11847
  }
11043
11848
  function certDir() {
11044
- return join3(homedir3(), ".hasna", "machines", "certs");
11849
+ return join4(homedir3(), ".hasna", "machines", "certs");
11045
11850
  }
11046
11851
  function buildCertPlan(domains) {
11047
11852
  if (domains.length === 0) {
11048
11853
  throw new Error("At least one domain is required.");
11049
11854
  }
11050
11855
  const primary = domains[0];
11051
- const certPath = join3(certDir(), `${primary}.pem`);
11052
- const keyPath = join3(certDir(), `${primary}-key.pem`);
11856
+ const certPath = join4(certDir(), `${primary}.pem`);
11857
+ const keyPath = join4(certDir(), `${primary}-key.pem`);
11053
11858
  const steps = [];
11054
- if (platform2() === "darwin") {
11859
+ if (platform3() === "darwin") {
11055
11860
  steps.push({
11056
11861
  id: "mkcert-install-macos",
11057
11862
  title: "Install mkcert on macOS",
@@ -11112,16 +11917,16 @@ function runCertPlan(domains, options = {}) {
11112
11917
  };
11113
11918
  }
11114
11919
  // src/commands/dns.ts
11115
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
11116
- import { join as join4 } from "path";
11920
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
11921
+ import { join as join5 } from "path";
11117
11922
  function getDnsPath() {
11118
- return join4(getDataDir(), "dns.json");
11923
+ return join5(getDataDir(), "dns.json");
11119
11924
  }
11120
11925
  function readMappings() {
11121
11926
  const path = getDnsPath();
11122
- if (!existsSync3(path))
11927
+ if (!existsSync5(path))
11123
11928
  return [];
11124
- return JSON.parse(readFileSync2(path, "utf8"));
11929
+ return JSON.parse(readFileSync3(path, "utf8"));
11125
11930
  }
11126
11931
  function writeMappings(mappings) {
11127
11932
  const path = getDnsPath();
@@ -11148,14 +11953,14 @@ function renderDomainMapping(domain) {
11148
11953
  hostsEntry: `${entry.targetHost} ${entry.domain}`,
11149
11954
  caddySnippet: `${entry.domain} {
11150
11955
  reverse_proxy 127.0.0.1:${entry.port}
11151
- tls ${join4(getDataDir(), "certs", `${entry.domain}.pem`)} ${join4(getDataDir(), "certs", `${entry.domain}-key.pem`)}
11956
+ tls ${join5(getDataDir(), "certs", `${entry.domain}.pem`)} ${join5(getDataDir(), "certs", `${entry.domain}-key.pem`)}
11152
11957
  }`,
11153
- certPath: join4(getDataDir(), "certs", `${entry.domain}.pem`),
11154
- keyPath: join4(getDataDir(), "certs", `${entry.domain}-key.pem`)
11958
+ certPath: join5(getDataDir(), "certs", `${entry.domain}.pem`),
11959
+ keyPath: join5(getDataDir(), "certs", `${entry.domain}-key.pem`)
11155
11960
  };
11156
11961
  }
11157
11962
  // src/commands/doctor.ts
11158
- function makeCheck(id, status, summary, detail) {
11963
+ function makeCheck2(id, status, summary, detail) {
11159
11964
  return { id, status, summary, detail };
11160
11965
  }
11161
11966
  function parseKeyValueOutput(stdout) {
@@ -11187,15 +11992,15 @@ function runDoctor(machineId = getLocalMachineId()) {
11187
11992
  const details = parseKeyValueOutput(commandChecks.stdout);
11188
11993
  const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
11189
11994
  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")
11995
+ makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
11996
+ makeCheck2("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
11997
+ makeCheck2("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
11998
+ makeCheck2("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
11999
+ makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
12000
+ makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
12001
+ makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
12002
+ makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
12003
+ makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
11199
12004
  ];
11200
12005
  return {
11201
12006
  machineId,
@@ -11451,7 +12256,7 @@ function runTailscaleInstall(machineId, options = {}) {
11451
12256
  };
11452
12257
  }
11453
12258
  // src/commands/notifications.ts
11454
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
12259
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
11455
12260
  var notificationChannelSchema = exports_external.object({
11456
12261
  id: exports_external.string(),
11457
12262
  type: exports_external.enum(["email", "webhook", "command"]),
@@ -11467,10 +12272,10 @@ var notificationConfigSchema = exports_external.object({
11467
12272
  function sortChannels(channels) {
11468
12273
  return [...channels].sort((left, right) => left.id.localeCompare(right.id));
11469
12274
  }
11470
- function shellQuote2(value) {
12275
+ function shellQuote4(value) {
11471
12276
  return `'${value.replace(/'/g, `'\\''`)}'`;
11472
12277
  }
11473
- function hasCommand(binary) {
12278
+ function hasCommand2(binary) {
11474
12279
  const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
11475
12280
  stdout: "ignore",
11476
12281
  stderr: "ignore",
@@ -11495,7 +12300,7 @@ Content-Type: text/plain; charset=utf-8
11495
12300
 
11496
12301
  ${message}
11497
12302
  `;
11498
- if (hasCommand("sendmail")) {
12303
+ if (hasCommand2("sendmail")) {
11499
12304
  const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
11500
12305
  stdin: new TextEncoder().encode(body),
11501
12306
  stdout: "pipe",
@@ -11513,8 +12318,8 @@ ${message}
11513
12318
  detail: `Delivered via sendmail to ${channel.target}`
11514
12319
  };
11515
12320
  }
11516
- if (hasCommand("mail")) {
11517
- const command = `printf %s ${shellQuote2(message)} | mail -s ${shellQuote2(subject)} ${shellQuote2(channel.target)}`;
12321
+ if (hasCommand2("mail")) {
12322
+ const command = `printf %s ${shellQuote4(message)} | mail -s ${shellQuote4(subject)} ${shellQuote4(channel.target)}`;
11518
12323
  const result = Bun.spawnSync(["bash", "-lc", command], {
11519
12324
  stdout: "pipe",
11520
12325
  stderr: "pipe",
@@ -11607,10 +12412,10 @@ function getDefaultNotificationConfig() {
11607
12412
  };
11608
12413
  }
11609
12414
  function readNotificationConfig(path = getNotificationsPath()) {
11610
- if (!existsSync4(path)) {
12415
+ if (!existsSync6(path)) {
11611
12416
  return getDefaultNotificationConfig();
11612
12417
  }
11613
- return notificationConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
12418
+ return notificationConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
11614
12419
  }
11615
12420
  function writeNotificationConfig(config, path = getNotificationsPath()) {
11616
12421
  ensureParentDir(path);
@@ -11697,7 +12502,7 @@ async function testNotificationChannel(channelId, event = "manual.test", message
11697
12502
  };
11698
12503
  }
11699
12504
  // src/commands/ports.ts
11700
- import { spawnSync as spawnSync3 } from "child_process";
12505
+ import { spawnSync as spawnSync4 } from "child_process";
11701
12506
  function parseSsOutput(output) {
11702
12507
  return output.trim().split(`
11703
12508
  `).map((line) => line.trim()).filter(Boolean).map((line) => {
@@ -11739,7 +12544,7 @@ function listPorts(machineId) {
11739
12544
  const isLocal = targetMachineId === getLocalMachineId();
11740
12545
  const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
11741
12546
  const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
11742
- const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
12547
+ const result = spawnSync4("bash", ["-lc", command], { encoding: "utf8" });
11743
12548
  if (result.status !== 0) {
11744
12549
  throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
11745
12550
  }
@@ -11749,24 +12554,6 @@ function listPorts(machineId) {
11749
12554
  listeners: parsePortOutput(result.stdout, format)
11750
12555
  };
11751
12556
  }
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
12557
  // src/commands/status.ts
11771
12558
  function getStatus() {
11772
12559
  const manifest = readManifest();
@@ -12198,7 +12985,7 @@ function runSetup(machineId, options = {}) {
12198
12985
  return summary;
12199
12986
  }
12200
12987
  // src/commands/sync.ts
12201
- import { existsSync as existsSync6, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
12988
+ import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
12202
12989
  function quote4(value) {
12203
12990
  return `'${value.replace(/'/g, `'\\''`)}'`;
12204
12991
  }
@@ -12246,8 +13033,8 @@ function detectPackageActions(machine) {
12246
13033
  }
12247
13034
  function detectFileActions(machine) {
12248
13035
  return (machine.files || []).map((file, index) => {
12249
- const sourceExists = existsSync6(file.source);
12250
- const targetExists = existsSync6(file.target);
13036
+ const sourceExists = existsSync7(file.source);
13037
+ const targetExists = existsSync7(file.target);
12251
13038
  let status = "missing";
12252
13039
  if (sourceExists && targetExists) {
12253
13040
  if (file.mode === "symlink") {
@@ -12425,7 +13212,7 @@ __export(exports_util, {
12425
13212
  omit: () => omit,
12426
13213
  numKeys: () => numKeys,
12427
13214
  nullish: () => nullish,
12428
- normalizeParams: () => normalizeParams,
13215
+ normalizeParams: () => normalizeParams2,
12429
13216
  merge: () => merge,
12430
13217
  jsonStringifyReplacer: () => jsonStringifyReplacer,
12431
13218
  joinValues: () => joinValues,
@@ -12662,7 +13449,7 @@ function clone(inst, def, params) {
12662
13449
  cl._zod.parent = inst;
12663
13450
  return cl;
12664
13451
  }
12665
- function normalizeParams(_params) {
13452
+ function normalizeParams2(_params) {
12666
13453
  const params = _params;
12667
13454
  if (!params)
12668
13455
  return {};
@@ -13072,7 +13859,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
13859
  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
13860
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
13074
13861
  var base64url = /^[A-Za-z0-9_-]*$/;
13075
- var hostname3 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
13862
+ var hostname5 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
13076
13863
  var e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
13077
13864
  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
13865
  var date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
@@ -13684,7 +14471,7 @@ var $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
13684
14471
  code: "invalid_format",
13685
14472
  format: "url",
13686
14473
  note: "Invalid hostname",
13687
- pattern: hostname3.source,
14474
+ pattern: hostname5.source,
13688
14475
  input: payload.value,
13689
14476
  inst,
13690
14477
  continue: !def.abort
@@ -14936,7 +15723,7 @@ var globalRegistry = /* @__PURE__ */ registry();
14936
15723
  function _string(Class2, params) {
14937
15724
  return new Class2({
14938
15725
  type: "string",
14939
- ...normalizeParams(params)
15726
+ ...normalizeParams2(params)
14940
15727
  });
14941
15728
  }
14942
15729
  function _email(Class2, params) {
@@ -14945,7 +15732,7 @@ function _email(Class2, params) {
14945
15732
  format: "email",
14946
15733
  check: "string_format",
14947
15734
  abort: false,
14948
- ...normalizeParams(params)
15735
+ ...normalizeParams2(params)
14949
15736
  });
14950
15737
  }
14951
15738
  function _guid(Class2, params) {
@@ -14954,7 +15741,7 @@ function _guid(Class2, params) {
14954
15741
  format: "guid",
14955
15742
  check: "string_format",
14956
15743
  abort: false,
14957
- ...normalizeParams(params)
15744
+ ...normalizeParams2(params)
14958
15745
  });
14959
15746
  }
14960
15747
  function _uuid(Class2, params) {
@@ -14963,7 +15750,7 @@ function _uuid(Class2, params) {
14963
15750
  format: "uuid",
14964
15751
  check: "string_format",
14965
15752
  abort: false,
14966
- ...normalizeParams(params)
15753
+ ...normalizeParams2(params)
14967
15754
  });
14968
15755
  }
14969
15756
  function _uuidv4(Class2, params) {
@@ -14973,7 +15760,7 @@ function _uuidv4(Class2, params) {
14973
15760
  check: "string_format",
14974
15761
  abort: false,
14975
15762
  version: "v4",
14976
- ...normalizeParams(params)
15763
+ ...normalizeParams2(params)
14977
15764
  });
14978
15765
  }
14979
15766
  function _uuidv6(Class2, params) {
@@ -14983,7 +15770,7 @@ function _uuidv6(Class2, params) {
14983
15770
  check: "string_format",
14984
15771
  abort: false,
14985
15772
  version: "v6",
14986
- ...normalizeParams(params)
15773
+ ...normalizeParams2(params)
14987
15774
  });
14988
15775
  }
14989
15776
  function _uuidv7(Class2, params) {
@@ -14993,7 +15780,7 @@ function _uuidv7(Class2, params) {
14993
15780
  check: "string_format",
14994
15781
  abort: false,
14995
15782
  version: "v7",
14996
- ...normalizeParams(params)
15783
+ ...normalizeParams2(params)
14997
15784
  });
14998
15785
  }
14999
15786
  function _url(Class2, params) {
@@ -15002,7 +15789,7 @@ function _url(Class2, params) {
15002
15789
  format: "url",
15003
15790
  check: "string_format",
15004
15791
  abort: false,
15005
- ...normalizeParams(params)
15792
+ ...normalizeParams2(params)
15006
15793
  });
15007
15794
  }
15008
15795
  function _emoji2(Class2, params) {
@@ -15011,7 +15798,7 @@ function _emoji2(Class2, params) {
15011
15798
  format: "emoji",
15012
15799
  check: "string_format",
15013
15800
  abort: false,
15014
- ...normalizeParams(params)
15801
+ ...normalizeParams2(params)
15015
15802
  });
15016
15803
  }
15017
15804
  function _nanoid(Class2, params) {
@@ -15020,7 +15807,7 @@ function _nanoid(Class2, params) {
15020
15807
  format: "nanoid",
15021
15808
  check: "string_format",
15022
15809
  abort: false,
15023
- ...normalizeParams(params)
15810
+ ...normalizeParams2(params)
15024
15811
  });
15025
15812
  }
15026
15813
  function _cuid(Class2, params) {
@@ -15029,7 +15816,7 @@ function _cuid(Class2, params) {
15029
15816
  format: "cuid",
15030
15817
  check: "string_format",
15031
15818
  abort: false,
15032
- ...normalizeParams(params)
15819
+ ...normalizeParams2(params)
15033
15820
  });
15034
15821
  }
15035
15822
  function _cuid2(Class2, params) {
@@ -15038,7 +15825,7 @@ function _cuid2(Class2, params) {
15038
15825
  format: "cuid2",
15039
15826
  check: "string_format",
15040
15827
  abort: false,
15041
- ...normalizeParams(params)
15828
+ ...normalizeParams2(params)
15042
15829
  });
15043
15830
  }
15044
15831
  function _ulid(Class2, params) {
@@ -15047,7 +15834,7 @@ function _ulid(Class2, params) {
15047
15834
  format: "ulid",
15048
15835
  check: "string_format",
15049
15836
  abort: false,
15050
- ...normalizeParams(params)
15837
+ ...normalizeParams2(params)
15051
15838
  });
15052
15839
  }
15053
15840
  function _xid(Class2, params) {
@@ -15056,7 +15843,7 @@ function _xid(Class2, params) {
15056
15843
  format: "xid",
15057
15844
  check: "string_format",
15058
15845
  abort: false,
15059
- ...normalizeParams(params)
15846
+ ...normalizeParams2(params)
15060
15847
  });
15061
15848
  }
15062
15849
  function _ksuid(Class2, params) {
@@ -15065,7 +15852,7 @@ function _ksuid(Class2, params) {
15065
15852
  format: "ksuid",
15066
15853
  check: "string_format",
15067
15854
  abort: false,
15068
- ...normalizeParams(params)
15855
+ ...normalizeParams2(params)
15069
15856
  });
15070
15857
  }
15071
15858
  function _ipv4(Class2, params) {
@@ -15074,7 +15861,7 @@ function _ipv4(Class2, params) {
15074
15861
  format: "ipv4",
15075
15862
  check: "string_format",
15076
15863
  abort: false,
15077
- ...normalizeParams(params)
15864
+ ...normalizeParams2(params)
15078
15865
  });
15079
15866
  }
15080
15867
  function _ipv6(Class2, params) {
@@ -15083,7 +15870,7 @@ function _ipv6(Class2, params) {
15083
15870
  format: "ipv6",
15084
15871
  check: "string_format",
15085
15872
  abort: false,
15086
- ...normalizeParams(params)
15873
+ ...normalizeParams2(params)
15087
15874
  });
15088
15875
  }
15089
15876
  function _cidrv4(Class2, params) {
@@ -15092,7 +15879,7 @@ function _cidrv4(Class2, params) {
15092
15879
  format: "cidrv4",
15093
15880
  check: "string_format",
15094
15881
  abort: false,
15095
- ...normalizeParams(params)
15882
+ ...normalizeParams2(params)
15096
15883
  });
15097
15884
  }
15098
15885
  function _cidrv6(Class2, params) {
@@ -15101,7 +15888,7 @@ function _cidrv6(Class2, params) {
15101
15888
  format: "cidrv6",
15102
15889
  check: "string_format",
15103
15890
  abort: false,
15104
- ...normalizeParams(params)
15891
+ ...normalizeParams2(params)
15105
15892
  });
15106
15893
  }
15107
15894
  function _base64(Class2, params) {
@@ -15110,7 +15897,7 @@ function _base64(Class2, params) {
15110
15897
  format: "base64",
15111
15898
  check: "string_format",
15112
15899
  abort: false,
15113
- ...normalizeParams(params)
15900
+ ...normalizeParams2(params)
15114
15901
  });
15115
15902
  }
15116
15903
  function _base64url(Class2, params) {
@@ -15119,7 +15906,7 @@ function _base64url(Class2, params) {
15119
15906
  format: "base64url",
15120
15907
  check: "string_format",
15121
15908
  abort: false,
15122
- ...normalizeParams(params)
15909
+ ...normalizeParams2(params)
15123
15910
  });
15124
15911
  }
15125
15912
  function _e164(Class2, params) {
@@ -15128,7 +15915,7 @@ function _e164(Class2, params) {
15128
15915
  format: "e164",
15129
15916
  check: "string_format",
15130
15917
  abort: false,
15131
- ...normalizeParams(params)
15918
+ ...normalizeParams2(params)
15132
15919
  });
15133
15920
  }
15134
15921
  function _jwt(Class2, params) {
@@ -15137,7 +15924,7 @@ function _jwt(Class2, params) {
15137
15924
  format: "jwt",
15138
15925
  check: "string_format",
15139
15926
  abort: false,
15140
- ...normalizeParams(params)
15927
+ ...normalizeParams2(params)
15141
15928
  });
15142
15929
  }
15143
15930
  function _isoDateTime(Class2, params) {
@@ -15148,7 +15935,7 @@ function _isoDateTime(Class2, params) {
15148
15935
  offset: false,
15149
15936
  local: false,
15150
15937
  precision: null,
15151
- ...normalizeParams(params)
15938
+ ...normalizeParams2(params)
15152
15939
  });
15153
15940
  }
15154
15941
  function _isoDate(Class2, params) {
@@ -15156,7 +15943,7 @@ function _isoDate(Class2, params) {
15156
15943
  type: "string",
15157
15944
  format: "date",
15158
15945
  check: "string_format",
15159
- ...normalizeParams(params)
15946
+ ...normalizeParams2(params)
15160
15947
  });
15161
15948
  }
15162
15949
  function _isoTime(Class2, params) {
@@ -15165,7 +15952,7 @@ function _isoTime(Class2, params) {
15165
15952
  format: "time",
15166
15953
  check: "string_format",
15167
15954
  precision: null,
15168
- ...normalizeParams(params)
15955
+ ...normalizeParams2(params)
15169
15956
  });
15170
15957
  }
15171
15958
  function _isoDuration(Class2, params) {
@@ -15173,14 +15960,14 @@ function _isoDuration(Class2, params) {
15173
15960
  type: "string",
15174
15961
  format: "duration",
15175
15962
  check: "string_format",
15176
- ...normalizeParams(params)
15963
+ ...normalizeParams2(params)
15177
15964
  });
15178
15965
  }
15179
15966
  function _number(Class2, params) {
15180
15967
  return new Class2({
15181
15968
  type: "number",
15182
15969
  checks: [],
15183
- ...normalizeParams(params)
15970
+ ...normalizeParams2(params)
15184
15971
  });
15185
15972
  }
15186
15973
  function _int(Class2, params) {
@@ -15189,19 +15976,19 @@ function _int(Class2, params) {
15189
15976
  check: "number_format",
15190
15977
  abort: false,
15191
15978
  format: "safeint",
15192
- ...normalizeParams(params)
15979
+ ...normalizeParams2(params)
15193
15980
  });
15194
15981
  }
15195
15982
  function _boolean(Class2, params) {
15196
15983
  return new Class2({
15197
15984
  type: "boolean",
15198
- ...normalizeParams(params)
15985
+ ...normalizeParams2(params)
15199
15986
  });
15200
15987
  }
15201
15988
  function _null2(Class2, params) {
15202
15989
  return new Class2({
15203
15990
  type: "null",
15204
- ...normalizeParams(params)
15991
+ ...normalizeParams2(params)
15205
15992
  });
15206
15993
  }
15207
15994
  function _unknown(Class2) {
@@ -15212,13 +15999,13 @@ function _unknown(Class2) {
15212
15999
  function _never(Class2, params) {
15213
16000
  return new Class2({
15214
16001
  type: "never",
15215
- ...normalizeParams(params)
16002
+ ...normalizeParams2(params)
15216
16003
  });
15217
16004
  }
15218
16005
  function _lt(value, params) {
15219
16006
  return new $ZodCheckLessThan({
15220
16007
  check: "less_than",
15221
- ...normalizeParams(params),
16008
+ ...normalizeParams2(params),
15222
16009
  value,
15223
16010
  inclusive: false
15224
16011
  });
@@ -15226,7 +16013,7 @@ function _lt(value, params) {
15226
16013
  function _lte(value, params) {
15227
16014
  return new $ZodCheckLessThan({
15228
16015
  check: "less_than",
15229
- ...normalizeParams(params),
16016
+ ...normalizeParams2(params),
15230
16017
  value,
15231
16018
  inclusive: true
15232
16019
  });
@@ -15234,7 +16021,7 @@ function _lte(value, params) {
15234
16021
  function _gt(value, params) {
15235
16022
  return new $ZodCheckGreaterThan({
15236
16023
  check: "greater_than",
15237
- ...normalizeParams(params),
16024
+ ...normalizeParams2(params),
15238
16025
  value,
15239
16026
  inclusive: false
15240
16027
  });
@@ -15242,7 +16029,7 @@ function _gt(value, params) {
15242
16029
  function _gte(value, params) {
15243
16030
  return new $ZodCheckGreaterThan({
15244
16031
  check: "greater_than",
15245
- ...normalizeParams(params),
16032
+ ...normalizeParams2(params),
15246
16033
  value,
15247
16034
  inclusive: true
15248
16035
  });
@@ -15250,14 +16037,14 @@ function _gte(value, params) {
15250
16037
  function _multipleOf(value, params) {
15251
16038
  return new $ZodCheckMultipleOf({
15252
16039
  check: "multiple_of",
15253
- ...normalizeParams(params),
16040
+ ...normalizeParams2(params),
15254
16041
  value
15255
16042
  });
15256
16043
  }
15257
16044
  function _maxLength(maximum, params) {
15258
16045
  const ch = new $ZodCheckMaxLength({
15259
16046
  check: "max_length",
15260
- ...normalizeParams(params),
16047
+ ...normalizeParams2(params),
15261
16048
  maximum
15262
16049
  });
15263
16050
  return ch;
@@ -15265,14 +16052,14 @@ function _maxLength(maximum, params) {
15265
16052
  function _minLength(minimum, params) {
15266
16053
  return new $ZodCheckMinLength({
15267
16054
  check: "min_length",
15268
- ...normalizeParams(params),
16055
+ ...normalizeParams2(params),
15269
16056
  minimum
15270
16057
  });
15271
16058
  }
15272
16059
  function _length(length, params) {
15273
16060
  return new $ZodCheckLengthEquals({
15274
16061
  check: "length_equals",
15275
- ...normalizeParams(params),
16062
+ ...normalizeParams2(params),
15276
16063
  length
15277
16064
  });
15278
16065
  }
@@ -15280,7 +16067,7 @@ function _regex(pattern, params) {
15280
16067
  return new $ZodCheckRegex({
15281
16068
  check: "string_format",
15282
16069
  format: "regex",
15283
- ...normalizeParams(params),
16070
+ ...normalizeParams2(params),
15284
16071
  pattern
15285
16072
  });
15286
16073
  }
@@ -15288,21 +16075,21 @@ function _lowercase(params) {
15288
16075
  return new $ZodCheckLowerCase({
15289
16076
  check: "string_format",
15290
16077
  format: "lowercase",
15291
- ...normalizeParams(params)
16078
+ ...normalizeParams2(params)
15292
16079
  });
15293
16080
  }
15294
16081
  function _uppercase(params) {
15295
16082
  return new $ZodCheckUpperCase({
15296
16083
  check: "string_format",
15297
16084
  format: "uppercase",
15298
- ...normalizeParams(params)
16085
+ ...normalizeParams2(params)
15299
16086
  });
15300
16087
  }
15301
16088
  function _includes(includes, params) {
15302
16089
  return new $ZodCheckIncludes({
15303
16090
  check: "string_format",
15304
16091
  format: "includes",
15305
- ...normalizeParams(params),
16092
+ ...normalizeParams2(params),
15306
16093
  includes
15307
16094
  });
15308
16095
  }
@@ -15310,7 +16097,7 @@ function _startsWith(prefix, params) {
15310
16097
  return new $ZodCheckStartsWith({
15311
16098
  check: "string_format",
15312
16099
  format: "starts_with",
15313
- ...normalizeParams(params),
16100
+ ...normalizeParams2(params),
15314
16101
  prefix
15315
16102
  });
15316
16103
  }
@@ -15318,7 +16105,7 @@ function _endsWith(suffix, params) {
15318
16105
  return new $ZodCheckEndsWith({
15319
16106
  check: "string_format",
15320
16107
  format: "ends_with",
15321
- ...normalizeParams(params),
16108
+ ...normalizeParams2(params),
15322
16109
  suffix
15323
16110
  });
15324
16111
  }
@@ -15344,11 +16131,11 @@ function _array(Class2, element, params) {
15344
16131
  return new Class2({
15345
16132
  type: "array",
15346
16133
  element,
15347
- ...normalizeParams(params)
16134
+ ...normalizeParams2(params)
15348
16135
  });
15349
16136
  }
15350
16137
  function _custom(Class2, fn, _params) {
15351
- const norm = normalizeParams(_params);
16138
+ const norm = normalizeParams2(_params);
15352
16139
  norm.abort ?? (norm.abort = true);
15353
16140
  const schema = new Class2({
15354
16141
  type: "custom",
@@ -15363,7 +16150,7 @@ function _refine(Class2, fn, _params) {
15363
16150
  type: "custom",
15364
16151
  check: "custom",
15365
16152
  fn,
15366
- ...normalizeParams(_params)
16153
+ ...normalizeParams2(_params)
15367
16154
  });
15368
16155
  return schema;
15369
16156
  }
@@ -21276,6 +22063,8 @@ var MACHINE_MCP_TOOL_NAMES = [
21276
22063
  "machines_setup_apply",
21277
22064
  "machines_sync_preview",
21278
22065
  "machines_sync_apply",
22066
+ "machines_topology",
22067
+ "machines_compatibility",
21279
22068
  "machines_diff",
21280
22069
  "machines_install_tailscale_preview",
21281
22070
  "machines_install_tailscale_apply",
@@ -21298,8 +22087,15 @@ var MACHINE_MCP_TOOL_NAMES = [
21298
22087
  "machines_notifications_dispatch",
21299
22088
  "machines_notifications_remove",
21300
22089
  "machines_serve_info",
21301
- "machines_serve_dashboard"
22090
+ "machines_serve_dashboard",
22091
+ "storage_status",
22092
+ "storage_push",
22093
+ "storage_pull",
22094
+ "storage_sync"
21302
22095
  ];
22096
+ function buildServer(version2 = getPackageVersion()) {
22097
+ return createMcpServer(version2);
22098
+ }
21303
22099
  function createMcpServer(version2) {
21304
22100
  const server = new McpServer({ name: "machines", version: version2 });
21305
22101
  server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
@@ -21332,6 +22128,33 @@ function createMcpServer(version2) {
21332
22128
  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
22129
  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
22130
  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) }] }));
22131
+ 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 }) => ({
22132
+ content: [{ type: "text", text: JSON.stringify(discoverMachineTopology({ includeTailscale: include_tailscale !== false }), null, 2) }]
22133
+ }));
22134
+ server.tool("machines_compatibility", "Check remote package, command, and workspace compatibility for open-* consumers.", {
22135
+ machine_id: exports_external.string().optional().describe("Machine identifier"),
22136
+ commands: exports_external.array(exports_external.object({
22137
+ command: exports_external.string(),
22138
+ expectedVersion: exports_external.string().optional(),
22139
+ versionArgs: exports_external.string().optional(),
22140
+ required: exports_external.boolean().optional()
22141
+ })).optional().describe("Commands to check"),
22142
+ packages: exports_external.array(exports_external.object({
22143
+ name: exports_external.string(),
22144
+ command: exports_external.string().optional(),
22145
+ expectedVersion: exports_external.string().optional(),
22146
+ required: exports_external.boolean().optional()
22147
+ })).optional().describe("Package-backed CLI checks"),
22148
+ workspaces: exports_external.array(exports_external.object({
22149
+ path: exports_external.string(),
22150
+ label: exports_external.string().optional(),
22151
+ expectedPackageName: exports_external.string().optional(),
22152
+ expectedVersion: exports_external.string().optional(),
22153
+ required: exports_external.boolean().optional()
22154
+ })).optional().describe("Workspace paths and package metadata to check")
22155
+ }, async ({ machine_id, commands, packages, workspaces }) => ({
22156
+ content: [{ type: "text", text: JSON.stringify(checkMachineCompatibility({ machineId: machine_id, commands, packages, workspaces }), null, 2) }]
22157
+ }));
21335
22158
  server.tool("machines_diff", "Show manifest differences between two machines.", {
21336
22159
  left_machine_id: exports_external.string().describe("Left machine identifier"),
21337
22160
  right_machine_id: exports_external.string().optional().describe("Right machine identifier")
@@ -21393,6 +22216,12 @@ function createMcpServer(version2) {
21393
22216
  server.tool("machines_serve_dashboard", "Render the current dashboard HTML.", {}, async () => ({
21394
22217
  content: [{ type: "text", text: renderDashboardHtml() }]
21395
22218
  }));
22219
+ server.tool("storage_status", "Show machines storage sync configuration and local sync history.", {}, async () => ({
22220
+ content: [{ type: "text", text: JSON.stringify(getStorageStatus(), null, 2) }]
22221
+ }));
22222
+ 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) }] }));
22223
+ 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) }] }));
22224
+ 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
22225
  return server;
21397
22226
  }
21398
22227
  export {
@@ -21402,10 +22231,14 @@ export {
21402
22231
  validateManifest,
21403
22232
  upsertHeartbeat,
21404
22233
  testNotificationChannel,
22234
+ storageSync,
22235
+ storagePush,
22236
+ storagePull,
21405
22237
  startDashboardServer,
21406
22238
  setHeartbeatStatus,
21407
22239
  runTailscaleInstall,
21408
22240
  runSync,
22241
+ runStorageMigrations,
21409
22242
  runSetup,
21410
22243
  runSelfTest,
21411
22244
  runDoctor,
@@ -21413,6 +22246,7 @@ export {
21413
22246
  runCertPlan,
21414
22247
  runBackup,
21415
22248
  runAppsInstall,
22249
+ resolveTables,
21416
22250
  resolveSshTarget,
21417
22251
  renderDomainMapping,
21418
22252
  renderDashboardHtml,
@@ -21421,6 +22255,7 @@ export {
21421
22255
  recordSetupRun,
21422
22256
  readNotificationConfig,
21423
22257
  readManifest,
22258
+ parseStorageTables,
21424
22259
  parsePortOutput,
21425
22260
  markOffline,
21426
22261
  manifestValidate,
@@ -21436,12 +22271,20 @@ export {
21436
22271
  listHeartbeats,
21437
22272
  listDomainMappings,
21438
22273
  listApps,
22274
+ getSyncMetaAll,
22275
+ getStorageStatus,
22276
+ getStoragePg,
22277
+ getStorageMode,
22278
+ getStorageDatabaseUrl,
22279
+ getStorageDatabaseEnvName,
22280
+ getStorageDatabaseEnv,
21439
22281
  getStatus,
21440
22282
  getServeInfo,
21441
22283
  getPackageVersion,
21442
22284
  getNotificationsPath,
21443
22285
  getManifestPath,
21444
22286
  getManifestMachine,
22287
+ getLocalMachineTopology,
21445
22288
  getLocalMachineId,
21446
22289
  getDefaultNotificationConfig,
21447
22290
  getDefaultManifest,
@@ -21458,16 +22301,20 @@ export {
21458
22301
  ensureParentDir,
21459
22302
  ensureDataDir,
21460
22303
  dispatchNotificationEvent,
22304
+ discoverMachineTopology,
21461
22305
  diffMachines,
21462
22306
  diffClaudeCli,
21463
22307
  diffApps,
21464
22308
  detectCurrentMachineManifest,
21465
22309
  createMcpServer,
21466
22310
  countRuns,
22311
+ closeDb,
22312
+ checkMachineCompatibility,
21467
22313
  buildTailscaleInstallPlan,
21468
22314
  buildSyncPlan,
21469
22315
  buildSshCommand,
21470
22316
  buildSetupPlan,
22317
+ buildServer,
21471
22318
  buildClaudeInstallPlan,
21472
22319
  buildCertPlan,
21473
22320
  buildBackupPlan,
@@ -21475,6 +22322,14 @@ export {
21475
22322
  addNotificationChannel,
21476
22323
  addDomainMapping,
21477
22324
  SqliteAdapter,
22325
+ STORAGE_TABLES,
22326
+ STORAGE_MODE_ENV,
22327
+ STORAGE_DATABASE_ENV,
21478
22328
  MACHINE_MCP_TOOL_NAMES,
22329
+ MACHINES_STORAGE_TABLES,
22330
+ MACHINES_STORAGE_MODE_FALLBACK_ENV,
22331
+ MACHINES_STORAGE_MODE_ENV,
22332
+ MACHINES_STORAGE_FALLBACK_ENV,
22333
+ MACHINES_STORAGE_ENV,
21479
22334
  CROSSREFS_KEY
21480
22335
  };