@hasna/machines 0.0.2 → 0.0.3
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 +33 -8
- package/dist/cli/index.js +714 -144
- package/dist/cli-utils.d.ts +7 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/commands/apps.d.ts +3 -1
- package/dist/commands/apps.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/install-claude.d.ts +3 -1
- package/dist/commands/install-claude.d.ts.map +1 -1
- package/dist/commands/notifications.d.ts +5 -2
- package/dist/commands/notifications.d.ts.map +1 -1
- package/dist/commands/self-test.d.ts +3 -0
- package/dist/commands/self-test.d.ts.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +668 -315
- package/dist/mcp/index.js +2055 -1747
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/remote.d.ts +9 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20335,17 +20335,95 @@ function runBackup(bucket, prefix = "machines", options = {}) {
|
|
|
20335
20335
|
executed
|
|
20336
20336
|
};
|
|
20337
20337
|
}
|
|
20338
|
+
// src/remote.ts
|
|
20339
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
20340
|
+
|
|
20341
|
+
// src/commands/ssh.ts
|
|
20342
|
+
import { spawnSync } from "child_process";
|
|
20343
|
+
function envReachableHosts() {
|
|
20344
|
+
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
20345
|
+
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
20346
|
+
}
|
|
20347
|
+
function isReachable(host) {
|
|
20348
|
+
const overrides = envReachableHosts();
|
|
20349
|
+
if (overrides.size > 0) {
|
|
20350
|
+
return overrides.has(host);
|
|
20351
|
+
}
|
|
20352
|
+
const probe = spawnSync("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
|
|
20353
|
+
stdio: "ignore"
|
|
20354
|
+
});
|
|
20355
|
+
return probe.status === 0;
|
|
20356
|
+
}
|
|
20357
|
+
function resolveSshTarget(machineId) {
|
|
20358
|
+
const machine = getManifestMachine(machineId);
|
|
20359
|
+
if (!machine) {
|
|
20360
|
+
throw new Error(`Machine not found in manifest: ${machineId}`);
|
|
20361
|
+
}
|
|
20362
|
+
const current = detectCurrentMachineManifest();
|
|
20363
|
+
if (machine.id === current.id) {
|
|
20364
|
+
return {
|
|
20365
|
+
machineId,
|
|
20366
|
+
target: "localhost",
|
|
20367
|
+
route: "local"
|
|
20368
|
+
};
|
|
20369
|
+
}
|
|
20370
|
+
const lanTarget = machine.sshAddress || machine.hostname || machine.id;
|
|
20371
|
+
const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
|
|
20372
|
+
const route = isReachable(lanTarget) ? "lan" : "tailscale";
|
|
20373
|
+
return {
|
|
20374
|
+
machineId,
|
|
20375
|
+
target: route === "lan" ? lanTarget : tailscaleTarget,
|
|
20376
|
+
route
|
|
20377
|
+
};
|
|
20378
|
+
}
|
|
20379
|
+
function buildSshCommand(machineId, remoteCommand) {
|
|
20380
|
+
const resolved = resolveSshTarget(machineId);
|
|
20381
|
+
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
20382
|
+
}
|
|
20383
|
+
|
|
20384
|
+
// src/remote.ts
|
|
20385
|
+
function runMachineCommand(machineId, command) {
|
|
20386
|
+
const localMachineId = getLocalMachineId();
|
|
20387
|
+
const isLocal = machineId === localMachineId;
|
|
20388
|
+
const route = isLocal ? "local" : resolveSshTarget(machineId).route;
|
|
20389
|
+
const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
|
|
20390
|
+
const result = spawnSync2("bash", ["-lc", shellCommand], {
|
|
20391
|
+
encoding: "utf8",
|
|
20392
|
+
env: process.env
|
|
20393
|
+
});
|
|
20394
|
+
return {
|
|
20395
|
+
machineId,
|
|
20396
|
+
source: route,
|
|
20397
|
+
stdout: result.stdout || "",
|
|
20398
|
+
stderr: result.stderr || "",
|
|
20399
|
+
exitCode: result.status ?? 1
|
|
20400
|
+
};
|
|
20401
|
+
}
|
|
20402
|
+
|
|
20338
20403
|
// src/commands/apps.ts
|
|
20339
20404
|
function getPackageName(app) {
|
|
20340
20405
|
return app.packageName || app.name;
|
|
20341
20406
|
}
|
|
20407
|
+
function getAppManager(machine, app) {
|
|
20408
|
+
if (app.manager)
|
|
20409
|
+
return app.manager;
|
|
20410
|
+
if (machine.platform === "macos")
|
|
20411
|
+
return "brew";
|
|
20412
|
+
if (machine.platform === "windows")
|
|
20413
|
+
return "winget";
|
|
20414
|
+
return "apt";
|
|
20415
|
+
}
|
|
20416
|
+
function shellQuote(value) {
|
|
20417
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20418
|
+
}
|
|
20342
20419
|
function buildAppCommand(machine, app) {
|
|
20343
20420
|
const packageName = getPackageName(app);
|
|
20344
|
-
|
|
20421
|
+
const manager = getAppManager(machine, app);
|
|
20422
|
+
if (manager === "custom") {
|
|
20345
20423
|
return packageName;
|
|
20346
20424
|
}
|
|
20347
20425
|
if (machine.platform === "macos") {
|
|
20348
|
-
if (
|
|
20426
|
+
if (manager === "cask") {
|
|
20349
20427
|
return `brew install --cask ${packageName}`;
|
|
20350
20428
|
}
|
|
20351
20429
|
return `brew install ${packageName}`;
|
|
@@ -20355,18 +20433,48 @@ function buildAppCommand(machine, app) {
|
|
|
20355
20433
|
}
|
|
20356
20434
|
return `sudo apt-get install -y ${packageName}`;
|
|
20357
20435
|
}
|
|
20436
|
+
function buildAppProbeCommand(machine, app) {
|
|
20437
|
+
const packageName = shellQuote(getPackageName(app));
|
|
20438
|
+
const manager = getAppManager(machine, app);
|
|
20439
|
+
if (manager === "custom") {
|
|
20440
|
+
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
20441
|
+
}
|
|
20442
|
+
if (machine.platform === "macos") {
|
|
20443
|
+
if (manager === "cask") {
|
|
20444
|
+
return `if brew list --cask ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
20445
|
+
}
|
|
20446
|
+
return `if brew list --versions ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion='; brew list --versions ${packageName} | awk '{print $2}'; printf '\\n'; else printf 'installed=0\\n'; fi`;
|
|
20447
|
+
}
|
|
20448
|
+
if (machine.platform === "windows") {
|
|
20449
|
+
return `if winget list --id ${packageName} --exact >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
20450
|
+
}
|
|
20451
|
+
return `if dpkg-query -W -f='${"${Version}"}' ${packageName} >/tmp/machines-app-version 2>/dev/null; then printf 'installed=1\\nversion='; cat /tmp/machines-app-version; printf '\\n'; rm -f /tmp/machines-app-version; else printf 'installed=0\\n'; fi`;
|
|
20452
|
+
}
|
|
20358
20453
|
function buildAppSteps(machine) {
|
|
20359
20454
|
return (machine.apps || []).map((app) => ({
|
|
20360
20455
|
id: `app-${app.name}`,
|
|
20361
20456
|
title: `Install ${app.name} on ${machine.id}`,
|
|
20362
20457
|
command: buildAppCommand(machine, app),
|
|
20363
|
-
manager: app
|
|
20458
|
+
manager: getAppManager(machine, app) === "custom" ? "custom" : machine.platform === "macos" ? "brew" : machine.platform === "windows" ? "custom" : "apt",
|
|
20364
20459
|
privileged: machine.platform === "linux"
|
|
20365
20460
|
}));
|
|
20366
20461
|
}
|
|
20367
20462
|
function resolveMachine(machineId) {
|
|
20368
20463
|
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
20369
20464
|
}
|
|
20465
|
+
function parseProbeOutput(app, machine, stdout) {
|
|
20466
|
+
const lines = stdout.trim().split(`
|
|
20467
|
+
`).filter(Boolean);
|
|
20468
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
20469
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
20470
|
+
return {
|
|
20471
|
+
name: app.name,
|
|
20472
|
+
packageName: getPackageName(app),
|
|
20473
|
+
manager: getAppManager(machine, app),
|
|
20474
|
+
installed: installedLine === "installed=1",
|
|
20475
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
20476
|
+
};
|
|
20477
|
+
}
|
|
20370
20478
|
function listApps(machineId) {
|
|
20371
20479
|
const machine = resolveMachine(machineId);
|
|
20372
20480
|
return {
|
|
@@ -20383,6 +20491,26 @@ function buildAppsPlan(machineId) {
|
|
|
20383
20491
|
executed: 0
|
|
20384
20492
|
};
|
|
20385
20493
|
}
|
|
20494
|
+
function getAppsStatus(machineId) {
|
|
20495
|
+
const machine = resolveMachine(machineId);
|
|
20496
|
+
const apps = (machine.apps || []).map((app) => {
|
|
20497
|
+
const probe = runMachineCommand(machine.id, buildAppProbeCommand(machine, app));
|
|
20498
|
+
return parseProbeOutput(app, machine, probe.stdout);
|
|
20499
|
+
});
|
|
20500
|
+
return {
|
|
20501
|
+
machineId: machine.id,
|
|
20502
|
+
source: apps.length > 0 ? runMachineCommand(machine.id, "true").source : machine.id === detectCurrentMachineManifest().id ? "local" : runMachineCommand(machine.id, "true").source,
|
|
20503
|
+
apps
|
|
20504
|
+
};
|
|
20505
|
+
}
|
|
20506
|
+
function diffApps(machineId) {
|
|
20507
|
+
const status = getAppsStatus(machineId);
|
|
20508
|
+
return {
|
|
20509
|
+
...status,
|
|
20510
|
+
missing: status.apps.filter((app) => !app.installed).map((app) => app.name),
|
|
20511
|
+
installed: status.apps.filter((app) => app.installed).map((app) => app.name)
|
|
20512
|
+
};
|
|
20513
|
+
}
|
|
20386
20514
|
function runAppsInstall(machineId, options = {}) {
|
|
20387
20515
|
const plan = buildAppsPlan(machineId);
|
|
20388
20516
|
if (!options.apply)
|
|
@@ -20529,6 +20657,58 @@ function renderDomainMapping(domain) {
|
|
|
20529
20657
|
keyPath: join8(getDataDir(), "certs", `${entry.domain}-key.pem`)
|
|
20530
20658
|
};
|
|
20531
20659
|
}
|
|
20660
|
+
// src/commands/doctor.ts
|
|
20661
|
+
function makeCheck(id, status, summary, detail) {
|
|
20662
|
+
return { id, status, summary, detail };
|
|
20663
|
+
}
|
|
20664
|
+
function parseKeyValueOutput(stdout) {
|
|
20665
|
+
return Object.fromEntries(stdout.trim().split(`
|
|
20666
|
+
`).map((line) => line.split("=")).filter((parts) => parts.length === 2).map(([key, value]) => [key, value]));
|
|
20667
|
+
}
|
|
20668
|
+
function buildDoctorCommand() {
|
|
20669
|
+
return [
|
|
20670
|
+
'data_dir="${HASNA_MACHINES_DIR:-$HOME/.hasna/machines}"',
|
|
20671
|
+
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
20672
|
+
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
20673
|
+
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
20674
|
+
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
20675
|
+
`printf 'db_path=%s\\n' "$db_path"`,
|
|
20676
|
+
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
20677
|
+
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
20678
|
+
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
20679
|
+
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
20680
|
+
`printf 'bun=%s\\n' "$(bun --version 2>/dev/null || printf missing)"`,
|
|
20681
|
+
`printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
|
|
20682
|
+
`printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
|
|
20683
|
+
`printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
|
|
20684
|
+
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
20685
|
+
].join("; ");
|
|
20686
|
+
}
|
|
20687
|
+
function runDoctor(machineId = getLocalMachineId()) {
|
|
20688
|
+
const manifest = readManifest();
|
|
20689
|
+
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
20690
|
+
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
20691
|
+
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
20692
|
+
const checks = [
|
|
20693
|
+
makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
|
|
20694
|
+
makeCheck("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20695
|
+
makeCheck("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20696
|
+
makeCheck("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20697
|
+
makeCheck("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
20698
|
+
makeCheck("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
20699
|
+
makeCheck("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
20700
|
+
makeCheck("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
20701
|
+
makeCheck("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
20702
|
+
];
|
|
20703
|
+
return {
|
|
20704
|
+
machineId,
|
|
20705
|
+
source: commandChecks.source,
|
|
20706
|
+
manifestPath: details["manifest_path"],
|
|
20707
|
+
dbPath: details["db_path"],
|
|
20708
|
+
notificationsPath: details["notifications_path"],
|
|
20709
|
+
checks
|
|
20710
|
+
};
|
|
20711
|
+
}
|
|
20532
20712
|
// src/commands/manifest.ts
|
|
20533
20713
|
function manifestInit() {
|
|
20534
20714
|
return writeManifest(getDefaultManifest(), getManifestPath());
|
|
@@ -20608,6 +20788,13 @@ var AI_CLI_PACKAGES = {
|
|
|
20608
20788
|
codex: "@openai/codex",
|
|
20609
20789
|
gemini: "@google/gemini-cli"
|
|
20610
20790
|
};
|
|
20791
|
+
function getToolBinary(tool) {
|
|
20792
|
+
if (tool === "claude")
|
|
20793
|
+
return process.env["HASNA_MACHINES_CLAUDE_BINARY"] || "claude";
|
|
20794
|
+
if (tool === "codex")
|
|
20795
|
+
return process.env["HASNA_MACHINES_CODEX_BINARY"] || "codex";
|
|
20796
|
+
return process.env["HASNA_MACHINES_GEMINI_BINARY"] || "gemini";
|
|
20797
|
+
}
|
|
20611
20798
|
function normalizeTools(tools) {
|
|
20612
20799
|
if (!tools || tools.length === 0) {
|
|
20613
20800
|
return ["claude", "codex", "gemini"];
|
|
@@ -20627,8 +20814,27 @@ function buildInstallSteps(machine, tools) {
|
|
|
20627
20814
|
manager: "bun"
|
|
20628
20815
|
}));
|
|
20629
20816
|
}
|
|
20817
|
+
function resolveMachine2(machineId) {
|
|
20818
|
+
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
20819
|
+
}
|
|
20820
|
+
function buildProbeCommand(tool) {
|
|
20821
|
+
const binary = getToolBinary(tool);
|
|
20822
|
+
return `if command -v ${binary} >/dev/null 2>&1; then printf 'installed=1\\nversion='; ${binary} --version 2>/dev/null | head -n 1; printf '\\n'; else printf 'installed=0\\n'; fi`;
|
|
20823
|
+
}
|
|
20824
|
+
function parseProbe(tool, stdout) {
|
|
20825
|
+
const lines = stdout.trim().split(`
|
|
20826
|
+
`).filter(Boolean);
|
|
20827
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
20828
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
20829
|
+
return {
|
|
20830
|
+
tool,
|
|
20831
|
+
packageName: AI_CLI_PACKAGES[tool],
|
|
20832
|
+
installed: installedLine === "installed=1",
|
|
20833
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
20834
|
+
};
|
|
20835
|
+
}
|
|
20630
20836
|
function buildClaudeInstallPlan(machineId, tools) {
|
|
20631
|
-
const machine = (machineId
|
|
20837
|
+
const machine = resolveMachine2(machineId);
|
|
20632
20838
|
return {
|
|
20633
20839
|
machineId: machine.id,
|
|
20634
20840
|
mode: "plan",
|
|
@@ -20636,6 +20842,24 @@ function buildClaudeInstallPlan(machineId, tools) {
|
|
|
20636
20842
|
executed: 0
|
|
20637
20843
|
};
|
|
20638
20844
|
}
|
|
20845
|
+
function getClaudeCliStatus(machineId, tools) {
|
|
20846
|
+
const machine = resolveMachine2(machineId);
|
|
20847
|
+
const normalizedTools = normalizeTools(tools);
|
|
20848
|
+
const route = runMachineCommand(machine.id, "true").source;
|
|
20849
|
+
return {
|
|
20850
|
+
machineId: machine.id,
|
|
20851
|
+
source: route,
|
|
20852
|
+
tools: normalizedTools.map((tool) => parseProbe(tool, runMachineCommand(machine.id, buildProbeCommand(tool)).stdout))
|
|
20853
|
+
};
|
|
20854
|
+
}
|
|
20855
|
+
function diffClaudeCli(machineId, tools) {
|
|
20856
|
+
const status = getClaudeCliStatus(machineId, tools);
|
|
20857
|
+
return {
|
|
20858
|
+
...status,
|
|
20859
|
+
missing: status.tools.filter((tool) => !tool.installed).map((tool) => tool.tool),
|
|
20860
|
+
installed: status.tools.filter((tool) => tool.installed).map((tool) => tool.tool)
|
|
20861
|
+
};
|
|
20862
|
+
}
|
|
20639
20863
|
function runClaudeInstall(machineId, tools, options = {}) {
|
|
20640
20864
|
const plan = buildClaudeInstallPlan(machineId, tools);
|
|
20641
20865
|
if (!options.apply)
|
|
@@ -20746,6 +20970,138 @@ var notificationConfigSchema = exports_external2.object({
|
|
|
20746
20970
|
function sortChannels(channels) {
|
|
20747
20971
|
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
20748
20972
|
}
|
|
20973
|
+
function shellQuote2(value) {
|
|
20974
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20975
|
+
}
|
|
20976
|
+
function hasCommand(binary) {
|
|
20977
|
+
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
|
|
20978
|
+
stdout: "ignore",
|
|
20979
|
+
stderr: "ignore",
|
|
20980
|
+
env: process.env
|
|
20981
|
+
});
|
|
20982
|
+
return result.exitCode === 0;
|
|
20983
|
+
}
|
|
20984
|
+
function buildNotificationPreview(channel, event, message) {
|
|
20985
|
+
if (channel.type === "email") {
|
|
20986
|
+
return `send email to ${channel.target}: [${event}] ${message}`;
|
|
20987
|
+
}
|
|
20988
|
+
if (channel.type === "webhook") {
|
|
20989
|
+
return `POST ${channel.target} with payload {"event":"${event}","message":"${message}"}`;
|
|
20990
|
+
}
|
|
20991
|
+
return `${channel.target} --event ${event} --message ${JSON.stringify(message)}`;
|
|
20992
|
+
}
|
|
20993
|
+
async function dispatchEmail(channel, event, message) {
|
|
20994
|
+
const subject = `[${event}] machines notification`;
|
|
20995
|
+
const body = `To: ${channel.target}
|
|
20996
|
+
Subject: ${subject}
|
|
20997
|
+
Content-Type: text/plain; charset=utf-8
|
|
20998
|
+
|
|
20999
|
+
${message}
|
|
21000
|
+
`;
|
|
21001
|
+
if (hasCommand("sendmail")) {
|
|
21002
|
+
const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
|
|
21003
|
+
stdin: new TextEncoder().encode(body),
|
|
21004
|
+
stdout: "pipe",
|
|
21005
|
+
stderr: "pipe",
|
|
21006
|
+
env: process.env
|
|
21007
|
+
});
|
|
21008
|
+
if (result.exitCode !== 0) {
|
|
21009
|
+
throw new Error(result.stderr.toString().trim() || `sendmail exited with ${result.exitCode}`);
|
|
21010
|
+
}
|
|
21011
|
+
return {
|
|
21012
|
+
channelId: channel.id,
|
|
21013
|
+
event,
|
|
21014
|
+
delivered: true,
|
|
21015
|
+
transport: channel.type,
|
|
21016
|
+
detail: `Delivered via sendmail to ${channel.target}`
|
|
21017
|
+
};
|
|
21018
|
+
}
|
|
21019
|
+
if (hasCommand("mail")) {
|
|
21020
|
+
const command = `printf %s ${shellQuote2(message)} | mail -s ${shellQuote2(subject)} ${shellQuote2(channel.target)}`;
|
|
21021
|
+
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
21022
|
+
stdout: "pipe",
|
|
21023
|
+
stderr: "pipe",
|
|
21024
|
+
env: process.env
|
|
21025
|
+
});
|
|
21026
|
+
if (result.exitCode !== 0) {
|
|
21027
|
+
throw new Error(result.stderr.toString().trim() || `mail exited with ${result.exitCode}`);
|
|
21028
|
+
}
|
|
21029
|
+
return {
|
|
21030
|
+
channelId: channel.id,
|
|
21031
|
+
event,
|
|
21032
|
+
delivered: true,
|
|
21033
|
+
transport: channel.type,
|
|
21034
|
+
detail: `Delivered via mail to ${channel.target}`
|
|
21035
|
+
};
|
|
21036
|
+
}
|
|
21037
|
+
throw new Error("No local email transport available. Install sendmail or mail.");
|
|
21038
|
+
}
|
|
21039
|
+
async function dispatchWebhook(channel, event, message) {
|
|
21040
|
+
const response = await fetch(channel.target, {
|
|
21041
|
+
method: "POST",
|
|
21042
|
+
headers: {
|
|
21043
|
+
"content-type": "application/json"
|
|
21044
|
+
},
|
|
21045
|
+
body: JSON.stringify({
|
|
21046
|
+
channelId: channel.id,
|
|
21047
|
+
event,
|
|
21048
|
+
message,
|
|
21049
|
+
sentAt: new Date().toISOString()
|
|
21050
|
+
})
|
|
21051
|
+
});
|
|
21052
|
+
if (!response.ok) {
|
|
21053
|
+
const text = await response.text();
|
|
21054
|
+
throw new Error(`Webhook responded ${response.status}: ${text || response.statusText}`);
|
|
21055
|
+
}
|
|
21056
|
+
return {
|
|
21057
|
+
channelId: channel.id,
|
|
21058
|
+
event,
|
|
21059
|
+
delivered: true,
|
|
21060
|
+
transport: channel.type,
|
|
21061
|
+
detail: `Webhook accepted with HTTP ${response.status}`
|
|
21062
|
+
};
|
|
21063
|
+
}
|
|
21064
|
+
async function dispatchCommand(channel, event, message) {
|
|
21065
|
+
const result = Bun.spawnSync(["bash", "-lc", channel.target], {
|
|
21066
|
+
stdout: "pipe",
|
|
21067
|
+
stderr: "pipe",
|
|
21068
|
+
env: {
|
|
21069
|
+
...process.env,
|
|
21070
|
+
HASNA_MACHINES_NOTIFICATION_CHANNEL: channel.id,
|
|
21071
|
+
HASNA_MACHINES_NOTIFICATION_EVENT: event,
|
|
21072
|
+
HASNA_MACHINES_NOTIFICATION_MESSAGE: message
|
|
21073
|
+
}
|
|
21074
|
+
});
|
|
21075
|
+
if (result.exitCode !== 0) {
|
|
21076
|
+
throw new Error(result.stderr.toString().trim() || `command exited with ${result.exitCode}`);
|
|
21077
|
+
}
|
|
21078
|
+
const stdout = result.stdout.toString().trim();
|
|
21079
|
+
return {
|
|
21080
|
+
channelId: channel.id,
|
|
21081
|
+
event,
|
|
21082
|
+
delivered: true,
|
|
21083
|
+
transport: channel.type,
|
|
21084
|
+
detail: stdout || "Command completed successfully"
|
|
21085
|
+
};
|
|
21086
|
+
}
|
|
21087
|
+
async function dispatchChannel(channel, event, message) {
|
|
21088
|
+
if (!channel.enabled) {
|
|
21089
|
+
return {
|
|
21090
|
+
channelId: channel.id,
|
|
21091
|
+
event,
|
|
21092
|
+
delivered: false,
|
|
21093
|
+
transport: channel.type,
|
|
21094
|
+
detail: "Channel is disabled"
|
|
21095
|
+
};
|
|
21096
|
+
}
|
|
21097
|
+
if (channel.type === "email") {
|
|
21098
|
+
return dispatchEmail(channel, event, message);
|
|
21099
|
+
}
|
|
21100
|
+
if (channel.type === "webhook") {
|
|
21101
|
+
return dispatchWebhook(channel, event, message);
|
|
21102
|
+
}
|
|
21103
|
+
return dispatchCommand(channel, event, message);
|
|
21104
|
+
}
|
|
20749
21105
|
function getDefaultNotificationConfig() {
|
|
20750
21106
|
return {
|
|
20751
21107
|
version: 1,
|
|
@@ -20789,16 +21145,34 @@ function removeNotificationChannel(channelId) {
|
|
|
20789
21145
|
channels: config.channels.filter((channel) => channel.id !== channelId)
|
|
20790
21146
|
});
|
|
20791
21147
|
}
|
|
20792
|
-
function
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
return
|
|
21148
|
+
async function dispatchNotificationEvent(event, message, options = {}) {
|
|
21149
|
+
const channels = readNotificationConfig().channels.filter((channel) => {
|
|
21150
|
+
if (options.channelId && channel.id !== options.channelId) {
|
|
21151
|
+
return false;
|
|
21152
|
+
}
|
|
21153
|
+
return channel.events.includes(event) || event === "manual.test";
|
|
21154
|
+
});
|
|
21155
|
+
const deliveries = [];
|
|
21156
|
+
for (const channel of channels) {
|
|
21157
|
+
try {
|
|
21158
|
+
deliveries.push(await dispatchChannel(channel, event, message));
|
|
21159
|
+
} catch (error) {
|
|
21160
|
+
deliveries.push({
|
|
21161
|
+
channelId: channel.id,
|
|
21162
|
+
event,
|
|
21163
|
+
delivered: false,
|
|
21164
|
+
transport: channel.type,
|
|
21165
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
21166
|
+
});
|
|
21167
|
+
}
|
|
20798
21168
|
}
|
|
20799
|
-
return
|
|
21169
|
+
return {
|
|
21170
|
+
event,
|
|
21171
|
+
message,
|
|
21172
|
+
deliveries
|
|
21173
|
+
};
|
|
20800
21174
|
}
|
|
20801
|
-
function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
21175
|
+
async function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
20802
21176
|
const channel = readNotificationConfig().channels.find((entry) => entry.id === channelId);
|
|
20803
21177
|
if (!channel) {
|
|
20804
21178
|
throw new Error(`Notification channel not found: ${channelId}`);
|
|
@@ -20809,76 +21183,24 @@ function testNotificationChannel(channelId, event = "manual.test", message = "ma
|
|
|
20809
21183
|
channelId,
|
|
20810
21184
|
mode: "plan",
|
|
20811
21185
|
delivered: false,
|
|
20812
|
-
preview
|
|
21186
|
+
preview,
|
|
21187
|
+
detail: "Preview only"
|
|
20813
21188
|
};
|
|
20814
21189
|
}
|
|
20815
21190
|
if (!options.yes) {
|
|
20816
21191
|
throw new Error("Notification test execution requires --yes.");
|
|
20817
21192
|
}
|
|
20818
|
-
|
|
20819
|
-
const result = Bun.spawnSync(["bash", "-lc", preview], {
|
|
20820
|
-
stdout: "pipe",
|
|
20821
|
-
stderr: "pipe",
|
|
20822
|
-
env: process.env
|
|
20823
|
-
});
|
|
20824
|
-
if (result.exitCode !== 0) {
|
|
20825
|
-
throw new Error(`Notification command failed (${channel.id}): ${result.stderr.toString().trim()}`);
|
|
20826
|
-
}
|
|
20827
|
-
}
|
|
21193
|
+
const delivery = await dispatchChannel(channel, event, message);
|
|
20828
21194
|
return {
|
|
20829
21195
|
channelId,
|
|
20830
21196
|
mode: "apply",
|
|
20831
|
-
delivered:
|
|
20832
|
-
preview
|
|
20833
|
-
|
|
20834
|
-
}
|
|
20835
|
-
// src/commands/ports.ts
|
|
20836
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
20837
|
-
|
|
20838
|
-
// src/commands/ssh.ts
|
|
20839
|
-
import { spawnSync } from "child_process";
|
|
20840
|
-
function envReachableHosts() {
|
|
20841
|
-
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
20842
|
-
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
20843
|
-
}
|
|
20844
|
-
function isReachable(host) {
|
|
20845
|
-
const overrides = envReachableHosts();
|
|
20846
|
-
if (overrides.size > 0) {
|
|
20847
|
-
return overrides.has(host);
|
|
20848
|
-
}
|
|
20849
|
-
const probe = spawnSync("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
|
|
20850
|
-
stdio: "ignore"
|
|
20851
|
-
});
|
|
20852
|
-
return probe.status === 0;
|
|
20853
|
-
}
|
|
20854
|
-
function resolveSshTarget(machineId) {
|
|
20855
|
-
const machine = getManifestMachine(machineId);
|
|
20856
|
-
if (!machine) {
|
|
20857
|
-
throw new Error(`Machine not found in manifest: ${machineId}`);
|
|
20858
|
-
}
|
|
20859
|
-
const current = detectCurrentMachineManifest();
|
|
20860
|
-
if (machine.id === current.id) {
|
|
20861
|
-
return {
|
|
20862
|
-
machineId,
|
|
20863
|
-
target: "localhost",
|
|
20864
|
-
route: "local"
|
|
20865
|
-
};
|
|
20866
|
-
}
|
|
20867
|
-
const lanTarget = machine.sshAddress || machine.hostname || machine.id;
|
|
20868
|
-
const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
|
|
20869
|
-
const route = isReachable(lanTarget) ? "lan" : "tailscale";
|
|
20870
|
-
return {
|
|
20871
|
-
machineId,
|
|
20872
|
-
target: route === "lan" ? lanTarget : tailscaleTarget,
|
|
20873
|
-
route
|
|
21197
|
+
delivered: delivery.delivered,
|
|
21198
|
+
preview,
|
|
21199
|
+
detail: delivery.detail
|
|
20874
21200
|
};
|
|
20875
21201
|
}
|
|
20876
|
-
function buildSshCommand(machineId, remoteCommand) {
|
|
20877
|
-
const resolved = resolveSshTarget(machineId);
|
|
20878
|
-
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
20879
|
-
}
|
|
20880
|
-
|
|
20881
21202
|
// src/commands/ports.ts
|
|
21203
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
20882
21204
|
function parseSsOutput(output) {
|
|
20883
21205
|
return output.trim().split(`
|
|
20884
21206
|
`).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
@@ -20920,7 +21242,7 @@ function listPorts(machineId) {
|
|
|
20920
21242
|
const isLocal = targetMachineId === getLocalMachineId();
|
|
20921
21243
|
const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
|
|
20922
21244
|
const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
|
|
20923
|
-
const result =
|
|
21245
|
+
const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
|
|
20924
21246
|
if (result.status !== 0) {
|
|
20925
21247
|
throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
|
|
20926
21248
|
}
|
|
@@ -20930,6 +21252,24 @@ function listPorts(machineId) {
|
|
|
20930
21252
|
listeners: parsePortOutput(result.stdout, format)
|
|
20931
21253
|
};
|
|
20932
21254
|
}
|
|
21255
|
+
// src/version.ts
|
|
21256
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
21257
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
21258
|
+
import { fileURLToPath } from "url";
|
|
21259
|
+
function getPackageVersion() {
|
|
21260
|
+
try {
|
|
21261
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
21262
|
+
const candidates = [join9(here, "..", "package.json"), join9(here, "..", "..", "package.json")];
|
|
21263
|
+
const pkgPath = candidates.find((candidate) => existsSync8(candidate));
|
|
21264
|
+
if (!pkgPath) {
|
|
21265
|
+
return "0.0.0";
|
|
21266
|
+
}
|
|
21267
|
+
return JSON.parse(readFileSync5(pkgPath, "utf8")).version || "0.0.0";
|
|
21268
|
+
} catch {
|
|
21269
|
+
return "0.0.0";
|
|
21270
|
+
}
|
|
21271
|
+
}
|
|
21272
|
+
|
|
20933
21273
|
// src/commands/status.ts
|
|
20934
21274
|
function getStatus() {
|
|
20935
21275
|
const manifest = readManifest();
|
|
@@ -20973,13 +21313,27 @@ function getServeInfo(options = {}) {
|
|
|
20973
21313
|
host,
|
|
20974
21314
|
port,
|
|
20975
21315
|
url: `http://${host}:${port}`,
|
|
20976
|
-
routes: [
|
|
21316
|
+
routes: [
|
|
21317
|
+
"/",
|
|
21318
|
+
"/health",
|
|
21319
|
+
"/api/status",
|
|
21320
|
+
"/api/manifest",
|
|
21321
|
+
"/api/notifications",
|
|
21322
|
+
"/api/doctor",
|
|
21323
|
+
"/api/self-test",
|
|
21324
|
+
"/api/apps/status",
|
|
21325
|
+
"/api/apps/diff",
|
|
21326
|
+
"/api/install-claude/status",
|
|
21327
|
+
"/api/install-claude/diff",
|
|
21328
|
+
"/api/notifications/test"
|
|
21329
|
+
]
|
|
20977
21330
|
};
|
|
20978
21331
|
}
|
|
20979
21332
|
function renderDashboardHtml() {
|
|
20980
21333
|
const status = getStatus();
|
|
20981
21334
|
const manifest = manifestList();
|
|
20982
21335
|
const notifications = listNotificationChannels();
|
|
21336
|
+
const doctor = runDoctor();
|
|
20983
21337
|
return `<!doctype html>
|
|
20984
21338
|
<html lang="en">
|
|
20985
21339
|
<head>
|
|
@@ -20995,12 +21349,14 @@ function renderDashboardHtml() {
|
|
|
20995
21349
|
.card { background: #121933; border: 1px solid #243057; border-radius: 16px; padding: 20px; }
|
|
20996
21350
|
.stat { font-size: 32px; font-weight: 700; margin-top: 8px; }
|
|
20997
21351
|
table { width: 100%; border-collapse: collapse; }
|
|
20998
|
-
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; }
|
|
21352
|
+
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; vertical-align: top; }
|
|
20999
21353
|
code { color: #9ed0ff; }
|
|
21000
21354
|
.badge { display: inline-block; border-radius: 999px; padding: 4px 10px; font-size: 12px; }
|
|
21001
|
-
.online { background: #12351f; color: #74f0a7; }
|
|
21002
|
-
.offline { background: #3b1a1a; color: #ff8c8c; }
|
|
21003
|
-
.unknown { background: #2f2b16; color: #ffd76a; }
|
|
21355
|
+
.online, .ok { background: #12351f; color: #74f0a7; }
|
|
21356
|
+
.offline, .fail { background: #3b1a1a; color: #ff8c8c; }
|
|
21357
|
+
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
21358
|
+
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
21359
|
+
.muted { color: #9fb0d9; }
|
|
21004
21360
|
</style>
|
|
21005
21361
|
</head>
|
|
21006
21362
|
<body>
|
|
@@ -21010,6 +21366,7 @@ function renderDashboardHtml() {
|
|
|
21010
21366
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
21011
21367
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
21012
21368
|
<section class="card"><div>Notification channels</div><div class="stat">${notifications.channels.length}</div></section>
|
|
21369
|
+
<section class="card"><div>Doctor warnings</div><div class="stat">${doctor.checks.filter((entry) => entry.status !== "ok").length}</div></section>
|
|
21013
21370
|
</div>
|
|
21014
21371
|
|
|
21015
21372
|
<section class="card" style="margin-top:16px">
|
|
@@ -21027,6 +21384,25 @@ function renderDashboardHtml() {
|
|
|
21027
21384
|
</table>
|
|
21028
21385
|
</section>
|
|
21029
21386
|
|
|
21387
|
+
<section class="card" style="margin-top:16px">
|
|
21388
|
+
<h2>Doctor</h2>
|
|
21389
|
+
<table>
|
|
21390
|
+
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
21391
|
+
<tbody>
|
|
21392
|
+
${doctor.checks.map((entry) => `<tr>
|
|
21393
|
+
<td>${escapeHtml(entry.summary)}</td>
|
|
21394
|
+
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
21395
|
+
<td class="muted">${escapeHtml(entry.detail)}</td>
|
|
21396
|
+
</tr>`).join("")}
|
|
21397
|
+
</tbody>
|
|
21398
|
+
</table>
|
|
21399
|
+
</section>
|
|
21400
|
+
|
|
21401
|
+
<section class="card" style="margin-top:16px">
|
|
21402
|
+
<h2>Self Test</h2>
|
|
21403
|
+
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
21404
|
+
</section>
|
|
21405
|
+
|
|
21030
21406
|
<section class="card" style="margin-top:16px">
|
|
21031
21407
|
<h2>Manifest</h2>
|
|
21032
21408
|
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
@@ -21035,13 +21411,25 @@ function renderDashboardHtml() {
|
|
|
21035
21411
|
</body>
|
|
21036
21412
|
</html>`;
|
|
21037
21413
|
}
|
|
21414
|
+
async function parseJsonBody(request) {
|
|
21415
|
+
try {
|
|
21416
|
+
return await request.json();
|
|
21417
|
+
} catch {
|
|
21418
|
+
return {};
|
|
21419
|
+
}
|
|
21420
|
+
}
|
|
21421
|
+
function jsonError(message, status = 400) {
|
|
21422
|
+
return Response.json({ error: message }, { status });
|
|
21423
|
+
}
|
|
21038
21424
|
function startDashboardServer(options = {}) {
|
|
21039
21425
|
const info = getServeInfo(options);
|
|
21040
21426
|
return Bun.serve({
|
|
21041
21427
|
hostname: info.host,
|
|
21042
21428
|
port: info.port,
|
|
21043
|
-
fetch(request) {
|
|
21429
|
+
async fetch(request) {
|
|
21044
21430
|
const url = new URL(request.url);
|
|
21431
|
+
const machineId = url.searchParams.get("machine") || undefined;
|
|
21432
|
+
const tools = url.searchParams.get("tools")?.split(",").map((value) => value.trim()).filter(Boolean);
|
|
21045
21433
|
if (url.pathname === "/health") {
|
|
21046
21434
|
return Response.json({ ok: true, ...getServeInfo(options) });
|
|
21047
21435
|
}
|
|
@@ -21054,6 +21442,43 @@ function startDashboardServer(options = {}) {
|
|
|
21054
21442
|
if (url.pathname === "/api/notifications") {
|
|
21055
21443
|
return Response.json(listNotificationChannels());
|
|
21056
21444
|
}
|
|
21445
|
+
if (url.pathname === "/api/doctor") {
|
|
21446
|
+
return Response.json(runDoctor(machineId));
|
|
21447
|
+
}
|
|
21448
|
+
if (url.pathname === "/api/self-test") {
|
|
21449
|
+
return Response.json(runSelfTest());
|
|
21450
|
+
}
|
|
21451
|
+
if (url.pathname === "/api/apps/status") {
|
|
21452
|
+
return Response.json(getAppsStatus(machineId));
|
|
21453
|
+
}
|
|
21454
|
+
if (url.pathname === "/api/apps/diff") {
|
|
21455
|
+
return Response.json(diffApps(machineId));
|
|
21456
|
+
}
|
|
21457
|
+
if (url.pathname === "/api/install-claude/status") {
|
|
21458
|
+
return Response.json(getClaudeCliStatus(machineId, tools));
|
|
21459
|
+
}
|
|
21460
|
+
if (url.pathname === "/api/install-claude/diff") {
|
|
21461
|
+
return Response.json(diffClaudeCli(machineId, tools));
|
|
21462
|
+
}
|
|
21463
|
+
if (url.pathname === "/api/notifications/test") {
|
|
21464
|
+
if (request.method !== "POST") {
|
|
21465
|
+
return jsonError("Use POST for notification tests.", 405);
|
|
21466
|
+
}
|
|
21467
|
+
const body = await parseJsonBody(request);
|
|
21468
|
+
const channelId = typeof body["channelId"] === "string" ? body["channelId"] : undefined;
|
|
21469
|
+
if (!channelId) {
|
|
21470
|
+
return jsonError("channelId is required.");
|
|
21471
|
+
}
|
|
21472
|
+
const event = typeof body["event"] === "string" ? body["event"] : undefined;
|
|
21473
|
+
const message = typeof body["message"] === "string" ? body["message"] : undefined;
|
|
21474
|
+
const apply = body["apply"] === true;
|
|
21475
|
+
const yes = body["yes"] === true;
|
|
21476
|
+
try {
|
|
21477
|
+
return Response.json(await testNotificationChannel(channelId, event, message, { apply, yes }));
|
|
21478
|
+
} catch (error) {
|
|
21479
|
+
return jsonError(error instanceof Error ? error.message : String(error));
|
|
21480
|
+
}
|
|
21481
|
+
}
|
|
21057
21482
|
return new Response(renderDashboardHtml(), {
|
|
21058
21483
|
headers: {
|
|
21059
21484
|
"content-type": "text/html; charset=utf-8"
|
|
@@ -21062,6 +21487,36 @@ function startDashboardServer(options = {}) {
|
|
|
21062
21487
|
}
|
|
21063
21488
|
});
|
|
21064
21489
|
}
|
|
21490
|
+
|
|
21491
|
+
// src/commands/self-test.ts
|
|
21492
|
+
function check(id, status, summary, detail) {
|
|
21493
|
+
return { id, status, summary, detail };
|
|
21494
|
+
}
|
|
21495
|
+
function runSelfTest() {
|
|
21496
|
+
const version = getPackageVersion();
|
|
21497
|
+
const status = getStatus();
|
|
21498
|
+
const doctor = runDoctor();
|
|
21499
|
+
const serveInfo = getServeInfo();
|
|
21500
|
+
const html = renderDashboardHtml();
|
|
21501
|
+
const notifications = listNotificationChannels();
|
|
21502
|
+
const apps = listApps(status.machineId);
|
|
21503
|
+
const appsDiff = diffApps(status.machineId);
|
|
21504
|
+
const cliPlan = buildClaudeInstallPlan(status.machineId);
|
|
21505
|
+
return {
|
|
21506
|
+
machineId: getLocalMachineId(),
|
|
21507
|
+
checks: [
|
|
21508
|
+
check("package-version", version === "0.0.0" ? "fail" : "ok", "Package version resolves", version),
|
|
21509
|
+
check("status", "ok", "Status loads", JSON.stringify({ machines: status.manifestMachineCount, heartbeats: status.heartbeatCount })),
|
|
21510
|
+
check("doctor", doctor.checks.some((entry) => entry.status === "fail") ? "warn" : "ok", "Doctor completed", `${doctor.checks.length} checks`),
|
|
21511
|
+
check("serve-info", "ok", "Dashboard info renders", `${serveInfo.url} routes=${serveInfo.routes.length}`),
|
|
21512
|
+
check("dashboard-html", html.includes("Machines Dashboard") ? "ok" : "fail", "Dashboard HTML renders", html.slice(0, 80)),
|
|
21513
|
+
check("notifications", "ok", "Notifications config loads", `${notifications.channels.length} channels`),
|
|
21514
|
+
check("apps", "ok", "Apps manifest loads", `${apps.apps.length} apps`),
|
|
21515
|
+
check("apps-diff", appsDiff.missing.length === 0 ? "ok" : "warn", "Apps diff computed", `missing=${appsDiff.missing.length} installed=${appsDiff.installed.length}`),
|
|
21516
|
+
check("install-claude-plan", cliPlan.steps.length > 0 ? "ok" : "warn", "Install plan renders", `${cliPlan.steps.length} steps`)
|
|
21517
|
+
]
|
|
21518
|
+
};
|
|
21519
|
+
}
|
|
21065
21520
|
// src/commands/setup.ts
|
|
21066
21521
|
function quote3(value) {
|
|
21067
21522
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -21177,7 +21632,7 @@ function runSetup(machineId, options = {}) {
|
|
|
21177
21632
|
return summary;
|
|
21178
21633
|
}
|
|
21179
21634
|
// src/commands/sync.ts
|
|
21180
|
-
import { existsSync as
|
|
21635
|
+
import { existsSync as existsSync9, lstatSync, readFileSync as readFileSync6, symlinkSync, copyFileSync as copyFileSync2 } from "fs";
|
|
21181
21636
|
function quote4(value) {
|
|
21182
21637
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21183
21638
|
}
|
|
@@ -21208,12 +21663,12 @@ function packageInstallCommand(machine, packageName, manager = machine.platform
|
|
|
21208
21663
|
function detectPackageActions(machine) {
|
|
21209
21664
|
return (machine.packages || []).map((pkg, index) => {
|
|
21210
21665
|
const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
|
|
21211
|
-
const
|
|
21666
|
+
const check2 = Bun.spawnSync(["bash", "-lc", packageCheckCommand(machine, pkg.name, manager)], {
|
|
21212
21667
|
stdout: "ignore",
|
|
21213
21668
|
stderr: "ignore",
|
|
21214
21669
|
env: process.env
|
|
21215
21670
|
});
|
|
21216
|
-
const installed =
|
|
21671
|
+
const installed = check2.exitCode === 0;
|
|
21217
21672
|
return {
|
|
21218
21673
|
id: `package-${index + 1}`,
|
|
21219
21674
|
title: `${installed ? "Package present" : "Install package"} ${pkg.name}`,
|
|
@@ -21225,15 +21680,15 @@ function detectPackageActions(machine) {
|
|
|
21225
21680
|
}
|
|
21226
21681
|
function detectFileActions(machine) {
|
|
21227
21682
|
return (machine.files || []).map((file, index) => {
|
|
21228
|
-
const sourceExists =
|
|
21229
|
-
const targetExists =
|
|
21683
|
+
const sourceExists = existsSync9(file.source);
|
|
21684
|
+
const targetExists = existsSync9(file.target);
|
|
21230
21685
|
let status = "missing";
|
|
21231
21686
|
if (sourceExists && targetExists) {
|
|
21232
21687
|
if (file.mode === "symlink") {
|
|
21233
21688
|
status = lstatSync(file.target).isSymbolicLink() ? "ok" : "drifted";
|
|
21234
21689
|
} else {
|
|
21235
|
-
const source =
|
|
21236
|
-
const target =
|
|
21690
|
+
const source = readFileSync6(file.source, "utf8");
|
|
21691
|
+
const target = readFileSync6(file.target, "utf8");
|
|
21237
21692
|
status = source === target ? "ok" : "drifted";
|
|
21238
21693
|
}
|
|
21239
21694
|
}
|
|
@@ -25384,7 +25839,7 @@ var ZodType3 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
25384
25839
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
25385
25840
|
inst.safeParseAsync = async (data, params) => safeParseAsync3(inst, data, params);
|
|
25386
25841
|
inst.spa = inst.safeParseAsync;
|
|
25387
|
-
inst.refine = (
|
|
25842
|
+
inst.refine = (check2, params) => inst.check(refine(check2, params));
|
|
25388
25843
|
inst.superRefine = (refinement) => inst.check(superRefine(refinement));
|
|
25389
25844
|
inst.overwrite = (fn) => inst.check(_overwrite(fn));
|
|
25390
25845
|
inst.optional = () => optional(inst);
|
|
@@ -25928,7 +26383,7 @@ var ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
25928
26383
|
$ZodCustom.init(inst, def);
|
|
25929
26384
|
ZodType3.init(inst, def);
|
|
25930
26385
|
});
|
|
25931
|
-
function
|
|
26386
|
+
function check2(fn) {
|
|
25932
26387
|
const ch = new $ZodCheck({
|
|
25933
26388
|
check: "custom"
|
|
25934
26389
|
});
|
|
@@ -25942,7 +26397,7 @@ function refine(fn, _params = {}) {
|
|
|
25942
26397
|
return _refine(ZodCustom, fn, _params);
|
|
25943
26398
|
}
|
|
25944
26399
|
function superRefine(fn) {
|
|
25945
|
-
const ch =
|
|
26400
|
+
const ch = check2((payload) => {
|
|
25946
26401
|
payload.addIssue = (issue2) => {
|
|
25947
26402
|
if (typeof issue2 === "string") {
|
|
25948
26403
|
payload.issues.push(exports_util.issue(issue2, payload.value, ch._zod.def));
|
|
@@ -26941,38 +27396,38 @@ function parseBigintDef(def, refs) {
|
|
|
26941
27396
|
};
|
|
26942
27397
|
if (!def.checks)
|
|
26943
27398
|
return res;
|
|
26944
|
-
for (const
|
|
26945
|
-
switch (
|
|
27399
|
+
for (const check3 of def.checks) {
|
|
27400
|
+
switch (check3.kind) {
|
|
26946
27401
|
case "min":
|
|
26947
27402
|
if (refs.target === "jsonSchema7") {
|
|
26948
|
-
if (
|
|
26949
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27403
|
+
if (check3.inclusive) {
|
|
27404
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
26950
27405
|
} else {
|
|
26951
|
-
setResponseValueAndErrors(res, "exclusiveMinimum",
|
|
27406
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check3.value, check3.message, refs);
|
|
26952
27407
|
}
|
|
26953
27408
|
} else {
|
|
26954
|
-
if (!
|
|
27409
|
+
if (!check3.inclusive) {
|
|
26955
27410
|
res.exclusiveMinimum = true;
|
|
26956
27411
|
}
|
|
26957
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27412
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
26958
27413
|
}
|
|
26959
27414
|
break;
|
|
26960
27415
|
case "max":
|
|
26961
27416
|
if (refs.target === "jsonSchema7") {
|
|
26962
|
-
if (
|
|
26963
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27417
|
+
if (check3.inclusive) {
|
|
27418
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
26964
27419
|
} else {
|
|
26965
|
-
setResponseValueAndErrors(res, "exclusiveMaximum",
|
|
27420
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check3.value, check3.message, refs);
|
|
26966
27421
|
}
|
|
26967
27422
|
} else {
|
|
26968
|
-
if (!
|
|
27423
|
+
if (!check3.inclusive) {
|
|
26969
27424
|
res.exclusiveMaximum = true;
|
|
26970
27425
|
}
|
|
26971
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27426
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
26972
27427
|
}
|
|
26973
27428
|
break;
|
|
26974
27429
|
case "multipleOf":
|
|
26975
|
-
setResponseValueAndErrors(res, "multipleOf",
|
|
27430
|
+
setResponseValueAndErrors(res, "multipleOf", check3.value, check3.message, refs);
|
|
26976
27431
|
break;
|
|
26977
27432
|
}
|
|
26978
27433
|
}
|
|
@@ -27028,13 +27483,13 @@ var integerDateParser = (def, refs) => {
|
|
|
27028
27483
|
if (refs.target === "openApi3") {
|
|
27029
27484
|
return res;
|
|
27030
27485
|
}
|
|
27031
|
-
for (const
|
|
27032
|
-
switch (
|
|
27486
|
+
for (const check3 of def.checks) {
|
|
27487
|
+
switch (check3.kind) {
|
|
27033
27488
|
case "min":
|
|
27034
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27489
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27035
27490
|
break;
|
|
27036
27491
|
case "max":
|
|
27037
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27492
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27038
27493
|
break;
|
|
27039
27494
|
}
|
|
27040
27495
|
}
|
|
@@ -27152,125 +27607,125 @@ function parseStringDef(def, refs) {
|
|
|
27152
27607
|
type: "string"
|
|
27153
27608
|
};
|
|
27154
27609
|
if (def.checks) {
|
|
27155
|
-
for (const
|
|
27156
|
-
switch (
|
|
27610
|
+
for (const check3 of def.checks) {
|
|
27611
|
+
switch (check3.kind) {
|
|
27157
27612
|
case "min":
|
|
27158
|
-
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength,
|
|
27613
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check3.value) : check3.value, check3.message, refs);
|
|
27159
27614
|
break;
|
|
27160
27615
|
case "max":
|
|
27161
|
-
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength,
|
|
27616
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check3.value) : check3.value, check3.message, refs);
|
|
27162
27617
|
break;
|
|
27163
27618
|
case "email":
|
|
27164
27619
|
switch (refs.emailStrategy) {
|
|
27165
27620
|
case "format:email":
|
|
27166
|
-
addFormat(res, "email",
|
|
27621
|
+
addFormat(res, "email", check3.message, refs);
|
|
27167
27622
|
break;
|
|
27168
27623
|
case "format:idn-email":
|
|
27169
|
-
addFormat(res, "idn-email",
|
|
27624
|
+
addFormat(res, "idn-email", check3.message, refs);
|
|
27170
27625
|
break;
|
|
27171
27626
|
case "pattern:zod":
|
|
27172
|
-
addPattern(res, zodPatterns.email,
|
|
27627
|
+
addPattern(res, zodPatterns.email, check3.message, refs);
|
|
27173
27628
|
break;
|
|
27174
27629
|
}
|
|
27175
27630
|
break;
|
|
27176
27631
|
case "url":
|
|
27177
|
-
addFormat(res, "uri",
|
|
27632
|
+
addFormat(res, "uri", check3.message, refs);
|
|
27178
27633
|
break;
|
|
27179
27634
|
case "uuid":
|
|
27180
|
-
addFormat(res, "uuid",
|
|
27635
|
+
addFormat(res, "uuid", check3.message, refs);
|
|
27181
27636
|
break;
|
|
27182
27637
|
case "regex":
|
|
27183
|
-
addPattern(res,
|
|
27638
|
+
addPattern(res, check3.regex, check3.message, refs);
|
|
27184
27639
|
break;
|
|
27185
27640
|
case "cuid":
|
|
27186
|
-
addPattern(res, zodPatterns.cuid,
|
|
27641
|
+
addPattern(res, zodPatterns.cuid, check3.message, refs);
|
|
27187
27642
|
break;
|
|
27188
27643
|
case "cuid2":
|
|
27189
|
-
addPattern(res, zodPatterns.cuid2,
|
|
27644
|
+
addPattern(res, zodPatterns.cuid2, check3.message, refs);
|
|
27190
27645
|
break;
|
|
27191
27646
|
case "startsWith":
|
|
27192
|
-
addPattern(res, RegExp(`^${escapeLiteralCheckValue(
|
|
27647
|
+
addPattern(res, RegExp(`^${escapeLiteralCheckValue(check3.value, refs)}`), check3.message, refs);
|
|
27193
27648
|
break;
|
|
27194
27649
|
case "endsWith":
|
|
27195
|
-
addPattern(res, RegExp(`${escapeLiteralCheckValue(
|
|
27650
|
+
addPattern(res, RegExp(`${escapeLiteralCheckValue(check3.value, refs)}$`), check3.message, refs);
|
|
27196
27651
|
break;
|
|
27197
27652
|
case "datetime":
|
|
27198
|
-
addFormat(res, "date-time",
|
|
27653
|
+
addFormat(res, "date-time", check3.message, refs);
|
|
27199
27654
|
break;
|
|
27200
27655
|
case "date":
|
|
27201
|
-
addFormat(res, "date",
|
|
27656
|
+
addFormat(res, "date", check3.message, refs);
|
|
27202
27657
|
break;
|
|
27203
27658
|
case "time":
|
|
27204
|
-
addFormat(res, "time",
|
|
27659
|
+
addFormat(res, "time", check3.message, refs);
|
|
27205
27660
|
break;
|
|
27206
27661
|
case "duration":
|
|
27207
|
-
addFormat(res, "duration",
|
|
27662
|
+
addFormat(res, "duration", check3.message, refs);
|
|
27208
27663
|
break;
|
|
27209
27664
|
case "length":
|
|
27210
|
-
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength,
|
|
27211
|
-
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength,
|
|
27665
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check3.value) : check3.value, check3.message, refs);
|
|
27666
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check3.value) : check3.value, check3.message, refs);
|
|
27212
27667
|
break;
|
|
27213
27668
|
case "includes": {
|
|
27214
|
-
addPattern(res, RegExp(escapeLiteralCheckValue(
|
|
27669
|
+
addPattern(res, RegExp(escapeLiteralCheckValue(check3.value, refs)), check3.message, refs);
|
|
27215
27670
|
break;
|
|
27216
27671
|
}
|
|
27217
27672
|
case "ip": {
|
|
27218
|
-
if (
|
|
27219
|
-
addFormat(res, "ipv4",
|
|
27673
|
+
if (check3.version !== "v6") {
|
|
27674
|
+
addFormat(res, "ipv4", check3.message, refs);
|
|
27220
27675
|
}
|
|
27221
|
-
if (
|
|
27222
|
-
addFormat(res, "ipv6",
|
|
27676
|
+
if (check3.version !== "v4") {
|
|
27677
|
+
addFormat(res, "ipv6", check3.message, refs);
|
|
27223
27678
|
}
|
|
27224
27679
|
break;
|
|
27225
27680
|
}
|
|
27226
27681
|
case "base64url":
|
|
27227
|
-
addPattern(res, zodPatterns.base64url,
|
|
27682
|
+
addPattern(res, zodPatterns.base64url, check3.message, refs);
|
|
27228
27683
|
break;
|
|
27229
27684
|
case "jwt":
|
|
27230
|
-
addPattern(res, zodPatterns.jwt,
|
|
27685
|
+
addPattern(res, zodPatterns.jwt, check3.message, refs);
|
|
27231
27686
|
break;
|
|
27232
27687
|
case "cidr": {
|
|
27233
|
-
if (
|
|
27234
|
-
addPattern(res, zodPatterns.ipv4Cidr,
|
|
27688
|
+
if (check3.version !== "v6") {
|
|
27689
|
+
addPattern(res, zodPatterns.ipv4Cidr, check3.message, refs);
|
|
27235
27690
|
}
|
|
27236
|
-
if (
|
|
27237
|
-
addPattern(res, zodPatterns.ipv6Cidr,
|
|
27691
|
+
if (check3.version !== "v4") {
|
|
27692
|
+
addPattern(res, zodPatterns.ipv6Cidr, check3.message, refs);
|
|
27238
27693
|
}
|
|
27239
27694
|
break;
|
|
27240
27695
|
}
|
|
27241
27696
|
case "emoji":
|
|
27242
|
-
addPattern(res, zodPatterns.emoji(),
|
|
27697
|
+
addPattern(res, zodPatterns.emoji(), check3.message, refs);
|
|
27243
27698
|
break;
|
|
27244
27699
|
case "ulid": {
|
|
27245
|
-
addPattern(res, zodPatterns.ulid,
|
|
27700
|
+
addPattern(res, zodPatterns.ulid, check3.message, refs);
|
|
27246
27701
|
break;
|
|
27247
27702
|
}
|
|
27248
27703
|
case "base64": {
|
|
27249
27704
|
switch (refs.base64Strategy) {
|
|
27250
27705
|
case "format:binary": {
|
|
27251
|
-
addFormat(res, "binary",
|
|
27706
|
+
addFormat(res, "binary", check3.message, refs);
|
|
27252
27707
|
break;
|
|
27253
27708
|
}
|
|
27254
27709
|
case "contentEncoding:base64": {
|
|
27255
|
-
setResponseValueAndErrors(res, "contentEncoding", "base64",
|
|
27710
|
+
setResponseValueAndErrors(res, "contentEncoding", "base64", check3.message, refs);
|
|
27256
27711
|
break;
|
|
27257
27712
|
}
|
|
27258
27713
|
case "pattern:zod": {
|
|
27259
|
-
addPattern(res, zodPatterns.base64,
|
|
27714
|
+
addPattern(res, zodPatterns.base64, check3.message, refs);
|
|
27260
27715
|
break;
|
|
27261
27716
|
}
|
|
27262
27717
|
}
|
|
27263
27718
|
break;
|
|
27264
27719
|
}
|
|
27265
27720
|
case "nanoid": {
|
|
27266
|
-
addPattern(res, zodPatterns.nanoid,
|
|
27721
|
+
addPattern(res, zodPatterns.nanoid, check3.message, refs);
|
|
27267
27722
|
}
|
|
27268
27723
|
case "toLowerCase":
|
|
27269
27724
|
case "toUpperCase":
|
|
27270
27725
|
case "trim":
|
|
27271
27726
|
break;
|
|
27272
27727
|
default:
|
|
27273
|
-
((_) => {})(
|
|
27728
|
+
((_) => {})(check3);
|
|
27274
27729
|
}
|
|
27275
27730
|
}
|
|
27276
27731
|
}
|
|
@@ -27639,42 +28094,42 @@ function parseNumberDef(def, refs) {
|
|
|
27639
28094
|
};
|
|
27640
28095
|
if (!def.checks)
|
|
27641
28096
|
return res;
|
|
27642
|
-
for (const
|
|
27643
|
-
switch (
|
|
28097
|
+
for (const check3 of def.checks) {
|
|
28098
|
+
switch (check3.kind) {
|
|
27644
28099
|
case "int":
|
|
27645
28100
|
res.type = "integer";
|
|
27646
|
-
addErrorMessage(res, "type",
|
|
28101
|
+
addErrorMessage(res, "type", check3.message, refs);
|
|
27647
28102
|
break;
|
|
27648
28103
|
case "min":
|
|
27649
28104
|
if (refs.target === "jsonSchema7") {
|
|
27650
|
-
if (
|
|
27651
|
-
setResponseValueAndErrors(res, "minimum",
|
|
28105
|
+
if (check3.inclusive) {
|
|
28106
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27652
28107
|
} else {
|
|
27653
|
-
setResponseValueAndErrors(res, "exclusiveMinimum",
|
|
28108
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check3.value, check3.message, refs);
|
|
27654
28109
|
}
|
|
27655
28110
|
} else {
|
|
27656
|
-
if (!
|
|
28111
|
+
if (!check3.inclusive) {
|
|
27657
28112
|
res.exclusiveMinimum = true;
|
|
27658
28113
|
}
|
|
27659
|
-
setResponseValueAndErrors(res, "minimum",
|
|
28114
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27660
28115
|
}
|
|
27661
28116
|
break;
|
|
27662
28117
|
case "max":
|
|
27663
28118
|
if (refs.target === "jsonSchema7") {
|
|
27664
|
-
if (
|
|
27665
|
-
setResponseValueAndErrors(res, "maximum",
|
|
28119
|
+
if (check3.inclusive) {
|
|
28120
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27666
28121
|
} else {
|
|
27667
|
-
setResponseValueAndErrors(res, "exclusiveMaximum",
|
|
28122
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check3.value, check3.message, refs);
|
|
27668
28123
|
}
|
|
27669
28124
|
} else {
|
|
27670
|
-
if (!
|
|
28125
|
+
if (!check3.inclusive) {
|
|
27671
28126
|
res.exclusiveMaximum = true;
|
|
27672
28127
|
}
|
|
27673
|
-
setResponseValueAndErrors(res, "maximum",
|
|
28128
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27674
28129
|
}
|
|
27675
28130
|
break;
|
|
27676
28131
|
case "multipleOf":
|
|
27677
|
-
setResponseValueAndErrors(res, "multipleOf",
|
|
28132
|
+
setResponseValueAndErrors(res, "multipleOf", check3.value, check3.message, refs);
|
|
27678
28133
|
break;
|
|
27679
28134
|
}
|
|
27680
28135
|
}
|
|
@@ -30238,7 +30693,11 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30238
30693
|
// src/mcp/server.ts
|
|
30239
30694
|
var MACHINE_MCP_TOOL_NAMES = [
|
|
30240
30695
|
"machines_status",
|
|
30696
|
+
"machines_doctor",
|
|
30697
|
+
"machines_self_test",
|
|
30241
30698
|
"machines_apps_list",
|
|
30699
|
+
"machines_apps_status",
|
|
30700
|
+
"machines_apps_diff",
|
|
30242
30701
|
"machines_apps_plan",
|
|
30243
30702
|
"machines_apps_apply",
|
|
30244
30703
|
"machines_manifest",
|
|
@@ -30254,6 +30713,8 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
30254
30713
|
"machines_diff",
|
|
30255
30714
|
"machines_install_tailscale_preview",
|
|
30256
30715
|
"machines_install_tailscale_apply",
|
|
30716
|
+
"machines_install_claude_status",
|
|
30717
|
+
"machines_install_claude_diff",
|
|
30257
30718
|
"machines_install_claude_preview",
|
|
30258
30719
|
"machines_install_claude_apply",
|
|
30259
30720
|
"machines_ssh_resolve",
|
|
@@ -30268,30 +30729,25 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
30268
30729
|
"machines_notifications_add",
|
|
30269
30730
|
"machines_notifications_list",
|
|
30270
30731
|
"machines_notifications_test",
|
|
30732
|
+
"machines_notifications_dispatch",
|
|
30271
30733
|
"machines_notifications_remove",
|
|
30272
30734
|
"machines_serve_info",
|
|
30273
30735
|
"machines_serve_dashboard"
|
|
30274
30736
|
];
|
|
30275
30737
|
function createMcpServer(version2) {
|
|
30276
|
-
const server = new McpServer({
|
|
30277
|
-
name: "machines",
|
|
30278
|
-
version: version2
|
|
30279
|
-
});
|
|
30738
|
+
const server = new McpServer({ name: "machines", version: version2 });
|
|
30280
30739
|
server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
|
|
30281
30740
|
content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
|
|
30282
30741
|
}));
|
|
30283
|
-
server.tool("
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
server.tool("machines_apps_plan", "Preview app install steps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30287
|
-
content: [{ type: "text", text: JSON.stringify(buildAppsPlan(machine_id), null, 2) }]
|
|
30288
|
-
}));
|
|
30289
|
-
server.tool("machines_apps_apply", "Install manifest-managed apps for a machine.", {
|
|
30290
|
-
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30291
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30292
|
-
}, async ({ machine_id, yes }) => ({
|
|
30293
|
-
content: [{ type: "text", text: JSON.stringify(runAppsInstall(machine_id, { apply: true, yes }), null, 2) }]
|
|
30742
|
+
server.tool("machines_doctor", "Run machine preflight checks.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(runDoctor(machine_id), null, 2) }] }));
|
|
30743
|
+
server.tool("machines_self_test", "Run local package smoke checks.", {}, async () => ({
|
|
30744
|
+
content: [{ type: "text", text: JSON.stringify(runSelfTest(), null, 2) }]
|
|
30294
30745
|
}));
|
|
30746
|
+
server.tool("machines_apps_list", "List manifest-managed apps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(listApps(machine_id), null, 2) }] }));
|
|
30747
|
+
server.tool("machines_apps_status", "Check installed state for manifest-managed apps.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(getAppsStatus(machine_id), null, 2) }] }));
|
|
30748
|
+
server.tool("machines_apps_diff", "Show missing and installed manifest-managed apps.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(diffApps(machine_id), null, 2) }] }));
|
|
30749
|
+
server.tool("machines_apps_plan", "Preview app install steps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildAppsPlan(machine_id), null, 2) }] }));
|
|
30750
|
+
server.tool("machines_apps_apply", "Install manifest-managed apps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runAppsInstall(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
30295
30751
|
server.tool("machines_manifest", "Read the current fleet manifest.", {}, async () => ({
|
|
30296
30752
|
content: [{ type: "text", text: JSON.stringify(manifestList(), null, 2) }]
|
|
30297
30753
|
}));
|
|
@@ -30301,45 +30757,33 @@ function createMcpServer(version2) {
|
|
|
30301
30757
|
server.tool("machines_manifest_bootstrap", "Detect and upsert the current machine into the fleet manifest.", {}, async () => ({
|
|
30302
30758
|
content: [{ type: "text", text: JSON.stringify(manifestBootstrapCurrentMachine(), null, 2) }]
|
|
30303
30759
|
}));
|
|
30304
|
-
server.tool("machines_manifest_get", "Read a single machine from the fleet manifest.", { machine_id: exports_external2.string().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30305
|
-
|
|
30306
|
-
}));
|
|
30307
|
-
server.tool("machines_manifest_remove", "Remove a single machine from the fleet manifest.", { machine_id: exports_external2.string().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30308
|
-
content: [{ type: "text", text: JSON.stringify(manifestRemove(machine_id), null, 2) }]
|
|
30309
|
-
}));
|
|
30760
|
+
server.tool("machines_manifest_get", "Read a single machine from the fleet manifest.", { machine_id: exports_external2.string().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(manifestGet(machine_id), null, 2) }] }));
|
|
30761
|
+
server.tool("machines_manifest_remove", "Remove a single machine from the fleet manifest.", { machine_id: exports_external2.string().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(manifestRemove(machine_id), null, 2) }] }));
|
|
30310
30762
|
server.tool("machines_agent_status", "List current machine agent heartbeats.", {}, async () => ({
|
|
30311
30763
|
content: [{ type: "text", text: JSON.stringify(getAgentStatus(), null, 2) }]
|
|
30312
30764
|
}));
|
|
30313
|
-
server.tool("machines_setup_preview", "Preview setup actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30314
|
-
|
|
30315
|
-
}));
|
|
30316
|
-
server.tool("
|
|
30317
|
-
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30318
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30319
|
-
}, async ({ machine_id, yes }) => ({
|
|
30320
|
-
content: [{ type: "text", text: JSON.stringify(runSetup(machine_id, { apply: true, yes }), null, 2) }]
|
|
30321
|
-
}));
|
|
30322
|
-
server.tool("machines_sync_preview", "Preview sync actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30323
|
-
content: [{ type: "text", text: JSON.stringify(buildSyncPlan(machine_id), null, 2) }]
|
|
30324
|
-
}));
|
|
30325
|
-
server.tool("machines_sync_apply", "Execute sync actions for a machine.", {
|
|
30326
|
-
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30327
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30328
|
-
}, async ({ machine_id, yes }) => ({
|
|
30329
|
-
content: [{ type: "text", text: JSON.stringify(runSync(machine_id, { apply: true, yes }), null, 2) }]
|
|
30330
|
-
}));
|
|
30765
|
+
server.tool("machines_setup_preview", "Preview setup actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildSetupPlan(machine_id), null, 2) }] }));
|
|
30766
|
+
server.tool("machines_setup_apply", "Execute setup actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSetup(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
30767
|
+
server.tool("machines_sync_preview", "Preview sync actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildSyncPlan(machine_id), null, 2) }] }));
|
|
30768
|
+
server.tool("machines_sync_apply", "Execute sync actions for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSync(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
30331
30769
|
server.tool("machines_diff", "Show manifest differences between two machines.", {
|
|
30332
30770
|
left_machine_id: exports_external2.string().describe("Left machine identifier"),
|
|
30333
30771
|
right_machine_id: exports_external2.string().optional().describe("Right machine identifier")
|
|
30334
30772
|
}, async ({ left_machine_id, right_machine_id }) => ({
|
|
30335
30773
|
content: [{ type: "text", text: JSON.stringify(diffMachines(left_machine_id, right_machine_id), null, 2) }]
|
|
30336
30774
|
}));
|
|
30775
|
+
server.tool("machines_install_claude_status", "Check installed state for Claude, Codex, and Gemini CLIs.", {
|
|
30776
|
+
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30777
|
+
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
30778
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(getClaudeCliStatus(machine_id, tools), null, 2) }] }));
|
|
30779
|
+
server.tool("machines_install_claude_diff", "Show missing and installed Claude, Codex, and Gemini CLIs.", {
|
|
30780
|
+
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30781
|
+
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
30782
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(diffClaudeCli(machine_id, tools), null, 2) }] }));
|
|
30337
30783
|
server.tool("machines_install_claude_preview", "Preview Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
30338
30784
|
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30339
30785
|
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to install")
|
|
30340
|
-
}, async ({ machine_id, tools }) => ({
|
|
30341
|
-
content: [{ type: "text", text: JSON.stringify(buildClaudeInstallPlan(machine_id, tools), null, 2) }]
|
|
30342
|
-
}));
|
|
30786
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(buildClaudeInstallPlan(machine_id, tools), null, 2) }] }));
|
|
30343
30787
|
server.tool("machines_install_claude_apply", "Execute Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
30344
30788
|
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30345
30789
|
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to install"),
|
|
@@ -30347,73 +30791,21 @@ function createMcpServer(version2) {
|
|
|
30347
30791
|
}, async ({ machine_id, tools, yes }) => ({
|
|
30348
30792
|
content: [{ type: "text", text: JSON.stringify(runClaudeInstall(machine_id, tools, { apply: true, yes }), null, 2) }]
|
|
30349
30793
|
}));
|
|
30350
|
-
server.tool("machines_install_tailscale_preview", "Preview Tailscale install steps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30351
|
-
|
|
30352
|
-
})
|
|
30353
|
-
|
|
30354
|
-
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30355
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30356
|
-
}, async ({ machine_id, yes }) => ({
|
|
30357
|
-
content: [{ type: "text", text: JSON.stringify(runTailscaleInstall(machine_id, { apply: true, yes }), null, 2) }]
|
|
30794
|
+
server.tool("machines_install_tailscale_preview", "Preview Tailscale install steps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildTailscaleInstallPlan(machine_id), null, 2) }] }));
|
|
30795
|
+
server.tool("machines_install_tailscale_apply", "Execute Tailscale install steps for a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runTailscaleInstall(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
30796
|
+
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", { machine_id: exports_external2.string().describe("Machine identifier"), remote_command: exports_external2.string().optional().describe("Optional remote command") }, async ({ machine_id, remote_command }) => ({
|
|
30797
|
+
content: [{ type: "text", text: JSON.stringify({ resolved: resolveSshTarget(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
30358
30798
|
}));
|
|
30359
|
-
server.tool("
|
|
30360
|
-
machine_id: exports_external2.string().describe("Machine identifier"),
|
|
30361
|
-
remote_command: exports_external2.string().optional().describe("Optional remote command")
|
|
30362
|
-
}, async ({ machine_id, remote_command }) => ({
|
|
30363
|
-
content: [
|
|
30364
|
-
{
|
|
30365
|
-
type: "text",
|
|
30366
|
-
text: JSON.stringify({
|
|
30367
|
-
resolved: resolveSshTarget(machine_id),
|
|
30368
|
-
command: buildSshCommand(machine_id, remote_command)
|
|
30369
|
-
}, null, 2)
|
|
30370
|
-
}
|
|
30371
|
-
]
|
|
30372
|
-
}));
|
|
30373
|
-
server.tool("machines_ports", "List listening ports on a machine.", {
|
|
30374
|
-
machine_id: exports_external2.string().optional().describe("Machine identifier")
|
|
30375
|
-
}, async ({ machine_id }) => ({
|
|
30799
|
+
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30376
30800
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
30377
30801
|
}));
|
|
30378
|
-
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", {
|
|
30379
|
-
|
|
30380
|
-
|
|
30381
|
-
}, async ({
|
|
30382
|
-
|
|
30383
|
-
}));
|
|
30384
|
-
server.tool("
|
|
30385
|
-
bucket: exports_external2.string().describe("S3 bucket name"),
|
|
30386
|
-
prefix: exports_external2.string().optional().describe("S3 key prefix"),
|
|
30387
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30388
|
-
}, async ({ bucket, prefix, yes }) => ({
|
|
30389
|
-
content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }]
|
|
30390
|
-
}));
|
|
30391
|
-
server.tool("machines_cert_preview", "Preview mkcert steps for one or more domains.", {
|
|
30392
|
-
domains: exports_external2.array(exports_external2.string()).describe("Domains to issue certificates for")
|
|
30393
|
-
}, async ({ domains }) => ({
|
|
30394
|
-
content: [{ type: "text", text: JSON.stringify(buildCertPlan(domains), null, 2) }]
|
|
30395
|
-
}));
|
|
30396
|
-
server.tool("machines_cert_apply", "Execute mkcert steps for one or more domains.", {
|
|
30397
|
-
domains: exports_external2.array(exports_external2.string()).describe("Domains to issue certificates for"),
|
|
30398
|
-
yes: exports_external2.boolean().describe("Confirmation flag for execution")
|
|
30399
|
-
}, async ({ domains, yes }) => ({
|
|
30400
|
-
content: [{ type: "text", text: JSON.stringify(runCertPlan(domains, { apply: true, yes }), null, 2) }]
|
|
30401
|
-
}));
|
|
30402
|
-
server.tool("machines_dns_add", "Add or replace a local domain mapping.", {
|
|
30403
|
-
domain: exports_external2.string().describe("Domain name"),
|
|
30404
|
-
port: exports_external2.number().describe("Target port"),
|
|
30405
|
-
target_host: exports_external2.string().optional().describe("Target host")
|
|
30406
|
-
}, async ({ domain, port, target_host }) => ({
|
|
30407
|
-
content: [{ type: "text", text: JSON.stringify(addDomainMapping(domain, port, target_host), null, 2) }]
|
|
30408
|
-
}));
|
|
30409
|
-
server.tool("machines_dns_list", "List local domain mappings.", {}, async () => ({
|
|
30410
|
-
content: [{ type: "text", text: JSON.stringify(listDomainMappings(), null, 2) }]
|
|
30411
|
-
}));
|
|
30412
|
-
server.tool("machines_dns_render", "Render hosts/proxy configuration for a domain.", {
|
|
30413
|
-
domain: exports_external2.string().describe("Domain name")
|
|
30414
|
-
}, async ({ domain }) => ({
|
|
30415
|
-
content: [{ type: "text", text: JSON.stringify(renderDomainMapping(domain), null, 2) }]
|
|
30416
|
-
}));
|
|
30802
|
+
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", { bucket: exports_external2.string().describe("S3 bucket name"), prefix: exports_external2.string().optional().describe("S3 key prefix") }, async ({ bucket, prefix }) => ({ content: [{ type: "text", text: JSON.stringify(buildBackupPlan(bucket, prefix), null, 2) }] }));
|
|
30803
|
+
server.tool("machines_backup_apply", "Execute backup steps for the current machine.", { bucket: exports_external2.string().describe("S3 bucket name"), prefix: exports_external2.string().optional().describe("S3 key prefix"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ bucket, prefix, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }] }));
|
|
30804
|
+
server.tool("machines_cert_preview", "Preview mkcert steps for one or more domains.", { domains: exports_external2.array(exports_external2.string()).describe("Domains to issue certificates for") }, async ({ domains }) => ({ content: [{ type: "text", text: JSON.stringify(buildCertPlan(domains), null, 2) }] }));
|
|
30805
|
+
server.tool("machines_cert_apply", "Execute mkcert steps for one or more domains.", { domains: exports_external2.array(exports_external2.string()).describe("Domains to issue certificates for"), yes: exports_external2.boolean().describe("Confirmation flag for execution") }, async ({ domains, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runCertPlan(domains, { apply: true, yes }), null, 2) }] }));
|
|
30806
|
+
server.tool("machines_dns_add", "Add or replace a local domain mapping.", { domain: exports_external2.string().describe("Domain name"), port: exports_external2.number().describe("Target port"), target_host: exports_external2.string().optional().describe("Target host") }, async ({ domain, port, target_host }) => ({ content: [{ type: "text", text: JSON.stringify(addDomainMapping(domain, port, target_host), null, 2) }] }));
|
|
30807
|
+
server.tool("machines_dns_list", "List local domain mappings.", {}, async () => ({ content: [{ type: "text", text: JSON.stringify(listDomainMappings(), null, 2) }] }));
|
|
30808
|
+
server.tool("machines_dns_render", "Render hosts/proxy configuration for a domain.", { domain: exports_external2.string().describe("Domain name") }, async ({ domain }) => ({ content: [{ type: "text", text: JSON.stringify(renderDomainMapping(domain), null, 2) }] }));
|
|
30417
30809
|
server.tool("machines_notifications_add", "Add or replace a notification channel.", {
|
|
30418
30810
|
channel_id: exports_external2.string().describe("Channel identifier"),
|
|
30419
30811
|
type: exports_external2.enum(["email", "webhook", "command"]).describe("Notification transport"),
|
|
@@ -30421,68 +30813,22 @@ function createMcpServer(version2) {
|
|
|
30421
30813
|
events: exports_external2.array(exports_external2.string()).describe("Events routed to this channel"),
|
|
30422
30814
|
enabled: exports_external2.boolean().optional().describe("Whether the channel is enabled")
|
|
30423
30815
|
}, async ({ channel_id, type, target, events, enabled }) => ({
|
|
30424
|
-
content: [
|
|
30425
|
-
{
|
|
30426
|
-
type: "text",
|
|
30427
|
-
text: JSON.stringify(addNotificationChannel({
|
|
30428
|
-
id: channel_id,
|
|
30429
|
-
type,
|
|
30430
|
-
target,
|
|
30431
|
-
events,
|
|
30432
|
-
enabled: enabled ?? true
|
|
30433
|
-
}), null, 2)
|
|
30434
|
-
}
|
|
30435
|
-
]
|
|
30816
|
+
content: [{ type: "text", text: JSON.stringify(addNotificationChannel({ id: channel_id, type, target, events, enabled: enabled ?? true }), null, 2) }]
|
|
30436
30817
|
}));
|
|
30437
30818
|
server.tool("machines_notifications_list", "List notification channels.", {}, async () => ({
|
|
30438
30819
|
content: [{ type: "text", text: JSON.stringify(listNotificationChannels(), null, 2) }]
|
|
30439
30820
|
}));
|
|
30440
|
-
server.tool("machines_notifications_test", "Preview or execute a notification test.", {
|
|
30441
|
-
|
|
30442
|
-
event: exports_external2.string().optional().describe("Event name"),
|
|
30443
|
-
message: exports_external2.string().optional().describe("Message body"),
|
|
30444
|
-
yes: exports_external2.boolean().optional().describe("Execute the test when true")
|
|
30445
|
-
}, async ({ channel_id, event, message, yes }) => ({
|
|
30446
|
-
content: [
|
|
30447
|
-
{
|
|
30448
|
-
type: "text",
|
|
30449
|
-
text: JSON.stringify(testNotificationChannel(channel_id, event, message, { apply: Boolean(yes), yes }), null, 2)
|
|
30450
|
-
}
|
|
30451
|
-
]
|
|
30452
|
-
}));
|
|
30453
|
-
server.tool("machines_notifications_remove", "Remove a notification channel.", {
|
|
30454
|
-
channel_id: exports_external2.string().describe("Channel identifier")
|
|
30455
|
-
}, async ({ channel_id }) => ({
|
|
30456
|
-
content: [{ type: "text", text: JSON.stringify(removeNotificationChannel(channel_id), null, 2) }]
|
|
30457
|
-
}));
|
|
30458
|
-
server.tool("machines_serve_info", "Preview the dashboard server bind address and routes.", {
|
|
30459
|
-
host: exports_external2.string().optional().describe("Host interface"),
|
|
30460
|
-
port: exports_external2.number().optional().describe("Port number")
|
|
30461
|
-
}, async ({ host, port }) => ({
|
|
30462
|
-
content: [{ type: "text", text: JSON.stringify(getServeInfo({ host, port }), null, 2) }]
|
|
30821
|
+
server.tool("machines_notifications_test", "Preview or execute a notification test.", { channel_id: exports_external2.string().describe("Channel identifier"), event: exports_external2.string().optional().describe("Event name"), message: exports_external2.string().optional().describe("Message body"), yes: exports_external2.boolean().optional().describe("Execute the test when true") }, async ({ channel_id, event, message, yes }) => ({
|
|
30822
|
+
content: [{ type: "text", text: JSON.stringify(await testNotificationChannel(channel_id, event, message, { apply: Boolean(yes), yes }), null, 2) }]
|
|
30463
30823
|
}));
|
|
30824
|
+
server.tool("machines_notifications_dispatch", "Dispatch an event to matching notification channels.", { event: exports_external2.string().describe("Event name"), message: exports_external2.string().describe("Message body"), channel_id: exports_external2.string().optional().describe("Limit delivery to one channel") }, async ({ event, message, channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(await dispatchNotificationEvent(event, message, { channelId: channel_id }), null, 2) }] }));
|
|
30825
|
+
server.tool("machines_notifications_remove", "Remove a notification channel.", { channel_id: exports_external2.string().describe("Channel identifier") }, async ({ channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(removeNotificationChannel(channel_id), null, 2) }] }));
|
|
30826
|
+
server.tool("machines_serve_info", "Preview the dashboard server bind address and routes.", { host: exports_external2.string().optional().describe("Host interface"), port: exports_external2.number().optional().describe("Port number") }, async ({ host, port }) => ({ content: [{ type: "text", text: JSON.stringify(getServeInfo({ host, port }), null, 2) }] }));
|
|
30464
30827
|
server.tool("machines_serve_dashboard", "Render the current dashboard HTML.", {}, async () => ({
|
|
30465
30828
|
content: [{ type: "text", text: renderDashboardHtml() }]
|
|
30466
30829
|
}));
|
|
30467
30830
|
return server;
|
|
30468
30831
|
}
|
|
30469
|
-
// src/version.ts
|
|
30470
|
-
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
30471
|
-
import { dirname as dirname4, join as join9 } from "path";
|
|
30472
|
-
import { fileURLToPath } from "url";
|
|
30473
|
-
function getPackageVersion() {
|
|
30474
|
-
try {
|
|
30475
|
-
const here = dirname4(fileURLToPath(import.meta.url));
|
|
30476
|
-
const candidates = [join9(here, "..", "package.json"), join9(here, "..", "..", "package.json")];
|
|
30477
|
-
const pkgPath = candidates.find((candidate) => existsSync9(candidate));
|
|
30478
|
-
if (!pkgPath) {
|
|
30479
|
-
return "0.0.0";
|
|
30480
|
-
}
|
|
30481
|
-
return JSON.parse(readFileSync6(pkgPath, "utf8")).version || "0.0.0";
|
|
30482
|
-
} catch {
|
|
30483
|
-
return "0.0.0";
|
|
30484
|
-
}
|
|
30485
|
-
}
|
|
30486
30832
|
export {
|
|
30487
30833
|
writeNotificationConfig,
|
|
30488
30834
|
writeManifest,
|
|
@@ -30495,6 +30841,8 @@ export {
|
|
|
30495
30841
|
runTailscaleInstall,
|
|
30496
30842
|
runSync,
|
|
30497
30843
|
runSetup,
|
|
30844
|
+
runSelfTest,
|
|
30845
|
+
runDoctor,
|
|
30498
30846
|
runClaudeInstall,
|
|
30499
30847
|
runCertPlan,
|
|
30500
30848
|
runBackup,
|
|
@@ -30534,12 +30882,17 @@ export {
|
|
|
30534
30882
|
getDbPath,
|
|
30535
30883
|
getDb,
|
|
30536
30884
|
getDataDir,
|
|
30885
|
+
getClaudeCliStatus,
|
|
30886
|
+
getAppsStatus,
|
|
30537
30887
|
getAgentStatus,
|
|
30538
30888
|
getAdapter,
|
|
30539
30889
|
fleetSchema,
|
|
30540
30890
|
ensureParentDir,
|
|
30541
30891
|
ensureDataDir,
|
|
30892
|
+
dispatchNotificationEvent,
|
|
30542
30893
|
diffMachines,
|
|
30894
|
+
diffClaudeCli,
|
|
30895
|
+
diffApps,
|
|
30543
30896
|
detectCurrentMachineManifest,
|
|
30544
30897
|
createMcpServer,
|
|
30545
30898
|
countRuns,
|