@hasna/machines 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11085,9 +11085,31 @@ function detectCurrentMachineManifest() {
11085
11085
  };
11086
11086
  }
11087
11087
  // src/topology.ts
11088
- import { existsSync as existsSync3 } from "fs";
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: null });
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: null });
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 = 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";
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: existsSync3(getManifestPath()),
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,54 +11459,36 @@ function getLocalMachineTopology(options = {}) {
11280
11459
  };
11281
11460
  }
11282
11461
  // src/remote.ts
11283
- import { spawnSync as spawnSync3 } from "child_process";
11462
+ import { spawnSync as spawnSync2 } from "child_process";
11284
11463
  import { hostname as hostname4 } from "os";
11285
11464
 
11286
11465
  // 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;
11466
+ function shellQuote(value) {
11467
+ return `'${value.replace(/'/g, "'\\''")}'`;
11301
11468
  }
11302
- function resolveSshTarget(machineId) {
11303
- const machine = getManifestMachine(machineId);
11304
- if (!machine) {
11305
- throw new Error(`Machine not found in manifest: ${machineId}`);
11469
+ function resolveSshTarget(machineId, options = {}) {
11470
+ const resolved = resolveMachineRoute(machineId, options);
11471
+ if (!resolved.ok || !resolved.target) {
11472
+ throw new Error(`Machine route not found: ${machineId}`);
11306
11473
  }
11307
- const current = detectCurrentMachineManifest();
11308
- if (machine.id === current.id) {
11309
- return {
11310
- machineId,
11311
- target: "localhost",
11312
- route: "local"
11313
- };
11474
+ if (resolved.route !== "local" && resolved.route !== "lan" && resolved.route !== "tailscale" && resolved.route !== "ssh") {
11475
+ throw new Error(`Machine route is not SSH-capable: ${machineId}`);
11314
11476
  }
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
11477
  return {
11319
- machineId,
11320
- target: route === "lan" ? lanTarget : tailscaleTarget,
11321
- route
11478
+ machineId: resolved.machine_id ?? machineId,
11479
+ target: resolved.target,
11480
+ route: resolved.route,
11481
+ confidence: resolved.confidence,
11482
+ warnings: resolved.warnings
11322
11483
  };
11323
11484
  }
11324
- function buildSshCommand(machineId, remoteCommand) {
11325
- const resolved = resolveSshTarget(machineId);
11326
- return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
11485
+ function buildSshCommand(machineId, remoteCommand, options = {}) {
11486
+ const resolved = resolveSshTarget(machineId, options);
11487
+ return remoteCommand ? `ssh ${resolved.target} ${shellQuote(remoteCommand)}` : `ssh ${resolved.target}`;
11327
11488
  }
11328
11489
 
11329
11490
  // src/remote.ts
11330
- function shellQuote(value) {
11491
+ function shellQuote2(value) {
11331
11492
  return `'${value.replace(/'/g, "'\\''")}'`;
11332
11493
  }
11333
11494
  function machineIsLocal(machineId, localMachineId) {
@@ -11343,15 +11504,16 @@ function resolveMachineCommand(machineId, command, localMachineId = getLocalMach
11343
11504
  shellCommand: buildSshCommand(machineId, command)
11344
11505
  };
11345
11506
  } catch (error) {
11346
- if (String(error.message ?? error).includes("Machine not found in manifest")) {
11347
- return { source: "ssh", shellCommand: `ssh ${shellQuote(machineId)} ${shellQuote(command)}` };
11507
+ const message = String(error.message ?? error);
11508
+ if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
11509
+ return { source: "ssh", shellCommand: `ssh ${shellQuote2(machineId)} ${shellQuote2(command)}` };
11348
11510
  }
11349
11511
  throw error;
11350
11512
  }
11351
11513
  }
11352
11514
  function runMachineCommand(machineId, command) {
11353
11515
  const resolved = resolveMachineCommand(machineId, command);
11354
- const result = spawnSync3("bash", ["-c", resolved.shellCommand], {
11516
+ const result = spawnSync2("bash", ["-c", resolved.shellCommand], {
11355
11517
  encoding: "utf8",
11356
11518
  env: process.env
11357
11519
  });
@@ -11364,24 +11526,6 @@ function runMachineCommand(machineId, command) {
11364
11526
  };
11365
11527
  }
11366
11528
 
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
11529
  // src/compatibility.ts
11386
11530
  var DEFAULT_COMMANDS = [
11387
11531
  { command: "bun", required: true },
@@ -11390,7 +11534,7 @@ var DEFAULT_COMMANDS = [
11390
11534
  function defaultPackages() {
11391
11535
  return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
11392
11536
  }
11393
- function shellQuote2(value) {
11537
+ function shellQuote3(value) {
11394
11538
  return `'${value.replace(/'/g, "'\\''")}'`;
11395
11539
  }
11396
11540
  function commandId(value) {
@@ -11441,7 +11585,7 @@ function defaultRunner2(machineId, command) {
11441
11585
  return runMachineCommand(machineId, command);
11442
11586
  }
11443
11587
  function inspectCommand(machineId, spec, runner) {
11444
- const command = shellQuote2(spec.command);
11588
+ const command = shellQuote3(spec.command);
11445
11589
  const versionArgs = spec.versionArgs ?? "--version";
11446
11590
  const script = [
11447
11591
  `cmd=${command}`,
@@ -11470,7 +11614,7 @@ function fieldCommand(field) {
11470
11614
  }
11471
11615
  function inspectWorkspace(machineId, spec, runner) {
11472
11616
  const script = [
11473
- `path=${shellQuote2(spec.path)}`,
11617
+ `path=${shellQuote3(spec.path)}`,
11474
11618
  'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
11475
11619
  'pkg="$path/package.json"',
11476
11620
  'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
@@ -11608,6 +11752,17 @@ function checkMachineCompatibility(options = {}) {
11608
11752
  fail: checks.filter((check) => check.status === "fail").length
11609
11753
  };
11610
11754
  return {
11755
+ schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
11756
+ package: {
11757
+ name: MACHINES_PACKAGE_NAME,
11758
+ version: getPackageVersion()
11759
+ },
11760
+ capabilities: {
11761
+ topology: true,
11762
+ compatibility: true,
11763
+ route_resolution: true,
11764
+ cli_json_fallback: true
11765
+ },
11611
11766
  ok: summary.fail === 0,
11612
11767
  machine_id: machineId,
11613
11768
  source: checks[0]?.source ?? "local",
@@ -11715,7 +11870,7 @@ function getAppManager(machine, app) {
11715
11870
  return "winget";
11716
11871
  return "apt";
11717
11872
  }
11718
- function shellQuote3(value) {
11873
+ function shellQuote4(value) {
11719
11874
  return `'${value.replace(/'/g, `'\\''`)}'`;
11720
11875
  }
11721
11876
  function buildAppCommand(machine, app) {
@@ -11736,7 +11891,7 @@ function buildAppCommand(machine, app) {
11736
11891
  return `sudo apt-get install -y ${packageName}`;
11737
11892
  }
11738
11893
  function buildAppProbeCommand(machine, app) {
11739
- const packageName = shellQuote3(getPackageName(app));
11894
+ const packageName = shellQuote4(getPackageName(app));
11740
11895
  const manager = getAppManager(machine, app);
11741
11896
  if (manager === "custom") {
11742
11897
  return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
@@ -12272,7 +12427,7 @@ var notificationConfigSchema = exports_external.object({
12272
12427
  function sortChannels(channels) {
12273
12428
  return [...channels].sort((left, right) => left.id.localeCompare(right.id));
12274
12429
  }
12275
- function shellQuote4(value) {
12430
+ function shellQuote5(value) {
12276
12431
  return `'${value.replace(/'/g, `'\\''`)}'`;
12277
12432
  }
12278
12433
  function hasCommand2(binary) {
@@ -12319,7 +12474,7 @@ ${message}
12319
12474
  };
12320
12475
  }
12321
12476
  if (hasCommand2("mail")) {
12322
- const command = `printf %s ${shellQuote4(message)} | mail -s ${shellQuote4(subject)} ${shellQuote4(channel.target)}`;
12477
+ const command = `printf %s ${shellQuote5(message)} | mail -s ${shellQuote5(subject)} ${shellQuote5(channel.target)}`;
12323
12478
  const result = Bun.spawnSync(["bash", "-lc", command], {
12324
12479
  stdout: "pipe",
12325
12480
  stderr: "pipe",
@@ -12502,7 +12657,7 @@ async function testNotificationChannel(channelId, event = "manual.test", message
12502
12657
  };
12503
12658
  }
12504
12659
  // src/commands/ports.ts
12505
- import { spawnSync as spawnSync4 } from "child_process";
12660
+ import { spawnSync as spawnSync3 } from "child_process";
12506
12661
  function parseSsOutput(output) {
12507
12662
  return output.trim().split(`
12508
12663
  `).map((line) => line.trim()).filter(Boolean).map((line) => {
@@ -12544,7 +12699,7 @@ function listPorts(machineId) {
12544
12699
  const isLocal = targetMachineId === getLocalMachineId();
12545
12700
  const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
12546
12701
  const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
12547
- const result = spawnSync4("bash", ["-lc", command], { encoding: "utf8" });
12702
+ const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
12548
12703
  if (result.status !== 0) {
12549
12704
  throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
12550
12705
  }
@@ -22072,6 +22227,7 @@ var MACHINE_MCP_TOOL_NAMES = [
22072
22227
  "machines_install_claude_diff",
22073
22228
  "machines_install_claude_preview",
22074
22229
  "machines_install_claude_apply",
22230
+ "machines_route_resolve",
22075
22231
  "machines_ssh_resolve",
22076
22232
  "machines_ports",
22077
22233
  "machines_backup_preview",
@@ -22182,8 +22338,11 @@ function createMcpServer(version2) {
22182
22338
  }));
22183
22339
  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) }] }));
22184
22340
  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) }] }));
22341
+ 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 }) => ({
22342
+ content: [{ type: "text", text: JSON.stringify(resolveMachineRoute(machine_id, { includeTailscale: include_tailscale !== false }), null, 2) }]
22343
+ }));
22185
22344
  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 }) => ({
22186
- content: [{ type: "text", text: JSON.stringify({ resolved: resolveSshTarget(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
22345
+ content: [{ type: "text", text: JSON.stringify({ resolved: resolveMachineRoute(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
22187
22346
  }));
22188
22347
  server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
22189
22348
  content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
@@ -22248,6 +22407,7 @@ export {
22248
22407
  runAppsInstall,
22249
22408
  resolveTables,
22250
22409
  resolveSshTarget,
22410
+ resolveMachineRoute,
22251
22411
  renderDomainMapping,
22252
22412
  renderDashboardHtml,
22253
22413
  removeNotificationChannel,
@@ -22331,5 +22491,7 @@ export {
22331
22491
  MACHINES_STORAGE_MODE_ENV,
22332
22492
  MACHINES_STORAGE_FALLBACK_ENV,
22333
22493
  MACHINES_STORAGE_ENV,
22494
+ MACHINES_PACKAGE_NAME,
22495
+ MACHINES_CONSUMER_CONTRACT_VERSION,
22334
22496
  CROSSREFS_KEY
22335
22497
  };