@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/LICENSE +2 -1
- package/README.md +87 -0
- package/dist/cli/index.js +1726 -179
- 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 +1040 -185
- package/dist/mcp/http.d.ts +12 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +954 -75
- 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/remote.d.ts +5 -1
- package/dist/remote.d.ts.map +1 -1
- 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/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- 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,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
|
|
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
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
11039
|
-
import { join as
|
|
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
|
|
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 =
|
|
11052
|
-
const keyPath =
|
|
11856
|
+
const certPath = join4(certDir(), `${primary}.pem`);
|
|
11857
|
+
const keyPath = join4(certDir(), `${primary}-key.pem`);
|
|
11053
11858
|
const steps = [];
|
|
11054
|
-
if (
|
|
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
|
|
11116
|
-
import { join as
|
|
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
|
|
11923
|
+
return join5(getDataDir(), "dns.json");
|
|
11119
11924
|
}
|
|
11120
11925
|
function readMappings() {
|
|
11121
11926
|
const path = getDnsPath();
|
|
11122
|
-
if (!
|
|
11927
|
+
if (!existsSync5(path))
|
|
11123
11928
|
return [];
|
|
11124
|
-
return JSON.parse(
|
|
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 ${
|
|
11956
|
+
tls ${join5(getDataDir(), "certs", `${entry.domain}.pem`)} ${join5(getDataDir(), "certs", `${entry.domain}-key.pem`)}
|
|
11152
11957
|
}`,
|
|
11153
|
-
certPath:
|
|
11154
|
-
keyPath:
|
|
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
|
|
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
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
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
|
|
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
|
|
12275
|
+
function shellQuote4(value) {
|
|
11471
12276
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11472
12277
|
}
|
|
11473
|
-
function
|
|
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 (
|
|
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 (
|
|
11517
|
-
const command = `printf %s ${
|
|
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 (!
|
|
12415
|
+
if (!existsSync6(path)) {
|
|
11611
12416
|
return getDefaultNotificationConfig();
|
|
11612
12417
|
}
|
|
11613
|
-
return notificationConfigSchema.parse(JSON.parse(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
12250
|
-
const targetExists =
|
|
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: () =>
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
15979
|
+
...normalizeParams2(params)
|
|
15193
15980
|
});
|
|
15194
15981
|
}
|
|
15195
15982
|
function _boolean(Class2, params) {
|
|
15196
15983
|
return new Class2({
|
|
15197
15984
|
type: "boolean",
|
|
15198
|
-
...
|
|
15985
|
+
...normalizeParams2(params)
|
|
15199
15986
|
});
|
|
15200
15987
|
}
|
|
15201
15988
|
function _null2(Class2, params) {
|
|
15202
15989
|
return new Class2({
|
|
15203
15990
|
type: "null",
|
|
15204
|
-
...
|
|
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
|
-
...
|
|
16002
|
+
...normalizeParams2(params)
|
|
15216
16003
|
});
|
|
15217
16004
|
}
|
|
15218
16005
|
function _lt(value, params) {
|
|
15219
16006
|
return new $ZodCheckLessThan({
|
|
15220
16007
|
check: "less_than",
|
|
15221
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
16134
|
+
...normalizeParams2(params)
|
|
15348
16135
|
});
|
|
15349
16136
|
}
|
|
15350
16137
|
function _custom(Class2, fn, _params) {
|
|
15351
|
-
const norm =
|
|
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
|
-
...
|
|
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
|
};
|