@hasna/machines 0.0.15 → 0.0.17
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/README.md +17 -5
- package/dist/cli/index.js +417 -227
- package/dist/commands/ssh.d.ts +6 -3
- package/dist/commands/ssh.d.ts.map +1 -1
- package/dist/compatibility.d.ts +4 -0
- package/dist/compatibility.d.ts.map +1 -1
- package/dist/consumer.d.ts +10 -0
- package/dist/consumer.d.ts.map +1 -0
- package/dist/consumer.js +4983 -0
- package/dist/index.js +256 -77
- package/dist/mcp/index.js +400 -227
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/remote.d.ts +5 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/topology.d.ts +45 -1
- package/dist/topology.d.ts.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -11085,9 +11085,31 @@ function detectCurrentMachineManifest() {
|
|
|
11085
11085
|
};
|
|
11086
11086
|
}
|
|
11087
11087
|
// src/topology.ts
|
|
11088
|
-
import { existsSync as
|
|
11088
|
+
import { existsSync as existsSync4 } from "fs";
|
|
11089
11089
|
import { arch as arch2, hostname as hostname3, platform as platform2, userInfo as userInfo2 } from "os";
|
|
11090
11090
|
import { spawnSync } from "child_process";
|
|
11091
|
+
|
|
11092
|
+
// src/version.ts
|
|
11093
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
11094
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
11095
|
+
import { fileURLToPath } from "url";
|
|
11096
|
+
function getPackageVersion() {
|
|
11097
|
+
try {
|
|
11098
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
11099
|
+
const candidates = [join2(here, "..", "package.json"), join2(here, "..", "..", "package.json")];
|
|
11100
|
+
const pkgPath = candidates.find((candidate) => existsSync3(candidate));
|
|
11101
|
+
if (!pkgPath) {
|
|
11102
|
+
return "0.0.0";
|
|
11103
|
+
}
|
|
11104
|
+
return JSON.parse(readFileSync2(pkgPath, "utf8")).version || "0.0.0";
|
|
11105
|
+
} catch {
|
|
11106
|
+
return "0.0.0";
|
|
11107
|
+
}
|
|
11108
|
+
}
|
|
11109
|
+
|
|
11110
|
+
// src/topology.ts
|
|
11111
|
+
var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
|
|
11112
|
+
var MACHINES_PACKAGE_NAME = "@hasna/machines";
|
|
11091
11113
|
function normalizePlatform2(value = platform2()) {
|
|
11092
11114
|
const normalized = value.toLowerCase();
|
|
11093
11115
|
if (normalized === "darwin" || normalized === "macos")
|
|
@@ -11169,16 +11191,26 @@ function findTailscalePeer(machine, machineId, peers) {
|
|
|
11169
11191
|
}
|
|
11170
11192
|
return peers.get(machineId) ?? null;
|
|
11171
11193
|
}
|
|
11194
|
+
function envReachableHosts() {
|
|
11195
|
+
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
11196
|
+
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
11197
|
+
}
|
|
11198
|
+
function manifestHostReachable(target) {
|
|
11199
|
+
const overrides = envReachableHosts();
|
|
11200
|
+
if (overrides.size === 0)
|
|
11201
|
+
return null;
|
|
11202
|
+
return overrides.has(target);
|
|
11203
|
+
}
|
|
11172
11204
|
function routeHints(input) {
|
|
11173
11205
|
const hints = [];
|
|
11174
11206
|
if (input.machineId === input.localMachineId) {
|
|
11175
11207
|
hints.push({ kind: "local", target: "localhost", reachable: true });
|
|
11176
11208
|
}
|
|
11177
11209
|
if (input.manifest?.sshAddress) {
|
|
11178
|
-
hints.push({ kind: "ssh", target: input.manifest.sshAddress, reachable:
|
|
11210
|
+
hints.push({ kind: "ssh", target: input.manifest.sshAddress, reachable: manifestHostReachable(input.manifest.sshAddress) });
|
|
11179
11211
|
}
|
|
11180
11212
|
if (input.manifest?.hostname) {
|
|
11181
|
-
hints.push({ kind: "lan", target: input.manifest.hostname, reachable:
|
|
11213
|
+
hints.push({ kind: "lan", target: input.manifest.hostname, reachable: manifestHostReachable(input.manifest.hostname) });
|
|
11182
11214
|
}
|
|
11183
11215
|
const tailscaleTarget = input.manifest?.tailscaleName ?? input.peer?.DNSName ?? input.peer?.TailscaleIPs?.[0];
|
|
11184
11216
|
if (tailscaleTarget) {
|
|
@@ -11186,6 +11218,28 @@ function routeHints(input) {
|
|
|
11186
11218
|
}
|
|
11187
11219
|
return hints;
|
|
11188
11220
|
}
|
|
11221
|
+
function routeRank(hint) {
|
|
11222
|
+
if (hint.kind === "local")
|
|
11223
|
+
return 0;
|
|
11224
|
+
if (hint.reachable === true && hint.kind === "ssh")
|
|
11225
|
+
return 1;
|
|
11226
|
+
if (hint.reachable === true && hint.kind === "lan")
|
|
11227
|
+
return 2;
|
|
11228
|
+
if (hint.reachable === true && hint.kind === "tailscale")
|
|
11229
|
+
return 3;
|
|
11230
|
+
if (hint.reachable === false)
|
|
11231
|
+
return 8;
|
|
11232
|
+
if (hint.kind === "ssh")
|
|
11233
|
+
return 4;
|
|
11234
|
+
if (hint.kind === "lan")
|
|
11235
|
+
return 5;
|
|
11236
|
+
if (hint.kind === "tailscale")
|
|
11237
|
+
return 6;
|
|
11238
|
+
return 9;
|
|
11239
|
+
}
|
|
11240
|
+
function selectRouteHint(hints) {
|
|
11241
|
+
return [...hints].sort((left, right) => routeRank(left) - routeRank(right))[0] ?? null;
|
|
11242
|
+
}
|
|
11189
11243
|
function buildEntry(input) {
|
|
11190
11244
|
const manifest = input.manifest;
|
|
11191
11245
|
const peer = input.peer;
|
|
@@ -11195,8 +11249,8 @@ function buildEntry(input) {
|
|
|
11195
11249
|
manifest,
|
|
11196
11250
|
peer
|
|
11197
11251
|
});
|
|
11198
|
-
const selectedRoute =
|
|
11199
|
-
const route = selectedRoute?.kind === "ssh" ? "
|
|
11252
|
+
const selectedRoute = selectRouteHint(hints);
|
|
11253
|
+
const route = selectedRoute?.kind === "ssh" ? "ssh" : selectedRoute?.kind ?? "unknown";
|
|
11200
11254
|
return {
|
|
11201
11255
|
machine_id: input.machineId,
|
|
11202
11256
|
hostname: manifest?.hostname ?? peer?.HostName ?? null,
|
|
@@ -11251,15 +11305,140 @@ function discoverMachineTopology(options = {}) {
|
|
|
11251
11305
|
});
|
|
11252
11306
|
});
|
|
11253
11307
|
return {
|
|
11308
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11309
|
+
package: {
|
|
11310
|
+
name: MACHINES_PACKAGE_NAME,
|
|
11311
|
+
version: getPackageVersion()
|
|
11312
|
+
},
|
|
11313
|
+
capabilities: {
|
|
11314
|
+
topology: true,
|
|
11315
|
+
compatibility: true,
|
|
11316
|
+
route_resolution: true,
|
|
11317
|
+
cli_json_fallback: true
|
|
11318
|
+
},
|
|
11254
11319
|
generated_at: now.toISOString(),
|
|
11255
11320
|
local_machine_id: localMachineId,
|
|
11256
11321
|
local_hostname: hostname3(),
|
|
11257
11322
|
current_platform: normalizePlatform2(),
|
|
11258
|
-
manifest_path_known:
|
|
11323
|
+
manifest_path_known: existsSync4(getManifestPath()),
|
|
11259
11324
|
machines,
|
|
11260
11325
|
warnings
|
|
11261
11326
|
};
|
|
11262
11327
|
}
|
|
11328
|
+
function normalizeMachineAlias(value) {
|
|
11329
|
+
return value.trim().replace(/\.$/, "").toLowerCase();
|
|
11330
|
+
}
|
|
11331
|
+
function routeTargetMatches(machine, requested) {
|
|
11332
|
+
const normalized = normalizeMachineAlias(requested);
|
|
11333
|
+
const values = [
|
|
11334
|
+
machine.ssh.address,
|
|
11335
|
+
machine.ssh.command_target,
|
|
11336
|
+
machine.tailscale.dns_name,
|
|
11337
|
+
machine.tailscale.dns_name?.split(".")[0],
|
|
11338
|
+
...machine.tailscale.ips,
|
|
11339
|
+
...machine.route_hints.map((hint) => hint.target),
|
|
11340
|
+
...machine.route_hints.map((hint) => hint.target.split("@").pop() ?? hint.target)
|
|
11341
|
+
].filter((value) => Boolean(value));
|
|
11342
|
+
return values.some((value) => normalizeMachineAlias(value) === normalized);
|
|
11343
|
+
}
|
|
11344
|
+
function findRouteMachine(topology, requestedMachineId) {
|
|
11345
|
+
const requested = normalizeMachineAlias(requestedMachineId);
|
|
11346
|
+
if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname3()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
|
|
11347
|
+
return {
|
|
11348
|
+
machine: topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? null,
|
|
11349
|
+
matchedBy: "local_alias"
|
|
11350
|
+
};
|
|
11351
|
+
}
|
|
11352
|
+
const machineIdMatch = topology.machines.find((machine) => normalizeMachineAlias(machine.machine_id) === requested);
|
|
11353
|
+
if (machineIdMatch)
|
|
11354
|
+
return { machine: machineIdMatch, matchedBy: "machine_id" };
|
|
11355
|
+
const hostnameMatch = topology.machines.find((machine) => machine.hostname && normalizeMachineAlias(machine.hostname) === requested);
|
|
11356
|
+
if (hostnameMatch)
|
|
11357
|
+
return { machine: hostnameMatch, matchedBy: "hostname" };
|
|
11358
|
+
const tailscaleMatch = topology.machines.find((machine) => {
|
|
11359
|
+
if (!machine.tailscale.dns_name)
|
|
11360
|
+
return false;
|
|
11361
|
+
const dns = normalizeMachineAlias(machine.tailscale.dns_name);
|
|
11362
|
+
return dns === requested || dns.split(".")[0] === requested;
|
|
11363
|
+
});
|
|
11364
|
+
if (tailscaleMatch)
|
|
11365
|
+
return { machine: tailscaleMatch, matchedBy: "tailscale" };
|
|
11366
|
+
const routeMatch = topology.machines.find((machine) => routeTargetMatches(machine, requestedMachineId));
|
|
11367
|
+
if (routeMatch)
|
|
11368
|
+
return { machine: routeMatch, matchedBy: "route_target" };
|
|
11369
|
+
return { machine: null, matchedBy: null };
|
|
11370
|
+
}
|
|
11371
|
+
function routeConfidence(input) {
|
|
11372
|
+
if (input.matchedBy === "local_alias")
|
|
11373
|
+
return "exact";
|
|
11374
|
+
if (input.hint?.kind === "local")
|
|
11375
|
+
return "exact";
|
|
11376
|
+
if (input.hint?.reachable === true)
|
|
11377
|
+
return "high";
|
|
11378
|
+
if (input.machine.manifest_declared && (input.hint?.kind === "ssh" || input.hint?.kind === "lan"))
|
|
11379
|
+
return "medium";
|
|
11380
|
+
if (input.hint)
|
|
11381
|
+
return "low";
|
|
11382
|
+
return "none";
|
|
11383
|
+
}
|
|
11384
|
+
function resolveMachineRoute(machineId, options = {}) {
|
|
11385
|
+
const topology = options.topology ?? discoverMachineTopology(options);
|
|
11386
|
+
const warnings = [...topology.warnings];
|
|
11387
|
+
const { machine, matchedBy } = findRouteMachine(topology, machineId);
|
|
11388
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
11389
|
+
if (!machine) {
|
|
11390
|
+
warnings.push(`machine_not_found:${machineId}`);
|
|
11391
|
+
return {
|
|
11392
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11393
|
+
package: { name: MACHINES_PACKAGE_NAME, version: getPackageVersion() },
|
|
11394
|
+
ok: false,
|
|
11395
|
+
machine_id: null,
|
|
11396
|
+
requested_machine_id: machineId,
|
|
11397
|
+
generated_at: generatedAt,
|
|
11398
|
+
route: "unknown",
|
|
11399
|
+
source: "unknown",
|
|
11400
|
+
target: null,
|
|
11401
|
+
command_target: null,
|
|
11402
|
+
confidence: "none",
|
|
11403
|
+
local: false,
|
|
11404
|
+
evidence: {
|
|
11405
|
+
topology: true,
|
|
11406
|
+
matched_by: null,
|
|
11407
|
+
manifest_declared: null,
|
|
11408
|
+
heartbeat_status: null,
|
|
11409
|
+
tailscale_online: null,
|
|
11410
|
+
selected_hint: null
|
|
11411
|
+
},
|
|
11412
|
+
warnings
|
|
11413
|
+
};
|
|
11414
|
+
}
|
|
11415
|
+
const selectedHint = selectRouteHint(machine.route_hints);
|
|
11416
|
+
const route = selectedHint?.kind ?? machine.ssh.route ?? "unknown";
|
|
11417
|
+
const local = route === "local" || machine.machine_id === topology.local_machine_id;
|
|
11418
|
+
return {
|
|
11419
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11420
|
+
package: topology.package,
|
|
11421
|
+
ok: Boolean(selectedHint?.target),
|
|
11422
|
+
machine_id: machine.machine_id,
|
|
11423
|
+
requested_machine_id: machineId,
|
|
11424
|
+
generated_at: generatedAt,
|
|
11425
|
+
route,
|
|
11426
|
+
source: route,
|
|
11427
|
+
target: selectedHint?.target ?? null,
|
|
11428
|
+
command_target: selectedHint?.target ?? null,
|
|
11429
|
+
confidence: routeConfidence({ machine, hint: selectedHint, matchedBy }),
|
|
11430
|
+
local,
|
|
11431
|
+
evidence: {
|
|
11432
|
+
topology: true,
|
|
11433
|
+
matched_by: matchedBy,
|
|
11434
|
+
manifest_declared: machine.manifest_declared,
|
|
11435
|
+
heartbeat_status: machine.heartbeat_status,
|
|
11436
|
+
tailscale_online: machine.tailscale.online,
|
|
11437
|
+
selected_hint: selectedHint
|
|
11438
|
+
},
|
|
11439
|
+
warnings
|
|
11440
|
+
};
|
|
11441
|
+
}
|
|
11263
11442
|
function getLocalMachineTopology(options = {}) {
|
|
11264
11443
|
const topology = discoverMachineTopology(options);
|
|
11265
11444
|
return topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? {
|
|
@@ -11280,88 +11459,70 @@ function getLocalMachineTopology(options = {}) {
|
|
|
11280
11459
|
};
|
|
11281
11460
|
}
|
|
11282
11461
|
// src/remote.ts
|
|
11283
|
-
import { spawnSync as
|
|
11462
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
11463
|
+
import { hostname as hostname4 } from "os";
|
|
11284
11464
|
|
|
11285
11465
|
// src/commands/ssh.ts
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
}
|
|
11291
|
-
function isReachable(host) {
|
|
11292
|
-
const overrides = envReachableHosts();
|
|
11293
|
-
if (overrides.size > 0) {
|
|
11294
|
-
return overrides.has(host);
|
|
11466
|
+
function resolveSshTarget(machineId, options = {}) {
|
|
11467
|
+
const resolved = resolveMachineRoute(machineId, options);
|
|
11468
|
+
if (!resolved.ok || !resolved.target) {
|
|
11469
|
+
throw new Error(`Machine route not found: ${machineId}`);
|
|
11295
11470
|
}
|
|
11296
|
-
|
|
11297
|
-
|
|
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}`);
|
|
11471
|
+
if (resolved.route !== "local" && resolved.route !== "lan" && resolved.route !== "tailscale" && resolved.route !== "ssh") {
|
|
11472
|
+
throw new Error(`Machine route is not SSH-capable: ${machineId}`);
|
|
11305
11473
|
}
|
|
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
11474
|
return {
|
|
11318
|
-
machineId,
|
|
11319
|
-
target:
|
|
11320
|
-
route
|
|
11475
|
+
machineId: resolved.machine_id ?? machineId,
|
|
11476
|
+
target: resolved.target,
|
|
11477
|
+
route: resolved.route,
|
|
11478
|
+
confidence: resolved.confidence,
|
|
11479
|
+
warnings: resolved.warnings
|
|
11321
11480
|
};
|
|
11322
11481
|
}
|
|
11323
|
-
function buildSshCommand(machineId, remoteCommand) {
|
|
11324
|
-
const resolved = resolveSshTarget(machineId);
|
|
11482
|
+
function buildSshCommand(machineId, remoteCommand, options = {}) {
|
|
11483
|
+
const resolved = resolveSshTarget(machineId, options);
|
|
11325
11484
|
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
11326
11485
|
}
|
|
11327
11486
|
|
|
11328
11487
|
// src/remote.ts
|
|
11488
|
+
function shellQuote(value) {
|
|
11489
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11490
|
+
}
|
|
11491
|
+
function machineIsLocal(machineId, localMachineId) {
|
|
11492
|
+
return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
|
|
11493
|
+
}
|
|
11494
|
+
function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
|
|
11495
|
+
if (machineIsLocal(machineId, localMachineId)) {
|
|
11496
|
+
return { source: "local", shellCommand: command };
|
|
11497
|
+
}
|
|
11498
|
+
try {
|
|
11499
|
+
return {
|
|
11500
|
+
source: resolveSshTarget(machineId).route,
|
|
11501
|
+
shellCommand: buildSshCommand(machineId, command)
|
|
11502
|
+
};
|
|
11503
|
+
} catch (error) {
|
|
11504
|
+
const message = String(error.message ?? error);
|
|
11505
|
+
if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
|
|
11506
|
+
return { source: "ssh", shellCommand: `ssh ${shellQuote(machineId)} ${shellQuote(command)}` };
|
|
11507
|
+
}
|
|
11508
|
+
throw error;
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11329
11511
|
function runMachineCommand(machineId, command) {
|
|
11330
|
-
const
|
|
11331
|
-
const
|
|
11332
|
-
const route = isLocal ? "local" : resolveSshTarget(machineId).route;
|
|
11333
|
-
const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
|
|
11334
|
-
const result = spawnSync3("bash", ["-c", shellCommand], {
|
|
11512
|
+
const resolved = resolveMachineCommand(machineId, command);
|
|
11513
|
+
const result = spawnSync2("bash", ["-c", resolved.shellCommand], {
|
|
11335
11514
|
encoding: "utf8",
|
|
11336
11515
|
env: process.env
|
|
11337
11516
|
});
|
|
11338
11517
|
return {
|
|
11339
11518
|
machineId,
|
|
11340
|
-
source:
|
|
11519
|
+
source: resolved.source,
|
|
11341
11520
|
stdout: result.stdout || "",
|
|
11342
11521
|
stderr: result.stderr || "",
|
|
11343
11522
|
exitCode: result.status ?? 1
|
|
11344
11523
|
};
|
|
11345
11524
|
}
|
|
11346
11525
|
|
|
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
11526
|
// src/compatibility.ts
|
|
11366
11527
|
var DEFAULT_COMMANDS = [
|
|
11367
11528
|
{ command: "bun", required: true },
|
|
@@ -11370,7 +11531,7 @@ var DEFAULT_COMMANDS = [
|
|
|
11370
11531
|
function defaultPackages() {
|
|
11371
11532
|
return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
|
|
11372
11533
|
}
|
|
11373
|
-
function
|
|
11534
|
+
function shellQuote2(value) {
|
|
11374
11535
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11375
11536
|
}
|
|
11376
11537
|
function commandId(value) {
|
|
@@ -11421,7 +11582,7 @@ function defaultRunner2(machineId, command) {
|
|
|
11421
11582
|
return runMachineCommand(machineId, command);
|
|
11422
11583
|
}
|
|
11423
11584
|
function inspectCommand(machineId, spec, runner) {
|
|
11424
|
-
const command =
|
|
11585
|
+
const command = shellQuote2(spec.command);
|
|
11425
11586
|
const versionArgs = spec.versionArgs ?? "--version";
|
|
11426
11587
|
const script = [
|
|
11427
11588
|
`cmd=${command}`,
|
|
@@ -11450,7 +11611,7 @@ function fieldCommand(field) {
|
|
|
11450
11611
|
}
|
|
11451
11612
|
function inspectWorkspace(machineId, spec, runner) {
|
|
11452
11613
|
const script = [
|
|
11453
|
-
`path=${
|
|
11614
|
+
`path=${shellQuote2(spec.path)}`,
|
|
11454
11615
|
'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
|
|
11455
11616
|
'pkg="$path/package.json"',
|
|
11456
11617
|
'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
|
|
@@ -11588,6 +11749,17 @@ function checkMachineCompatibility(options = {}) {
|
|
|
11588
11749
|
fail: checks.filter((check) => check.status === "fail").length
|
|
11589
11750
|
};
|
|
11590
11751
|
return {
|
|
11752
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11753
|
+
package: {
|
|
11754
|
+
name: MACHINES_PACKAGE_NAME,
|
|
11755
|
+
version: getPackageVersion()
|
|
11756
|
+
},
|
|
11757
|
+
capabilities: {
|
|
11758
|
+
topology: true,
|
|
11759
|
+
compatibility: true,
|
|
11760
|
+
route_resolution: true,
|
|
11761
|
+
cli_json_fallback: true
|
|
11762
|
+
},
|
|
11591
11763
|
ok: summary.fail === 0,
|
|
11592
11764
|
machine_id: machineId,
|
|
11593
11765
|
source: checks[0]?.source ?? "local",
|
|
@@ -11695,7 +11867,7 @@ function getAppManager(machine, app) {
|
|
|
11695
11867
|
return "winget";
|
|
11696
11868
|
return "apt";
|
|
11697
11869
|
}
|
|
11698
|
-
function
|
|
11870
|
+
function shellQuote3(value) {
|
|
11699
11871
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11700
11872
|
}
|
|
11701
11873
|
function buildAppCommand(machine, app) {
|
|
@@ -11716,7 +11888,7 @@ function buildAppCommand(machine, app) {
|
|
|
11716
11888
|
return `sudo apt-get install -y ${packageName}`;
|
|
11717
11889
|
}
|
|
11718
11890
|
function buildAppProbeCommand(machine, app) {
|
|
11719
|
-
const packageName =
|
|
11891
|
+
const packageName = shellQuote3(getPackageName(app));
|
|
11720
11892
|
const manager = getAppManager(machine, app);
|
|
11721
11893
|
if (manager === "custom") {
|
|
11722
11894
|
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
@@ -12252,7 +12424,7 @@ var notificationConfigSchema = exports_external.object({
|
|
|
12252
12424
|
function sortChannels(channels) {
|
|
12253
12425
|
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
12254
12426
|
}
|
|
12255
|
-
function
|
|
12427
|
+
function shellQuote4(value) {
|
|
12256
12428
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12257
12429
|
}
|
|
12258
12430
|
function hasCommand2(binary) {
|
|
@@ -12299,7 +12471,7 @@ ${message}
|
|
|
12299
12471
|
};
|
|
12300
12472
|
}
|
|
12301
12473
|
if (hasCommand2("mail")) {
|
|
12302
|
-
const command = `printf %s ${
|
|
12474
|
+
const command = `printf %s ${shellQuote4(message)} | mail -s ${shellQuote4(subject)} ${shellQuote4(channel.target)}`;
|
|
12303
12475
|
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
12304
12476
|
stdout: "pipe",
|
|
12305
12477
|
stderr: "pipe",
|
|
@@ -12482,7 +12654,7 @@ async function testNotificationChannel(channelId, event = "manual.test", message
|
|
|
12482
12654
|
};
|
|
12483
12655
|
}
|
|
12484
12656
|
// src/commands/ports.ts
|
|
12485
|
-
import { spawnSync as
|
|
12657
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
12486
12658
|
function parseSsOutput(output) {
|
|
12487
12659
|
return output.trim().split(`
|
|
12488
12660
|
`).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
@@ -12524,7 +12696,7 @@ function listPorts(machineId) {
|
|
|
12524
12696
|
const isLocal = targetMachineId === getLocalMachineId();
|
|
12525
12697
|
const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
|
|
12526
12698
|
const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
|
|
12527
|
-
const result =
|
|
12699
|
+
const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
|
|
12528
12700
|
if (result.status !== 0) {
|
|
12529
12701
|
throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
|
|
12530
12702
|
}
|
|
@@ -13839,7 +14011,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]
|
|
|
13839
14011
|
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])$/;
|
|
13840
14012
|
var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
13841
14013
|
var base64url = /^[A-Za-z0-9_-]*$/;
|
|
13842
|
-
var
|
|
14014
|
+
var hostname5 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
|
|
13843
14015
|
var e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
|
|
13844
14016
|
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])))`;
|
|
13845
14017
|
var date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
|
|
@@ -14451,7 +14623,7 @@ var $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
|
|
|
14451
14623
|
code: "invalid_format",
|
|
14452
14624
|
format: "url",
|
|
14453
14625
|
note: "Invalid hostname",
|
|
14454
|
-
pattern:
|
|
14626
|
+
pattern: hostname5.source,
|
|
14455
14627
|
input: payload.value,
|
|
14456
14628
|
inst,
|
|
14457
14629
|
continue: !def.abort
|
|
@@ -22052,6 +22224,7 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
22052
22224
|
"machines_install_claude_diff",
|
|
22053
22225
|
"machines_install_claude_preview",
|
|
22054
22226
|
"machines_install_claude_apply",
|
|
22227
|
+
"machines_route_resolve",
|
|
22055
22228
|
"machines_ssh_resolve",
|
|
22056
22229
|
"machines_ports",
|
|
22057
22230
|
"machines_backup_preview",
|
|
@@ -22162,8 +22335,11 @@ function createMcpServer(version2) {
|
|
|
22162
22335
|
}));
|
|
22163
22336
|
server.tool("machines_install_tailscale_preview", "Preview Tailscale install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildTailscaleInstallPlan(machine_id), null, 2) }] }));
|
|
22164
22337
|
server.tool("machines_install_tailscale_apply", "Execute Tailscale install steps 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(runTailscaleInstall(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
22338
|
+
server.tool("machines_route_resolve", "Resolve the best route for a machine using manifest, heartbeat, SSH, LAN, and Tailscale topology.", { machine_id: exports_external.string().describe("Machine identifier"), include_tailscale: exports_external.boolean().optional().describe("Whether to probe tailscale status --json") }, async ({ machine_id, include_tailscale }) => ({
|
|
22339
|
+
content: [{ type: "text", text: JSON.stringify(resolveMachineRoute(machine_id, { includeTailscale: include_tailscale !== false }), null, 2) }]
|
|
22340
|
+
}));
|
|
22165
22341
|
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", { machine_id: exports_external.string().describe("Machine identifier"), remote_command: exports_external.string().optional().describe("Optional remote command") }, async ({ machine_id, remote_command }) => ({
|
|
22166
|
-
content: [{ type: "text", text: JSON.stringify({ resolved:
|
|
22342
|
+
content: [{ type: "text", text: JSON.stringify({ resolved: resolveMachineRoute(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
22167
22343
|
}));
|
|
22168
22344
|
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
22169
22345
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
@@ -22228,6 +22404,7 @@ export {
|
|
|
22228
22404
|
runAppsInstall,
|
|
22229
22405
|
resolveTables,
|
|
22230
22406
|
resolveSshTarget,
|
|
22407
|
+
resolveMachineRoute,
|
|
22231
22408
|
renderDomainMapping,
|
|
22232
22409
|
renderDashboardHtml,
|
|
22233
22410
|
removeNotificationChannel,
|
|
@@ -22311,5 +22488,7 @@ export {
|
|
|
22311
22488
|
MACHINES_STORAGE_MODE_ENV,
|
|
22312
22489
|
MACHINES_STORAGE_FALLBACK_ENV,
|
|
22313
22490
|
MACHINES_STORAGE_ENV,
|
|
22491
|
+
MACHINES_PACKAGE_NAME,
|
|
22492
|
+
MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
22314
22493
|
CROSSREFS_KEY
|
|
22315
22494
|
};
|