@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/mcp/index.js
CHANGED
|
@@ -4218,6 +4218,7 @@ function detectCurrentMachineManifest() {
|
|
|
4218
4218
|
|
|
4219
4219
|
// src/remote.ts
|
|
4220
4220
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4221
|
+
import { hostname as hostname4 } from "os";
|
|
4221
4222
|
|
|
4222
4223
|
// src/db.ts
|
|
4223
4224
|
import { Database } from "bun:sqlite";
|
|
@@ -4318,62 +4319,397 @@ function recordSyncRun(machineId, status, actions) {
|
|
|
4318
4319
|
VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(actions), now, now);
|
|
4319
4320
|
}
|
|
4320
4321
|
|
|
4321
|
-
// src/
|
|
4322
|
+
// src/topology.ts
|
|
4323
|
+
import { existsSync as existsSync4 } from "fs";
|
|
4324
|
+
import { arch as arch2, hostname as hostname3, platform as platform2, userInfo as userInfo2 } from "os";
|
|
4322
4325
|
import { spawnSync } from "child_process";
|
|
4326
|
+
var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
|
|
4327
|
+
var MACHINES_PACKAGE_NAME = "@hasna/machines";
|
|
4328
|
+
function normalizePlatform2(value = platform2()) {
|
|
4329
|
+
const normalized = value.toLowerCase();
|
|
4330
|
+
if (normalized === "darwin" || normalized === "macos")
|
|
4331
|
+
return "macos";
|
|
4332
|
+
if (normalized === "win32" || normalized === "windows")
|
|
4333
|
+
return "windows";
|
|
4334
|
+
if (normalized === "linux")
|
|
4335
|
+
return "linux";
|
|
4336
|
+
return value;
|
|
4337
|
+
}
|
|
4338
|
+
function defaultRunner(command) {
|
|
4339
|
+
const result = spawnSync("bash", ["-c", command], {
|
|
4340
|
+
encoding: "utf8",
|
|
4341
|
+
env: process.env
|
|
4342
|
+
});
|
|
4343
|
+
return {
|
|
4344
|
+
stdout: result.stdout || "",
|
|
4345
|
+
stderr: result.stderr || "",
|
|
4346
|
+
exitCode: result.status ?? 1
|
|
4347
|
+
};
|
|
4348
|
+
}
|
|
4349
|
+
function hasCommand(command, runner) {
|
|
4350
|
+
return runner(`command -v ${command} >/dev/null 2>&1`).exitCode === 0;
|
|
4351
|
+
}
|
|
4352
|
+
function parseTailscaleStatus(raw) {
|
|
4353
|
+
try {
|
|
4354
|
+
const parsed = JSON.parse(raw);
|
|
4355
|
+
if (!parsed || typeof parsed !== "object")
|
|
4356
|
+
return null;
|
|
4357
|
+
return parsed;
|
|
4358
|
+
} catch {
|
|
4359
|
+
return null;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
function loadTailscalePeers(runner, warnings) {
|
|
4363
|
+
const peers = new Map;
|
|
4364
|
+
if (!hasCommand("tailscale", runner)) {
|
|
4365
|
+
warnings.push("tailscale_not_available");
|
|
4366
|
+
return peers;
|
|
4367
|
+
}
|
|
4368
|
+
const result = runner("tailscale status --json");
|
|
4369
|
+
if (result.exitCode !== 0) {
|
|
4370
|
+
warnings.push(`tailscale_status_failed:${result.stderr.trim() || result.exitCode}`);
|
|
4371
|
+
return peers;
|
|
4372
|
+
}
|
|
4373
|
+
const status = parseTailscaleStatus(result.stdout);
|
|
4374
|
+
if (!status) {
|
|
4375
|
+
warnings.push("tailscale_status_invalid_json");
|
|
4376
|
+
return peers;
|
|
4377
|
+
}
|
|
4378
|
+
const addPeer = (peer) => {
|
|
4379
|
+
if (!peer)
|
|
4380
|
+
return;
|
|
4381
|
+
const id = peer.HostName || peer.DNSName?.split(".")[0];
|
|
4382
|
+
if (id)
|
|
4383
|
+
peers.set(id, peer);
|
|
4384
|
+
};
|
|
4385
|
+
addPeer(status.Self);
|
|
4386
|
+
for (const peer of Object.values(status.Peer ?? {}))
|
|
4387
|
+
addPeer(peer);
|
|
4388
|
+
return peers;
|
|
4389
|
+
}
|
|
4390
|
+
function machineKeys(machine) {
|
|
4391
|
+
return [
|
|
4392
|
+
machine.id,
|
|
4393
|
+
machine.hostname,
|
|
4394
|
+
machine.tailscaleName?.split(".")[0],
|
|
4395
|
+
machine.tailscaleName,
|
|
4396
|
+
machine.sshAddress?.split("@").pop()
|
|
4397
|
+
].filter((value) => Boolean(value));
|
|
4398
|
+
}
|
|
4399
|
+
function findTailscalePeer(machine, machineId, peers) {
|
|
4400
|
+
if (machine) {
|
|
4401
|
+
for (const key of machineKeys(machine)) {
|
|
4402
|
+
const peer = peers.get(key) ?? peers.get(key.replace(/\.$/, ""));
|
|
4403
|
+
if (peer)
|
|
4404
|
+
return peer;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
return peers.get(machineId) ?? null;
|
|
4408
|
+
}
|
|
4323
4409
|
function envReachableHosts() {
|
|
4324
4410
|
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
4325
4411
|
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
4326
4412
|
}
|
|
4327
|
-
function
|
|
4413
|
+
function manifestHostReachable(target) {
|
|
4328
4414
|
const overrides = envReachableHosts();
|
|
4329
|
-
if (overrides.size
|
|
4330
|
-
return
|
|
4415
|
+
if (overrides.size === 0)
|
|
4416
|
+
return null;
|
|
4417
|
+
return overrides.has(target);
|
|
4418
|
+
}
|
|
4419
|
+
function routeHints(input) {
|
|
4420
|
+
const hints = [];
|
|
4421
|
+
if (input.machineId === input.localMachineId) {
|
|
4422
|
+
hints.push({ kind: "local", target: "localhost", reachable: true });
|
|
4423
|
+
}
|
|
4424
|
+
if (input.manifest?.sshAddress) {
|
|
4425
|
+
hints.push({ kind: "ssh", target: input.manifest.sshAddress, reachable: manifestHostReachable(input.manifest.sshAddress) });
|
|
4426
|
+
}
|
|
4427
|
+
if (input.manifest?.hostname) {
|
|
4428
|
+
hints.push({ kind: "lan", target: input.manifest.hostname, reachable: manifestHostReachable(input.manifest.hostname) });
|
|
4331
4429
|
}
|
|
4332
|
-
const
|
|
4333
|
-
|
|
4430
|
+
const tailscaleTarget = input.manifest?.tailscaleName ?? input.peer?.DNSName ?? input.peer?.TailscaleIPs?.[0];
|
|
4431
|
+
if (tailscaleTarget) {
|
|
4432
|
+
hints.push({ kind: "tailscale", target: tailscaleTarget.replace(/\.$/, ""), reachable: input.peer?.Online ?? null });
|
|
4433
|
+
}
|
|
4434
|
+
return hints;
|
|
4435
|
+
}
|
|
4436
|
+
function routeRank(hint) {
|
|
4437
|
+
if (hint.kind === "local")
|
|
4438
|
+
return 0;
|
|
4439
|
+
if (hint.reachable === true && hint.kind === "ssh")
|
|
4440
|
+
return 1;
|
|
4441
|
+
if (hint.reachable === true && hint.kind === "lan")
|
|
4442
|
+
return 2;
|
|
4443
|
+
if (hint.reachable === true && hint.kind === "tailscale")
|
|
4444
|
+
return 3;
|
|
4445
|
+
if (hint.reachable === false)
|
|
4446
|
+
return 8;
|
|
4447
|
+
if (hint.kind === "ssh")
|
|
4448
|
+
return 4;
|
|
4449
|
+
if (hint.kind === "lan")
|
|
4450
|
+
return 5;
|
|
4451
|
+
if (hint.kind === "tailscale")
|
|
4452
|
+
return 6;
|
|
4453
|
+
return 9;
|
|
4454
|
+
}
|
|
4455
|
+
function selectRouteHint(hints) {
|
|
4456
|
+
return [...hints].sort((left, right) => routeRank(left) - routeRank(right))[0] ?? null;
|
|
4457
|
+
}
|
|
4458
|
+
function buildEntry(input) {
|
|
4459
|
+
const manifest = input.manifest;
|
|
4460
|
+
const peer = input.peer;
|
|
4461
|
+
const hints = routeHints({
|
|
4462
|
+
machineId: input.machineId,
|
|
4463
|
+
localMachineId: input.localMachineId,
|
|
4464
|
+
manifest,
|
|
4465
|
+
peer
|
|
4334
4466
|
});
|
|
4335
|
-
|
|
4467
|
+
const selectedRoute = selectRouteHint(hints);
|
|
4468
|
+
const route = selectedRoute?.kind === "ssh" ? "ssh" : selectedRoute?.kind ?? "unknown";
|
|
4469
|
+
return {
|
|
4470
|
+
machine_id: input.machineId,
|
|
4471
|
+
hostname: manifest?.hostname ?? peer?.HostName ?? null,
|
|
4472
|
+
platform: manifest?.platform ?? (peer?.OS ? normalizePlatform2(peer.OS) : null),
|
|
4473
|
+
os: peer?.OS ?? null,
|
|
4474
|
+
user: typeof manifest?.metadata?.user === "string" ? manifest.metadata.user : null,
|
|
4475
|
+
workspace_path: manifest?.workspacePath ?? null,
|
|
4476
|
+
manifest_declared: Boolean(manifest),
|
|
4477
|
+
heartbeat_status: input.heartbeat?.status ?? "unknown",
|
|
4478
|
+
last_heartbeat_at: input.heartbeat?.updated_at ?? null,
|
|
4479
|
+
tailscale: {
|
|
4480
|
+
dns_name: manifest?.tailscaleName ?? peer?.DNSName?.replace(/\.$/, "") ?? null,
|
|
4481
|
+
ips: peer?.TailscaleIPs ?? [],
|
|
4482
|
+
online: peer?.Online ?? null,
|
|
4483
|
+
active: peer?.Active ?? null,
|
|
4484
|
+
last_seen: peer?.LastSeen ?? null
|
|
4485
|
+
},
|
|
4486
|
+
ssh: {
|
|
4487
|
+
address: manifest?.sshAddress ?? null,
|
|
4488
|
+
route,
|
|
4489
|
+
command_target: selectedRoute?.target ?? null
|
|
4490
|
+
},
|
|
4491
|
+
route_hints: hints,
|
|
4492
|
+
tags: manifest?.tags ?? [],
|
|
4493
|
+
metadata: manifest?.metadata ?? {}
|
|
4494
|
+
};
|
|
4336
4495
|
}
|
|
4337
|
-
function
|
|
4338
|
-
const
|
|
4339
|
-
|
|
4340
|
-
|
|
4496
|
+
function discoverMachineTopology(options = {}) {
|
|
4497
|
+
const now = options.now ?? new Date;
|
|
4498
|
+
const runner = options.runner ?? defaultRunner;
|
|
4499
|
+
const warnings = [];
|
|
4500
|
+
const manifest = readManifest();
|
|
4501
|
+
const heartbeats = listHeartbeats();
|
|
4502
|
+
const heartbeatByMachine = new Map(heartbeats.map((heartbeat) => [heartbeat.machine_id, heartbeat]));
|
|
4503
|
+
const localMachineId = getLocalMachineId();
|
|
4504
|
+
const peers = options.includeTailscale === false ? new Map : loadTailscalePeers(runner, warnings);
|
|
4505
|
+
const machineIds = new Set([
|
|
4506
|
+
localMachineId,
|
|
4507
|
+
...manifest.machines.map((machine) => machine.id),
|
|
4508
|
+
...heartbeats.map((heartbeat) => heartbeat.machine_id),
|
|
4509
|
+
...peers.keys()
|
|
4510
|
+
]);
|
|
4511
|
+
const manifestById = new Map(manifest.machines.map((machine) => [machine.id, machine]));
|
|
4512
|
+
const machines = [...machineIds].sort().map((machineId) => {
|
|
4513
|
+
const manifestMachine = manifestById.get(machineId);
|
|
4514
|
+
return buildEntry({
|
|
4515
|
+
machineId,
|
|
4516
|
+
localMachineId,
|
|
4517
|
+
manifest: manifestMachine,
|
|
4518
|
+
peer: findTailscalePeer(manifestMachine ?? null, machineId, peers),
|
|
4519
|
+
heartbeat: heartbeatByMachine.get(machineId)
|
|
4520
|
+
});
|
|
4521
|
+
});
|
|
4522
|
+
return {
|
|
4523
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
4524
|
+
package: {
|
|
4525
|
+
name: MACHINES_PACKAGE_NAME,
|
|
4526
|
+
version: getPackageVersion()
|
|
4527
|
+
},
|
|
4528
|
+
capabilities: {
|
|
4529
|
+
topology: true,
|
|
4530
|
+
compatibility: true,
|
|
4531
|
+
route_resolution: true,
|
|
4532
|
+
cli_json_fallback: true
|
|
4533
|
+
},
|
|
4534
|
+
generated_at: now.toISOString(),
|
|
4535
|
+
local_machine_id: localMachineId,
|
|
4536
|
+
local_hostname: hostname3(),
|
|
4537
|
+
current_platform: normalizePlatform2(),
|
|
4538
|
+
manifest_path_known: existsSync4(getManifestPath()),
|
|
4539
|
+
machines,
|
|
4540
|
+
warnings
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
function normalizeMachineAlias(value) {
|
|
4544
|
+
return value.trim().replace(/\.$/, "").toLowerCase();
|
|
4545
|
+
}
|
|
4546
|
+
function routeTargetMatches(machine, requested) {
|
|
4547
|
+
const normalized = normalizeMachineAlias(requested);
|
|
4548
|
+
const values = [
|
|
4549
|
+
machine.ssh.address,
|
|
4550
|
+
machine.ssh.command_target,
|
|
4551
|
+
machine.tailscale.dns_name,
|
|
4552
|
+
machine.tailscale.dns_name?.split(".")[0],
|
|
4553
|
+
...machine.tailscale.ips,
|
|
4554
|
+
...machine.route_hints.map((hint) => hint.target),
|
|
4555
|
+
...machine.route_hints.map((hint) => hint.target.split("@").pop() ?? hint.target)
|
|
4556
|
+
].filter((value) => Boolean(value));
|
|
4557
|
+
return values.some((value) => normalizeMachineAlias(value) === normalized);
|
|
4558
|
+
}
|
|
4559
|
+
function findRouteMachine(topology, requestedMachineId) {
|
|
4560
|
+
const requested = normalizeMachineAlias(requestedMachineId);
|
|
4561
|
+
if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname3()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
|
|
4562
|
+
return {
|
|
4563
|
+
machine: topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? null,
|
|
4564
|
+
matchedBy: "local_alias"
|
|
4565
|
+
};
|
|
4341
4566
|
}
|
|
4342
|
-
const
|
|
4343
|
-
if (
|
|
4567
|
+
const machineIdMatch = topology.machines.find((machine) => normalizeMachineAlias(machine.machine_id) === requested);
|
|
4568
|
+
if (machineIdMatch)
|
|
4569
|
+
return { machine: machineIdMatch, matchedBy: "machine_id" };
|
|
4570
|
+
const hostnameMatch = topology.machines.find((machine) => machine.hostname && normalizeMachineAlias(machine.hostname) === requested);
|
|
4571
|
+
if (hostnameMatch)
|
|
4572
|
+
return { machine: hostnameMatch, matchedBy: "hostname" };
|
|
4573
|
+
const tailscaleMatch = topology.machines.find((machine) => {
|
|
4574
|
+
if (!machine.tailscale.dns_name)
|
|
4575
|
+
return false;
|
|
4576
|
+
const dns = normalizeMachineAlias(machine.tailscale.dns_name);
|
|
4577
|
+
return dns === requested || dns.split(".")[0] === requested;
|
|
4578
|
+
});
|
|
4579
|
+
if (tailscaleMatch)
|
|
4580
|
+
return { machine: tailscaleMatch, matchedBy: "tailscale" };
|
|
4581
|
+
const routeMatch = topology.machines.find((machine) => routeTargetMatches(machine, requestedMachineId));
|
|
4582
|
+
if (routeMatch)
|
|
4583
|
+
return { machine: routeMatch, matchedBy: "route_target" };
|
|
4584
|
+
return { machine: null, matchedBy: null };
|
|
4585
|
+
}
|
|
4586
|
+
function routeConfidence(input) {
|
|
4587
|
+
if (input.matchedBy === "local_alias")
|
|
4588
|
+
return "exact";
|
|
4589
|
+
if (input.hint?.kind === "local")
|
|
4590
|
+
return "exact";
|
|
4591
|
+
if (input.hint?.reachable === true)
|
|
4592
|
+
return "high";
|
|
4593
|
+
if (input.machine.manifest_declared && (input.hint?.kind === "ssh" || input.hint?.kind === "lan"))
|
|
4594
|
+
return "medium";
|
|
4595
|
+
if (input.hint)
|
|
4596
|
+
return "low";
|
|
4597
|
+
return "none";
|
|
4598
|
+
}
|
|
4599
|
+
function resolveMachineRoute(machineId, options = {}) {
|
|
4600
|
+
const topology = options.topology ?? discoverMachineTopology(options);
|
|
4601
|
+
const warnings = [...topology.warnings];
|
|
4602
|
+
const { machine, matchedBy } = findRouteMachine(topology, machineId);
|
|
4603
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
4604
|
+
if (!machine) {
|
|
4605
|
+
warnings.push(`machine_not_found:${machineId}`);
|
|
4344
4606
|
return {
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4607
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
4608
|
+
package: { name: MACHINES_PACKAGE_NAME, version: getPackageVersion() },
|
|
4609
|
+
ok: false,
|
|
4610
|
+
machine_id: null,
|
|
4611
|
+
requested_machine_id: machineId,
|
|
4612
|
+
generated_at: generatedAt,
|
|
4613
|
+
route: "unknown",
|
|
4614
|
+
source: "unknown",
|
|
4615
|
+
target: null,
|
|
4616
|
+
command_target: null,
|
|
4617
|
+
confidence: "none",
|
|
4618
|
+
local: false,
|
|
4619
|
+
evidence: {
|
|
4620
|
+
topology: true,
|
|
4621
|
+
matched_by: null,
|
|
4622
|
+
manifest_declared: null,
|
|
4623
|
+
heartbeat_status: null,
|
|
4624
|
+
tailscale_online: null,
|
|
4625
|
+
selected_hint: null
|
|
4626
|
+
},
|
|
4627
|
+
warnings
|
|
4348
4628
|
};
|
|
4349
4629
|
}
|
|
4350
|
-
const
|
|
4351
|
-
const
|
|
4352
|
-
const
|
|
4630
|
+
const selectedHint = selectRouteHint(machine.route_hints);
|
|
4631
|
+
const route = selectedHint?.kind ?? machine.ssh.route ?? "unknown";
|
|
4632
|
+
const local = route === "local" || machine.machine_id === topology.local_machine_id;
|
|
4353
4633
|
return {
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4634
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
4635
|
+
package: topology.package,
|
|
4636
|
+
ok: Boolean(selectedHint?.target),
|
|
4637
|
+
machine_id: machine.machine_id,
|
|
4638
|
+
requested_machine_id: machineId,
|
|
4639
|
+
generated_at: generatedAt,
|
|
4640
|
+
route,
|
|
4641
|
+
source: route,
|
|
4642
|
+
target: selectedHint?.target ?? null,
|
|
4643
|
+
command_target: selectedHint?.target ?? null,
|
|
4644
|
+
confidence: routeConfidence({ machine, hint: selectedHint, matchedBy }),
|
|
4645
|
+
local,
|
|
4646
|
+
evidence: {
|
|
4647
|
+
topology: true,
|
|
4648
|
+
matched_by: matchedBy,
|
|
4649
|
+
manifest_declared: machine.manifest_declared,
|
|
4650
|
+
heartbeat_status: machine.heartbeat_status,
|
|
4651
|
+
tailscale_online: machine.tailscale.online,
|
|
4652
|
+
selected_hint: selectedHint
|
|
4653
|
+
},
|
|
4654
|
+
warnings
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
|
|
4658
|
+
// src/commands/ssh.ts
|
|
4659
|
+
function resolveSshTarget(machineId, options = {}) {
|
|
4660
|
+
const resolved = resolveMachineRoute(machineId, options);
|
|
4661
|
+
if (!resolved.ok || !resolved.target) {
|
|
4662
|
+
throw new Error(`Machine route not found: ${machineId}`);
|
|
4663
|
+
}
|
|
4664
|
+
if (resolved.route !== "local" && resolved.route !== "lan" && resolved.route !== "tailscale" && resolved.route !== "ssh") {
|
|
4665
|
+
throw new Error(`Machine route is not SSH-capable: ${machineId}`);
|
|
4666
|
+
}
|
|
4667
|
+
return {
|
|
4668
|
+
machineId: resolved.machine_id ?? machineId,
|
|
4669
|
+
target: resolved.target,
|
|
4670
|
+
route: resolved.route,
|
|
4671
|
+
confidence: resolved.confidence,
|
|
4672
|
+
warnings: resolved.warnings
|
|
4357
4673
|
};
|
|
4358
4674
|
}
|
|
4359
|
-
function buildSshCommand(machineId, remoteCommand) {
|
|
4360
|
-
const resolved = resolveSshTarget(machineId);
|
|
4675
|
+
function buildSshCommand(machineId, remoteCommand, options = {}) {
|
|
4676
|
+
const resolved = resolveSshTarget(machineId, options);
|
|
4361
4677
|
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
4362
4678
|
}
|
|
4363
4679
|
|
|
4364
4680
|
// src/remote.ts
|
|
4681
|
+
function shellQuote(value) {
|
|
4682
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
4683
|
+
}
|
|
4684
|
+
function machineIsLocal(machineId, localMachineId) {
|
|
4685
|
+
return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
|
|
4686
|
+
}
|
|
4687
|
+
function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
|
|
4688
|
+
if (machineIsLocal(machineId, localMachineId)) {
|
|
4689
|
+
return { source: "local", shellCommand: command };
|
|
4690
|
+
}
|
|
4691
|
+
try {
|
|
4692
|
+
return {
|
|
4693
|
+
source: resolveSshTarget(machineId).route,
|
|
4694
|
+
shellCommand: buildSshCommand(machineId, command)
|
|
4695
|
+
};
|
|
4696
|
+
} catch (error) {
|
|
4697
|
+
const message = String(error.message ?? error);
|
|
4698
|
+
if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
|
|
4699
|
+
return { source: "ssh", shellCommand: `ssh ${shellQuote(machineId)} ${shellQuote(command)}` };
|
|
4700
|
+
}
|
|
4701
|
+
throw error;
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4365
4704
|
function runMachineCommand(machineId, command) {
|
|
4366
|
-
const
|
|
4367
|
-
const
|
|
4368
|
-
const route = isLocal ? "local" : resolveSshTarget(machineId).route;
|
|
4369
|
-
const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
|
|
4370
|
-
const result = spawnSync2("bash", ["-c", shellCommand], {
|
|
4705
|
+
const resolved = resolveMachineCommand(machineId, command);
|
|
4706
|
+
const result = spawnSync2("bash", ["-c", resolved.shellCommand], {
|
|
4371
4707
|
encoding: "utf8",
|
|
4372
4708
|
env: process.env
|
|
4373
4709
|
});
|
|
4374
4710
|
return {
|
|
4375
4711
|
machineId,
|
|
4376
|
-
source:
|
|
4712
|
+
source: resolved.source,
|
|
4377
4713
|
stdout: result.stdout || "",
|
|
4378
4714
|
stderr: result.stderr || "",
|
|
4379
4715
|
exitCode: result.status ?? 1
|
|
@@ -4393,7 +4729,7 @@ function getAppManager(machine, app) {
|
|
|
4393
4729
|
return "winget";
|
|
4394
4730
|
return "apt";
|
|
4395
4731
|
}
|
|
4396
|
-
function
|
|
4732
|
+
function shellQuote2(value) {
|
|
4397
4733
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4398
4734
|
}
|
|
4399
4735
|
function buildAppCommand(machine, app) {
|
|
@@ -4414,7 +4750,7 @@ function buildAppCommand(machine, app) {
|
|
|
4414
4750
|
return `sudo apt-get install -y ${packageName}`;
|
|
4415
4751
|
}
|
|
4416
4752
|
function buildAppProbeCommand(machine, app) {
|
|
4417
|
-
const packageName =
|
|
4753
|
+
const packageName = shellQuote2(getPackageName(app));
|
|
4418
4754
|
const manager = getAppManager(machine, app);
|
|
4419
4755
|
if (manager === "custom") {
|
|
4420
4756
|
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
@@ -4519,7 +4855,7 @@ function runAppsInstall(machineId, options = {}) {
|
|
|
4519
4855
|
}
|
|
4520
4856
|
|
|
4521
4857
|
// src/commands/cert.ts
|
|
4522
|
-
import { homedir as homedir3, platform as
|
|
4858
|
+
import { homedir as homedir3, platform as platform3 } from "os";
|
|
4523
4859
|
import { join as join4 } from "path";
|
|
4524
4860
|
function quote2(value) {
|
|
4525
4861
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -4535,7 +4871,7 @@ function buildCertPlan(domains) {
|
|
|
4535
4871
|
const certPath = join4(certDir(), `${primary}.pem`);
|
|
4536
4872
|
const keyPath = join4(certDir(), `${primary}-key.pem`);
|
|
4537
4873
|
const steps = [];
|
|
4538
|
-
if (
|
|
4874
|
+
if (platform3() === "darwin") {
|
|
4539
4875
|
steps.push({
|
|
4540
4876
|
id: "mkcert-install-macos",
|
|
4541
4877
|
title: "Install mkcert on macOS",
|
|
@@ -4597,14 +4933,14 @@ function runCertPlan(domains, options = {}) {
|
|
|
4597
4933
|
}
|
|
4598
4934
|
|
|
4599
4935
|
// src/commands/dns.ts
|
|
4600
|
-
import { existsSync as
|
|
4936
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4601
4937
|
import { join as join5 } from "path";
|
|
4602
4938
|
function getDnsPath() {
|
|
4603
4939
|
return join5(getDataDir(), "dns.json");
|
|
4604
4940
|
}
|
|
4605
4941
|
function readMappings() {
|
|
4606
4942
|
const path = getDnsPath();
|
|
4607
|
-
if (!
|
|
4943
|
+
if (!existsSync5(path))
|
|
4608
4944
|
return [];
|
|
4609
4945
|
return JSON.parse(readFileSync3(path, "utf8"));
|
|
4610
4946
|
}
|
|
@@ -4908,7 +5244,7 @@ function runTailscaleInstall(machineId, options = {}) {
|
|
|
4908
5244
|
}
|
|
4909
5245
|
|
|
4910
5246
|
// src/commands/notifications.ts
|
|
4911
|
-
import { existsSync as
|
|
5247
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
4912
5248
|
var notificationChannelSchema = exports_external.object({
|
|
4913
5249
|
id: exports_external.string(),
|
|
4914
5250
|
type: exports_external.enum(["email", "webhook", "command"]),
|
|
@@ -4924,10 +5260,10 @@ var notificationConfigSchema = exports_external.object({
|
|
|
4924
5260
|
function sortChannels(channels) {
|
|
4925
5261
|
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
4926
5262
|
}
|
|
4927
|
-
function
|
|
5263
|
+
function shellQuote3(value) {
|
|
4928
5264
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4929
5265
|
}
|
|
4930
|
-
function
|
|
5266
|
+
function hasCommand2(binary) {
|
|
4931
5267
|
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
|
|
4932
5268
|
stdout: "ignore",
|
|
4933
5269
|
stderr: "ignore",
|
|
@@ -4952,7 +5288,7 @@ Content-Type: text/plain; charset=utf-8
|
|
|
4952
5288
|
|
|
4953
5289
|
${message}
|
|
4954
5290
|
`;
|
|
4955
|
-
if (
|
|
5291
|
+
if (hasCommand2("sendmail")) {
|
|
4956
5292
|
const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
|
|
4957
5293
|
stdin: new TextEncoder().encode(body),
|
|
4958
5294
|
stdout: "pipe",
|
|
@@ -4970,8 +5306,8 @@ ${message}
|
|
|
4970
5306
|
detail: `Delivered via sendmail to ${channel.target}`
|
|
4971
5307
|
};
|
|
4972
5308
|
}
|
|
4973
|
-
if (
|
|
4974
|
-
const command = `printf %s ${
|
|
5309
|
+
if (hasCommand2("mail")) {
|
|
5310
|
+
const command = `printf %s ${shellQuote3(message)} | mail -s ${shellQuote3(subject)} ${shellQuote3(channel.target)}`;
|
|
4975
5311
|
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
4976
5312
|
stdout: "pipe",
|
|
4977
5313
|
stderr: "pipe",
|
|
@@ -5064,7 +5400,7 @@ function getDefaultNotificationConfig() {
|
|
|
5064
5400
|
};
|
|
5065
5401
|
}
|
|
5066
5402
|
function readNotificationConfig(path = getNotificationsPath()) {
|
|
5067
|
-
if (!
|
|
5403
|
+
if (!existsSync6(path)) {
|
|
5068
5404
|
return getDefaultNotificationConfig();
|
|
5069
5405
|
}
|
|
5070
5406
|
return notificationConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
|
|
@@ -5596,7 +5932,7 @@ function runSetup(machineId, options = {}) {
|
|
|
5596
5932
|
}
|
|
5597
5933
|
|
|
5598
5934
|
// src/commands/sync.ts
|
|
5599
|
-
import { existsSync as
|
|
5935
|
+
import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
|
|
5600
5936
|
function quote4(value) {
|
|
5601
5937
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5602
5938
|
}
|
|
@@ -5644,8 +5980,8 @@ function detectPackageActions(machine) {
|
|
|
5644
5980
|
}
|
|
5645
5981
|
function detectFileActions(machine) {
|
|
5646
5982
|
return (machine.files || []).map((file, index) => {
|
|
5647
|
-
const sourceExists =
|
|
5648
|
-
const targetExists =
|
|
5983
|
+
const sourceExists = existsSync7(file.source);
|
|
5984
|
+
const targetExists = existsSync7(file.target);
|
|
5649
5985
|
let status = "missing";
|
|
5650
5986
|
if (sourceExists && targetExists) {
|
|
5651
5987
|
if (file.mode === "symlink") {
|
|
@@ -5757,183 +6093,6 @@ function getAgentStatus(machineId = getLocalMachineId()) {
|
|
|
5757
6093
|
}));
|
|
5758
6094
|
}
|
|
5759
6095
|
|
|
5760
|
-
// src/topology.ts
|
|
5761
|
-
import { existsSync as existsSync7 } from "fs";
|
|
5762
|
-
import { arch as arch2, hostname as hostname3, platform as platform3, userInfo as userInfo2 } from "os";
|
|
5763
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
5764
|
-
function normalizePlatform2(value = platform3()) {
|
|
5765
|
-
const normalized = value.toLowerCase();
|
|
5766
|
-
if (normalized === "darwin" || normalized === "macos")
|
|
5767
|
-
return "macos";
|
|
5768
|
-
if (normalized === "win32" || normalized === "windows")
|
|
5769
|
-
return "windows";
|
|
5770
|
-
if (normalized === "linux")
|
|
5771
|
-
return "linux";
|
|
5772
|
-
return value;
|
|
5773
|
-
}
|
|
5774
|
-
function defaultRunner(command) {
|
|
5775
|
-
const result = spawnSync4("bash", ["-c", command], {
|
|
5776
|
-
encoding: "utf8",
|
|
5777
|
-
env: process.env
|
|
5778
|
-
});
|
|
5779
|
-
return {
|
|
5780
|
-
stdout: result.stdout || "",
|
|
5781
|
-
stderr: result.stderr || "",
|
|
5782
|
-
exitCode: result.status ?? 1
|
|
5783
|
-
};
|
|
5784
|
-
}
|
|
5785
|
-
function hasCommand2(command, runner) {
|
|
5786
|
-
return runner(`command -v ${command} >/dev/null 2>&1`).exitCode === 0;
|
|
5787
|
-
}
|
|
5788
|
-
function parseTailscaleStatus(raw) {
|
|
5789
|
-
try {
|
|
5790
|
-
const parsed = JSON.parse(raw);
|
|
5791
|
-
if (!parsed || typeof parsed !== "object")
|
|
5792
|
-
return null;
|
|
5793
|
-
return parsed;
|
|
5794
|
-
} catch {
|
|
5795
|
-
return null;
|
|
5796
|
-
}
|
|
5797
|
-
}
|
|
5798
|
-
function loadTailscalePeers(runner, warnings) {
|
|
5799
|
-
const peers = new Map;
|
|
5800
|
-
if (!hasCommand2("tailscale", runner)) {
|
|
5801
|
-
warnings.push("tailscale_not_available");
|
|
5802
|
-
return peers;
|
|
5803
|
-
}
|
|
5804
|
-
const result = runner("tailscale status --json");
|
|
5805
|
-
if (result.exitCode !== 0) {
|
|
5806
|
-
warnings.push(`tailscale_status_failed:${result.stderr.trim() || result.exitCode}`);
|
|
5807
|
-
return peers;
|
|
5808
|
-
}
|
|
5809
|
-
const status = parseTailscaleStatus(result.stdout);
|
|
5810
|
-
if (!status) {
|
|
5811
|
-
warnings.push("tailscale_status_invalid_json");
|
|
5812
|
-
return peers;
|
|
5813
|
-
}
|
|
5814
|
-
const addPeer = (peer) => {
|
|
5815
|
-
if (!peer)
|
|
5816
|
-
return;
|
|
5817
|
-
const id = peer.HostName || peer.DNSName?.split(".")[0];
|
|
5818
|
-
if (id)
|
|
5819
|
-
peers.set(id, peer);
|
|
5820
|
-
};
|
|
5821
|
-
addPeer(status.Self);
|
|
5822
|
-
for (const peer of Object.values(status.Peer ?? {}))
|
|
5823
|
-
addPeer(peer);
|
|
5824
|
-
return peers;
|
|
5825
|
-
}
|
|
5826
|
-
function machineKeys(machine) {
|
|
5827
|
-
return [
|
|
5828
|
-
machine.id,
|
|
5829
|
-
machine.hostname,
|
|
5830
|
-
machine.tailscaleName?.split(".")[0],
|
|
5831
|
-
machine.tailscaleName,
|
|
5832
|
-
machine.sshAddress?.split("@").pop()
|
|
5833
|
-
].filter((value) => Boolean(value));
|
|
5834
|
-
}
|
|
5835
|
-
function findTailscalePeer(machine, machineId, peers) {
|
|
5836
|
-
if (machine) {
|
|
5837
|
-
for (const key of machineKeys(machine)) {
|
|
5838
|
-
const peer = peers.get(key) ?? peers.get(key.replace(/\.$/, ""));
|
|
5839
|
-
if (peer)
|
|
5840
|
-
return peer;
|
|
5841
|
-
}
|
|
5842
|
-
}
|
|
5843
|
-
return peers.get(machineId) ?? null;
|
|
5844
|
-
}
|
|
5845
|
-
function routeHints(input) {
|
|
5846
|
-
const hints = [];
|
|
5847
|
-
if (input.machineId === input.localMachineId) {
|
|
5848
|
-
hints.push({ kind: "local", target: "localhost", reachable: true });
|
|
5849
|
-
}
|
|
5850
|
-
if (input.manifest?.sshAddress) {
|
|
5851
|
-
hints.push({ kind: "ssh", target: input.manifest.sshAddress, reachable: null });
|
|
5852
|
-
}
|
|
5853
|
-
if (input.manifest?.hostname) {
|
|
5854
|
-
hints.push({ kind: "lan", target: input.manifest.hostname, reachable: null });
|
|
5855
|
-
}
|
|
5856
|
-
const tailscaleTarget = input.manifest?.tailscaleName ?? input.peer?.DNSName ?? input.peer?.TailscaleIPs?.[0];
|
|
5857
|
-
if (tailscaleTarget) {
|
|
5858
|
-
hints.push({ kind: "tailscale", target: tailscaleTarget.replace(/\.$/, ""), reachable: input.peer?.Online ?? null });
|
|
5859
|
-
}
|
|
5860
|
-
return hints;
|
|
5861
|
-
}
|
|
5862
|
-
function buildEntry(input) {
|
|
5863
|
-
const manifest = input.manifest;
|
|
5864
|
-
const peer = input.peer;
|
|
5865
|
-
const hints = routeHints({
|
|
5866
|
-
machineId: input.machineId,
|
|
5867
|
-
localMachineId: input.localMachineId,
|
|
5868
|
-
manifest,
|
|
5869
|
-
peer
|
|
5870
|
-
});
|
|
5871
|
-
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");
|
|
5872
|
-
const route = selectedRoute?.kind === "ssh" ? "lan" : selectedRoute?.kind ?? "unknown";
|
|
5873
|
-
return {
|
|
5874
|
-
machine_id: input.machineId,
|
|
5875
|
-
hostname: manifest?.hostname ?? peer?.HostName ?? null,
|
|
5876
|
-
platform: manifest?.platform ?? (peer?.OS ? normalizePlatform2(peer.OS) : null),
|
|
5877
|
-
os: peer?.OS ?? null,
|
|
5878
|
-
user: typeof manifest?.metadata?.user === "string" ? manifest.metadata.user : null,
|
|
5879
|
-
workspace_path: manifest?.workspacePath ?? null,
|
|
5880
|
-
manifest_declared: Boolean(manifest),
|
|
5881
|
-
heartbeat_status: input.heartbeat?.status ?? "unknown",
|
|
5882
|
-
last_heartbeat_at: input.heartbeat?.updated_at ?? null,
|
|
5883
|
-
tailscale: {
|
|
5884
|
-
dns_name: manifest?.tailscaleName ?? peer?.DNSName?.replace(/\.$/, "") ?? null,
|
|
5885
|
-
ips: peer?.TailscaleIPs ?? [],
|
|
5886
|
-
online: peer?.Online ?? null,
|
|
5887
|
-
active: peer?.Active ?? null,
|
|
5888
|
-
last_seen: peer?.LastSeen ?? null
|
|
5889
|
-
},
|
|
5890
|
-
ssh: {
|
|
5891
|
-
address: manifest?.sshAddress ?? null,
|
|
5892
|
-
route,
|
|
5893
|
-
command_target: selectedRoute?.target ?? null
|
|
5894
|
-
},
|
|
5895
|
-
route_hints: hints,
|
|
5896
|
-
tags: manifest?.tags ?? [],
|
|
5897
|
-
metadata: manifest?.metadata ?? {}
|
|
5898
|
-
};
|
|
5899
|
-
}
|
|
5900
|
-
function discoverMachineTopology(options = {}) {
|
|
5901
|
-
const now = options.now ?? new Date;
|
|
5902
|
-
const runner = options.runner ?? defaultRunner;
|
|
5903
|
-
const warnings = [];
|
|
5904
|
-
const manifest = readManifest();
|
|
5905
|
-
const heartbeats = listHeartbeats();
|
|
5906
|
-
const heartbeatByMachine = new Map(heartbeats.map((heartbeat) => [heartbeat.machine_id, heartbeat]));
|
|
5907
|
-
const localMachineId = getLocalMachineId();
|
|
5908
|
-
const peers = options.includeTailscale === false ? new Map : loadTailscalePeers(runner, warnings);
|
|
5909
|
-
const machineIds = new Set([
|
|
5910
|
-
localMachineId,
|
|
5911
|
-
...manifest.machines.map((machine) => machine.id),
|
|
5912
|
-
...heartbeats.map((heartbeat) => heartbeat.machine_id),
|
|
5913
|
-
...peers.keys()
|
|
5914
|
-
]);
|
|
5915
|
-
const manifestById = new Map(manifest.machines.map((machine) => [machine.id, machine]));
|
|
5916
|
-
const machines = [...machineIds].sort().map((machineId) => {
|
|
5917
|
-
const manifestMachine = manifestById.get(machineId);
|
|
5918
|
-
return buildEntry({
|
|
5919
|
-
machineId,
|
|
5920
|
-
localMachineId,
|
|
5921
|
-
manifest: manifestMachine,
|
|
5922
|
-
peer: findTailscalePeer(manifestMachine ?? null, machineId, peers),
|
|
5923
|
-
heartbeat: heartbeatByMachine.get(machineId)
|
|
5924
|
-
});
|
|
5925
|
-
});
|
|
5926
|
-
return {
|
|
5927
|
-
generated_at: now.toISOString(),
|
|
5928
|
-
local_machine_id: localMachineId,
|
|
5929
|
-
local_hostname: hostname3(),
|
|
5930
|
-
current_platform: normalizePlatform2(),
|
|
5931
|
-
manifest_path_known: existsSync7(getManifestPath()),
|
|
5932
|
-
machines,
|
|
5933
|
-
warnings
|
|
5934
|
-
};
|
|
5935
|
-
}
|
|
5936
|
-
|
|
5937
6096
|
// src/compatibility.ts
|
|
5938
6097
|
var DEFAULT_COMMANDS = [
|
|
5939
6098
|
{ command: "bun", required: true },
|
|
@@ -5942,7 +6101,7 @@ var DEFAULT_COMMANDS = [
|
|
|
5942
6101
|
function defaultPackages() {
|
|
5943
6102
|
return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
|
|
5944
6103
|
}
|
|
5945
|
-
function
|
|
6104
|
+
function shellQuote4(value) {
|
|
5946
6105
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
5947
6106
|
}
|
|
5948
6107
|
function commandId(value) {
|
|
@@ -5993,7 +6152,7 @@ function defaultRunner2(machineId, command) {
|
|
|
5993
6152
|
return runMachineCommand(machineId, command);
|
|
5994
6153
|
}
|
|
5995
6154
|
function inspectCommand(machineId, spec, runner) {
|
|
5996
|
-
const command =
|
|
6155
|
+
const command = shellQuote4(spec.command);
|
|
5997
6156
|
const versionArgs = spec.versionArgs ?? "--version";
|
|
5998
6157
|
const script = [
|
|
5999
6158
|
`cmd=${command}`,
|
|
@@ -6022,7 +6181,7 @@ function fieldCommand(field) {
|
|
|
6022
6181
|
}
|
|
6023
6182
|
function inspectWorkspace(machineId, spec, runner) {
|
|
6024
6183
|
const script = [
|
|
6025
|
-
`path=${
|
|
6184
|
+
`path=${shellQuote4(spec.path)}`,
|
|
6026
6185
|
'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
|
|
6027
6186
|
'pkg="$path/package.json"',
|
|
6028
6187
|
'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
|
|
@@ -6160,6 +6319,17 @@ function checkMachineCompatibility(options = {}) {
|
|
|
6160
6319
|
fail: checks.filter((check2) => check2.status === "fail").length
|
|
6161
6320
|
};
|
|
6162
6321
|
return {
|
|
6322
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
6323
|
+
package: {
|
|
6324
|
+
name: MACHINES_PACKAGE_NAME,
|
|
6325
|
+
version: getPackageVersion()
|
|
6326
|
+
},
|
|
6327
|
+
capabilities: {
|
|
6328
|
+
topology: true,
|
|
6329
|
+
compatibility: true,
|
|
6330
|
+
route_resolution: true,
|
|
6331
|
+
cli_json_fallback: true
|
|
6332
|
+
},
|
|
6163
6333
|
ok: summary.fail === 0,
|
|
6164
6334
|
machine_id: machineId,
|
|
6165
6335
|
source: checks[0]?.source ?? "local",
|
|
@@ -6579,8 +6749,11 @@ function createMcpServer(version) {
|
|
|
6579
6749
|
}));
|
|
6580
6750
|
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) }] }));
|
|
6581
6751
|
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) }] }));
|
|
6752
|
+
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 }) => ({
|
|
6753
|
+
content: [{ type: "text", text: JSON.stringify(resolveMachineRoute(machine_id, { includeTailscale: include_tailscale !== false }), null, 2) }]
|
|
6754
|
+
}));
|
|
6582
6755
|
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 }) => ({
|
|
6583
|
-
content: [{ type: "text", text: JSON.stringify({ resolved:
|
|
6756
|
+
content: [{ type: "text", text: JSON.stringify({ resolved: resolveMachineRoute(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
6584
6757
|
}));
|
|
6585
6758
|
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
6586
6759
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|