@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/LICENSE +2 -1
- package/README.md +87 -0
- package/dist/cli/index.js +1697 -170
- package/dist/commands/heal-daemon.d.ts +36 -0
- package/dist/commands/heal-daemon.d.ts.map +1 -0
- package/dist/commands/heal.d.ts +122 -0
- package/dist/commands/heal.d.ts.map +1 -0
- package/dist/compatibility.d.ts +55 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/db.d.ts +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1020 -185
- package/dist/mcp/http.d.ts +12 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +925 -66
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/pg-migrations.d.ts +7 -0
- package/dist/pg-migrations.d.ts.map +1 -0
- package/dist/remote-storage.d.ts +10 -0
- package/dist/remote-storage.d.ts.map +1 -0
- package/dist/storage-sync.d.ts +58 -0
- package/dist/storage-sync.d.ts.map +1 -0
- package/dist/storage.d.ts +5 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +557 -0
- package/dist/topology.d.ts +55 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +8 -2
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
|
-
|
|
6519
|
-
|
|
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
|
|
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
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
11039
|
-
import { join as
|
|
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
|
|
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 =
|
|
11052
|
-
const keyPath =
|
|
11836
|
+
const certPath = join4(certDir(), `${primary}.pem`);
|
|
11837
|
+
const keyPath = join4(certDir(), `${primary}-key.pem`);
|
|
11053
11838
|
const steps = [];
|
|
11054
|
-
if (
|
|
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
|
|
11116
|
-
import { join as
|
|
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
|
|
11903
|
+
return join5(getDataDir(), "dns.json");
|
|
11119
11904
|
}
|
|
11120
11905
|
function readMappings() {
|
|
11121
11906
|
const path = getDnsPath();
|
|
11122
|
-
if (!
|
|
11907
|
+
if (!existsSync5(path))
|
|
11123
11908
|
return [];
|
|
11124
|
-
return JSON.parse(
|
|
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 ${
|
|
11936
|
+
tls ${join5(getDataDir(), "certs", `${entry.domain}.pem`)} ${join5(getDataDir(), "certs", `${entry.domain}-key.pem`)}
|
|
11152
11937
|
}`,
|
|
11153
|
-
certPath:
|
|
11154
|
-
keyPath:
|
|
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
|
|
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
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
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
|
|
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
|
|
12255
|
+
function shellQuote3(value) {
|
|
11471
12256
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11472
12257
|
}
|
|
11473
|
-
function
|
|
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 (
|
|
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 (
|
|
11517
|
-
const command = `printf %s ${
|
|
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 (!
|
|
12395
|
+
if (!existsSync6(path)) {
|
|
11611
12396
|
return getDefaultNotificationConfig();
|
|
11612
12397
|
}
|
|
11613
|
-
return notificationConfigSchema.parse(JSON.parse(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
12250
|
-
const targetExists =
|
|
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: () =>
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
15959
|
+
...normalizeParams2(params)
|
|
15193
15960
|
});
|
|
15194
15961
|
}
|
|
15195
15962
|
function _boolean(Class2, params) {
|
|
15196
15963
|
return new Class2({
|
|
15197
15964
|
type: "boolean",
|
|
15198
|
-
...
|
|
15965
|
+
...normalizeParams2(params)
|
|
15199
15966
|
});
|
|
15200
15967
|
}
|
|
15201
15968
|
function _null2(Class2, params) {
|
|
15202
15969
|
return new Class2({
|
|
15203
15970
|
type: "null",
|
|
15204
|
-
...
|
|
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
|
-
...
|
|
15982
|
+
...normalizeParams2(params)
|
|
15216
15983
|
});
|
|
15217
15984
|
}
|
|
15218
15985
|
function _lt(value, params) {
|
|
15219
15986
|
return new $ZodCheckLessThan({
|
|
15220
15987
|
check: "less_than",
|
|
15221
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
16114
|
+
...normalizeParams2(params)
|
|
15348
16115
|
});
|
|
15349
16116
|
}
|
|
15350
16117
|
function _custom(Class2, fn, _params) {
|
|
15351
|
-
const norm =
|
|
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
|
-
...
|
|
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
|
};
|