@hasna/machines 0.0.2 → 0.0.4
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 +966 -151
- 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/clipboard-server.d.ts +17 -0
- package/dist/commands/clipboard-server.d.ts.map +1 -0
- package/dist/commands/clipboard.d.ts +19 -0
- package/dist/commands/clipboard.d.ts.map +1 -0
- 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/cross-project-types.d.ts +26 -0
- package/dist/cross-project-types.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +751 -318
- package/dist/mcp/index.js +1893 -1516
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.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 +98 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6515,6 +6515,8 @@ var require_dist2 = __commonJS((exports, module) => {
|
|
|
6515
6515
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6516
6516
|
exports.default = formatsPlugin;
|
|
6517
6517
|
});
|
|
6518
|
+
// src/cross-project-types.ts
|
|
6519
|
+
var CROSSREFS_KEY = "_crossRefs";
|
|
6518
6520
|
// src/paths.ts
|
|
6519
6521
|
import { existsSync, mkdirSync } from "fs";
|
|
6520
6522
|
import { dirname, join, resolve } from "path";
|
|
@@ -6533,6 +6535,12 @@ function getManifestPath() {
|
|
|
6533
6535
|
function getNotificationsPath() {
|
|
6534
6536
|
return process.env["HASNA_MACHINES_NOTIFICATIONS_PATH"] || join(getDataDir(), "notifications.json");
|
|
6535
6537
|
}
|
|
6538
|
+
function getClipboardKeyPath() {
|
|
6539
|
+
return process.env["HASNA_MACHINES_CLIPBOARD_KEY_PATH"] || join(getDataDir(), "clipboard.key");
|
|
6540
|
+
}
|
|
6541
|
+
function getClipboardHistoryPath() {
|
|
6542
|
+
return process.env["HASNA_MACHINES_CLIPBOARD_HISTORY_PATH"] || join(getDataDir(), "clipboard-history.json");
|
|
6543
|
+
}
|
|
6536
6544
|
function ensureParentDir(filePath) {
|
|
6537
6545
|
if (filePath === ":memory:")
|
|
6538
6546
|
return;
|
|
@@ -20335,17 +20343,95 @@ function runBackup(bucket, prefix = "machines", options = {}) {
|
|
|
20335
20343
|
executed
|
|
20336
20344
|
};
|
|
20337
20345
|
}
|
|
20346
|
+
// src/remote.ts
|
|
20347
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
20348
|
+
|
|
20349
|
+
// src/commands/ssh.ts
|
|
20350
|
+
import { spawnSync } from "child_process";
|
|
20351
|
+
function envReachableHosts() {
|
|
20352
|
+
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
20353
|
+
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
20354
|
+
}
|
|
20355
|
+
function isReachable(host) {
|
|
20356
|
+
const overrides = envReachableHosts();
|
|
20357
|
+
if (overrides.size > 0) {
|
|
20358
|
+
return overrides.has(host);
|
|
20359
|
+
}
|
|
20360
|
+
const probe = spawnSync("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
|
|
20361
|
+
stdio: "ignore"
|
|
20362
|
+
});
|
|
20363
|
+
return probe.status === 0;
|
|
20364
|
+
}
|
|
20365
|
+
function resolveSshTarget(machineId) {
|
|
20366
|
+
const machine = getManifestMachine(machineId);
|
|
20367
|
+
if (!machine) {
|
|
20368
|
+
throw new Error(`Machine not found in manifest: ${machineId}`);
|
|
20369
|
+
}
|
|
20370
|
+
const current = detectCurrentMachineManifest();
|
|
20371
|
+
if (machine.id === current.id) {
|
|
20372
|
+
return {
|
|
20373
|
+
machineId,
|
|
20374
|
+
target: "localhost",
|
|
20375
|
+
route: "local"
|
|
20376
|
+
};
|
|
20377
|
+
}
|
|
20378
|
+
const lanTarget = machine.sshAddress || machine.hostname || machine.id;
|
|
20379
|
+
const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
|
|
20380
|
+
const route = isReachable(lanTarget) ? "lan" : "tailscale";
|
|
20381
|
+
return {
|
|
20382
|
+
machineId,
|
|
20383
|
+
target: route === "lan" ? lanTarget : tailscaleTarget,
|
|
20384
|
+
route
|
|
20385
|
+
};
|
|
20386
|
+
}
|
|
20387
|
+
function buildSshCommand(machineId, remoteCommand) {
|
|
20388
|
+
const resolved = resolveSshTarget(machineId);
|
|
20389
|
+
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
20390
|
+
}
|
|
20391
|
+
|
|
20392
|
+
// src/remote.ts
|
|
20393
|
+
function runMachineCommand(machineId, command) {
|
|
20394
|
+
const localMachineId = getLocalMachineId();
|
|
20395
|
+
const isLocal = machineId === localMachineId;
|
|
20396
|
+
const route = isLocal ? "local" : resolveSshTarget(machineId).route;
|
|
20397
|
+
const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
|
|
20398
|
+
const result = spawnSync2("bash", ["-lc", shellCommand], {
|
|
20399
|
+
encoding: "utf8",
|
|
20400
|
+
env: process.env
|
|
20401
|
+
});
|
|
20402
|
+
return {
|
|
20403
|
+
machineId,
|
|
20404
|
+
source: route,
|
|
20405
|
+
stdout: result.stdout || "",
|
|
20406
|
+
stderr: result.stderr || "",
|
|
20407
|
+
exitCode: result.status ?? 1
|
|
20408
|
+
};
|
|
20409
|
+
}
|
|
20410
|
+
|
|
20338
20411
|
// src/commands/apps.ts
|
|
20339
20412
|
function getPackageName(app) {
|
|
20340
20413
|
return app.packageName || app.name;
|
|
20341
20414
|
}
|
|
20415
|
+
function getAppManager(machine, app) {
|
|
20416
|
+
if (app.manager)
|
|
20417
|
+
return app.manager;
|
|
20418
|
+
if (machine.platform === "macos")
|
|
20419
|
+
return "brew";
|
|
20420
|
+
if (machine.platform === "windows")
|
|
20421
|
+
return "winget";
|
|
20422
|
+
return "apt";
|
|
20423
|
+
}
|
|
20424
|
+
function shellQuote(value) {
|
|
20425
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20426
|
+
}
|
|
20342
20427
|
function buildAppCommand(machine, app) {
|
|
20343
20428
|
const packageName = getPackageName(app);
|
|
20344
|
-
|
|
20429
|
+
const manager = getAppManager(machine, app);
|
|
20430
|
+
if (manager === "custom") {
|
|
20345
20431
|
return packageName;
|
|
20346
20432
|
}
|
|
20347
20433
|
if (machine.platform === "macos") {
|
|
20348
|
-
if (
|
|
20434
|
+
if (manager === "cask") {
|
|
20349
20435
|
return `brew install --cask ${packageName}`;
|
|
20350
20436
|
}
|
|
20351
20437
|
return `brew install ${packageName}`;
|
|
@@ -20355,18 +20441,48 @@ function buildAppCommand(machine, app) {
|
|
|
20355
20441
|
}
|
|
20356
20442
|
return `sudo apt-get install -y ${packageName}`;
|
|
20357
20443
|
}
|
|
20444
|
+
function buildAppProbeCommand(machine, app) {
|
|
20445
|
+
const packageName = shellQuote(getPackageName(app));
|
|
20446
|
+
const manager = getAppManager(machine, app);
|
|
20447
|
+
if (manager === "custom") {
|
|
20448
|
+
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
20449
|
+
}
|
|
20450
|
+
if (machine.platform === "macos") {
|
|
20451
|
+
if (manager === "cask") {
|
|
20452
|
+
return `if brew list --cask ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
20453
|
+
}
|
|
20454
|
+
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`;
|
|
20455
|
+
}
|
|
20456
|
+
if (machine.platform === "windows") {
|
|
20457
|
+
return `if winget list --id ${packageName} --exact >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
20458
|
+
}
|
|
20459
|
+
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`;
|
|
20460
|
+
}
|
|
20358
20461
|
function buildAppSteps(machine) {
|
|
20359
20462
|
return (machine.apps || []).map((app) => ({
|
|
20360
20463
|
id: `app-${app.name}`,
|
|
20361
20464
|
title: `Install ${app.name} on ${machine.id}`,
|
|
20362
20465
|
command: buildAppCommand(machine, app),
|
|
20363
|
-
manager: app
|
|
20466
|
+
manager: getAppManager(machine, app) === "custom" ? "custom" : machine.platform === "macos" ? "brew" : machine.platform === "windows" ? "custom" : "apt",
|
|
20364
20467
|
privileged: machine.platform === "linux"
|
|
20365
20468
|
}));
|
|
20366
20469
|
}
|
|
20367
20470
|
function resolveMachine(machineId) {
|
|
20368
20471
|
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
20369
20472
|
}
|
|
20473
|
+
function parseProbeOutput(app, machine, stdout) {
|
|
20474
|
+
const lines = stdout.trim().split(`
|
|
20475
|
+
`).filter(Boolean);
|
|
20476
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
20477
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
20478
|
+
return {
|
|
20479
|
+
name: app.name,
|
|
20480
|
+
packageName: getPackageName(app),
|
|
20481
|
+
manager: getAppManager(machine, app),
|
|
20482
|
+
installed: installedLine === "installed=1",
|
|
20483
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
20484
|
+
};
|
|
20485
|
+
}
|
|
20370
20486
|
function listApps(machineId) {
|
|
20371
20487
|
const machine = resolveMachine(machineId);
|
|
20372
20488
|
return {
|
|
@@ -20383,6 +20499,26 @@ function buildAppsPlan(machineId) {
|
|
|
20383
20499
|
executed: 0
|
|
20384
20500
|
};
|
|
20385
20501
|
}
|
|
20502
|
+
function getAppsStatus(machineId) {
|
|
20503
|
+
const machine = resolveMachine(machineId);
|
|
20504
|
+
const apps = (machine.apps || []).map((app) => {
|
|
20505
|
+
const probe = runMachineCommand(machine.id, buildAppProbeCommand(machine, app));
|
|
20506
|
+
return parseProbeOutput(app, machine, probe.stdout);
|
|
20507
|
+
});
|
|
20508
|
+
return {
|
|
20509
|
+
machineId: machine.id,
|
|
20510
|
+
source: apps.length > 0 ? runMachineCommand(machine.id, "true").source : machine.id === detectCurrentMachineManifest().id ? "local" : runMachineCommand(machine.id, "true").source,
|
|
20511
|
+
apps
|
|
20512
|
+
};
|
|
20513
|
+
}
|
|
20514
|
+
function diffApps(machineId) {
|
|
20515
|
+
const status = getAppsStatus(machineId);
|
|
20516
|
+
return {
|
|
20517
|
+
...status,
|
|
20518
|
+
missing: status.apps.filter((app) => !app.installed).map((app) => app.name),
|
|
20519
|
+
installed: status.apps.filter((app) => app.installed).map((app) => app.name)
|
|
20520
|
+
};
|
|
20521
|
+
}
|
|
20386
20522
|
function runAppsInstall(machineId, options = {}) {
|
|
20387
20523
|
const plan = buildAppsPlan(machineId);
|
|
20388
20524
|
if (!options.apply)
|
|
@@ -20529,6 +20665,58 @@ function renderDomainMapping(domain) {
|
|
|
20529
20665
|
keyPath: join8(getDataDir(), "certs", `${entry.domain}-key.pem`)
|
|
20530
20666
|
};
|
|
20531
20667
|
}
|
|
20668
|
+
// src/commands/doctor.ts
|
|
20669
|
+
function makeCheck(id, status, summary, detail) {
|
|
20670
|
+
return { id, status, summary, detail };
|
|
20671
|
+
}
|
|
20672
|
+
function parseKeyValueOutput(stdout) {
|
|
20673
|
+
return Object.fromEntries(stdout.trim().split(`
|
|
20674
|
+
`).map((line) => line.split("=")).filter((parts) => parts.length === 2).map(([key, value]) => [key, value]));
|
|
20675
|
+
}
|
|
20676
|
+
function buildDoctorCommand() {
|
|
20677
|
+
return [
|
|
20678
|
+
'data_dir="${HASNA_MACHINES_DIR:-$HOME/.hasna/machines}"',
|
|
20679
|
+
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
20680
|
+
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
20681
|
+
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
20682
|
+
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
20683
|
+
`printf 'db_path=%s\\n' "$db_path"`,
|
|
20684
|
+
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
20685
|
+
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
20686
|
+
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
20687
|
+
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
20688
|
+
`printf 'bun=%s\\n' "$(bun --version 2>/dev/null || printf missing)"`,
|
|
20689
|
+
`printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
|
|
20690
|
+
`printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
|
|
20691
|
+
`printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
|
|
20692
|
+
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
20693
|
+
].join("; ");
|
|
20694
|
+
}
|
|
20695
|
+
function runDoctor(machineId = getLocalMachineId()) {
|
|
20696
|
+
const manifest = readManifest();
|
|
20697
|
+
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
20698
|
+
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
20699
|
+
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
20700
|
+
const checks = [
|
|
20701
|
+
makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
|
|
20702
|
+
makeCheck("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20703
|
+
makeCheck("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20704
|
+
makeCheck("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
|
|
20705
|
+
makeCheck("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
20706
|
+
makeCheck("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
20707
|
+
makeCheck("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
20708
|
+
makeCheck("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
20709
|
+
makeCheck("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
20710
|
+
];
|
|
20711
|
+
return {
|
|
20712
|
+
machineId,
|
|
20713
|
+
source: commandChecks.source,
|
|
20714
|
+
manifestPath: details["manifest_path"],
|
|
20715
|
+
dbPath: details["db_path"],
|
|
20716
|
+
notificationsPath: details["notifications_path"],
|
|
20717
|
+
checks
|
|
20718
|
+
};
|
|
20719
|
+
}
|
|
20532
20720
|
// src/commands/manifest.ts
|
|
20533
20721
|
function manifestInit() {
|
|
20534
20722
|
return writeManifest(getDefaultManifest(), getManifestPath());
|
|
@@ -20608,6 +20796,13 @@ var AI_CLI_PACKAGES = {
|
|
|
20608
20796
|
codex: "@openai/codex",
|
|
20609
20797
|
gemini: "@google/gemini-cli"
|
|
20610
20798
|
};
|
|
20799
|
+
function getToolBinary(tool) {
|
|
20800
|
+
if (tool === "claude")
|
|
20801
|
+
return process.env["HASNA_MACHINES_CLAUDE_BINARY"] || "claude";
|
|
20802
|
+
if (tool === "codex")
|
|
20803
|
+
return process.env["HASNA_MACHINES_CODEX_BINARY"] || "codex";
|
|
20804
|
+
return process.env["HASNA_MACHINES_GEMINI_BINARY"] || "gemini";
|
|
20805
|
+
}
|
|
20611
20806
|
function normalizeTools(tools) {
|
|
20612
20807
|
if (!tools || tools.length === 0) {
|
|
20613
20808
|
return ["claude", "codex", "gemini"];
|
|
@@ -20627,8 +20822,27 @@ function buildInstallSteps(machine, tools) {
|
|
|
20627
20822
|
manager: "bun"
|
|
20628
20823
|
}));
|
|
20629
20824
|
}
|
|
20825
|
+
function resolveMachine2(machineId) {
|
|
20826
|
+
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
20827
|
+
}
|
|
20828
|
+
function buildProbeCommand(tool) {
|
|
20829
|
+
const binary = getToolBinary(tool);
|
|
20830
|
+
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`;
|
|
20831
|
+
}
|
|
20832
|
+
function parseProbe(tool, stdout) {
|
|
20833
|
+
const lines = stdout.trim().split(`
|
|
20834
|
+
`).filter(Boolean);
|
|
20835
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
20836
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
20837
|
+
return {
|
|
20838
|
+
tool,
|
|
20839
|
+
packageName: AI_CLI_PACKAGES[tool],
|
|
20840
|
+
installed: installedLine === "installed=1",
|
|
20841
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
20842
|
+
};
|
|
20843
|
+
}
|
|
20630
20844
|
function buildClaudeInstallPlan(machineId, tools) {
|
|
20631
|
-
const machine = (machineId
|
|
20845
|
+
const machine = resolveMachine2(machineId);
|
|
20632
20846
|
return {
|
|
20633
20847
|
machineId: machine.id,
|
|
20634
20848
|
mode: "plan",
|
|
@@ -20636,6 +20850,24 @@ function buildClaudeInstallPlan(machineId, tools) {
|
|
|
20636
20850
|
executed: 0
|
|
20637
20851
|
};
|
|
20638
20852
|
}
|
|
20853
|
+
function getClaudeCliStatus(machineId, tools) {
|
|
20854
|
+
const machine = resolveMachine2(machineId);
|
|
20855
|
+
const normalizedTools = normalizeTools(tools);
|
|
20856
|
+
const route = runMachineCommand(machine.id, "true").source;
|
|
20857
|
+
return {
|
|
20858
|
+
machineId: machine.id,
|
|
20859
|
+
source: route,
|
|
20860
|
+
tools: normalizedTools.map((tool) => parseProbe(tool, runMachineCommand(machine.id, buildProbeCommand(tool)).stdout))
|
|
20861
|
+
};
|
|
20862
|
+
}
|
|
20863
|
+
function diffClaudeCli(machineId, tools) {
|
|
20864
|
+
const status = getClaudeCliStatus(machineId, tools);
|
|
20865
|
+
return {
|
|
20866
|
+
...status,
|
|
20867
|
+
missing: status.tools.filter((tool) => !tool.installed).map((tool) => tool.tool),
|
|
20868
|
+
installed: status.tools.filter((tool) => tool.installed).map((tool) => tool.tool)
|
|
20869
|
+
};
|
|
20870
|
+
}
|
|
20639
20871
|
function runClaudeInstall(machineId, tools, options = {}) {
|
|
20640
20872
|
const plan = buildClaudeInstallPlan(machineId, tools);
|
|
20641
20873
|
if (!options.apply)
|
|
@@ -20746,6 +20978,138 @@ var notificationConfigSchema = exports_external2.object({
|
|
|
20746
20978
|
function sortChannels(channels) {
|
|
20747
20979
|
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
20748
20980
|
}
|
|
20981
|
+
function shellQuote2(value) {
|
|
20982
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20983
|
+
}
|
|
20984
|
+
function hasCommand(binary) {
|
|
20985
|
+
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
|
|
20986
|
+
stdout: "ignore",
|
|
20987
|
+
stderr: "ignore",
|
|
20988
|
+
env: process.env
|
|
20989
|
+
});
|
|
20990
|
+
return result.exitCode === 0;
|
|
20991
|
+
}
|
|
20992
|
+
function buildNotificationPreview(channel, event, message) {
|
|
20993
|
+
if (channel.type === "email") {
|
|
20994
|
+
return `send email to ${channel.target}: [${event}] ${message}`;
|
|
20995
|
+
}
|
|
20996
|
+
if (channel.type === "webhook") {
|
|
20997
|
+
return `POST ${channel.target} with payload {"event":"${event}","message":"${message}"}`;
|
|
20998
|
+
}
|
|
20999
|
+
return `${channel.target} --event ${event} --message ${JSON.stringify(message)}`;
|
|
21000
|
+
}
|
|
21001
|
+
async function dispatchEmail(channel, event, message) {
|
|
21002
|
+
const subject = `[${event}] machines notification`;
|
|
21003
|
+
const body = `To: ${channel.target}
|
|
21004
|
+
Subject: ${subject}
|
|
21005
|
+
Content-Type: text/plain; charset=utf-8
|
|
21006
|
+
|
|
21007
|
+
${message}
|
|
21008
|
+
`;
|
|
21009
|
+
if (hasCommand("sendmail")) {
|
|
21010
|
+
const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
|
|
21011
|
+
stdin: new TextEncoder().encode(body),
|
|
21012
|
+
stdout: "pipe",
|
|
21013
|
+
stderr: "pipe",
|
|
21014
|
+
env: process.env
|
|
21015
|
+
});
|
|
21016
|
+
if (result.exitCode !== 0) {
|
|
21017
|
+
throw new Error(result.stderr.toString().trim() || `sendmail exited with ${result.exitCode}`);
|
|
21018
|
+
}
|
|
21019
|
+
return {
|
|
21020
|
+
channelId: channel.id,
|
|
21021
|
+
event,
|
|
21022
|
+
delivered: true,
|
|
21023
|
+
transport: channel.type,
|
|
21024
|
+
detail: `Delivered via sendmail to ${channel.target}`
|
|
21025
|
+
};
|
|
21026
|
+
}
|
|
21027
|
+
if (hasCommand("mail")) {
|
|
21028
|
+
const command = `printf %s ${shellQuote2(message)} | mail -s ${shellQuote2(subject)} ${shellQuote2(channel.target)}`;
|
|
21029
|
+
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
21030
|
+
stdout: "pipe",
|
|
21031
|
+
stderr: "pipe",
|
|
21032
|
+
env: process.env
|
|
21033
|
+
});
|
|
21034
|
+
if (result.exitCode !== 0) {
|
|
21035
|
+
throw new Error(result.stderr.toString().trim() || `mail exited with ${result.exitCode}`);
|
|
21036
|
+
}
|
|
21037
|
+
return {
|
|
21038
|
+
channelId: channel.id,
|
|
21039
|
+
event,
|
|
21040
|
+
delivered: true,
|
|
21041
|
+
transport: channel.type,
|
|
21042
|
+
detail: `Delivered via mail to ${channel.target}`
|
|
21043
|
+
};
|
|
21044
|
+
}
|
|
21045
|
+
throw new Error("No local email transport available. Install sendmail or mail.");
|
|
21046
|
+
}
|
|
21047
|
+
async function dispatchWebhook(channel, event, message) {
|
|
21048
|
+
const response = await fetch(channel.target, {
|
|
21049
|
+
method: "POST",
|
|
21050
|
+
headers: {
|
|
21051
|
+
"content-type": "application/json"
|
|
21052
|
+
},
|
|
21053
|
+
body: JSON.stringify({
|
|
21054
|
+
channelId: channel.id,
|
|
21055
|
+
event,
|
|
21056
|
+
message,
|
|
21057
|
+
sentAt: new Date().toISOString()
|
|
21058
|
+
})
|
|
21059
|
+
});
|
|
21060
|
+
if (!response.ok) {
|
|
21061
|
+
const text = await response.text();
|
|
21062
|
+
throw new Error(`Webhook responded ${response.status}: ${text || response.statusText}`);
|
|
21063
|
+
}
|
|
21064
|
+
return {
|
|
21065
|
+
channelId: channel.id,
|
|
21066
|
+
event,
|
|
21067
|
+
delivered: true,
|
|
21068
|
+
transport: channel.type,
|
|
21069
|
+
detail: `Webhook accepted with HTTP ${response.status}`
|
|
21070
|
+
};
|
|
21071
|
+
}
|
|
21072
|
+
async function dispatchCommand(channel, event, message) {
|
|
21073
|
+
const result = Bun.spawnSync(["bash", "-lc", channel.target], {
|
|
21074
|
+
stdout: "pipe",
|
|
21075
|
+
stderr: "pipe",
|
|
21076
|
+
env: {
|
|
21077
|
+
...process.env,
|
|
21078
|
+
HASNA_MACHINES_NOTIFICATION_CHANNEL: channel.id,
|
|
21079
|
+
HASNA_MACHINES_NOTIFICATION_EVENT: event,
|
|
21080
|
+
HASNA_MACHINES_NOTIFICATION_MESSAGE: message
|
|
21081
|
+
}
|
|
21082
|
+
});
|
|
21083
|
+
if (result.exitCode !== 0) {
|
|
21084
|
+
throw new Error(result.stderr.toString().trim() || `command exited with ${result.exitCode}`);
|
|
21085
|
+
}
|
|
21086
|
+
const stdout = result.stdout.toString().trim();
|
|
21087
|
+
return {
|
|
21088
|
+
channelId: channel.id,
|
|
21089
|
+
event,
|
|
21090
|
+
delivered: true,
|
|
21091
|
+
transport: channel.type,
|
|
21092
|
+
detail: stdout || "Command completed successfully"
|
|
21093
|
+
};
|
|
21094
|
+
}
|
|
21095
|
+
async function dispatchChannel(channel, event, message) {
|
|
21096
|
+
if (!channel.enabled) {
|
|
21097
|
+
return {
|
|
21098
|
+
channelId: channel.id,
|
|
21099
|
+
event,
|
|
21100
|
+
delivered: false,
|
|
21101
|
+
transport: channel.type,
|
|
21102
|
+
detail: "Channel is disabled"
|
|
21103
|
+
};
|
|
21104
|
+
}
|
|
21105
|
+
if (channel.type === "email") {
|
|
21106
|
+
return dispatchEmail(channel, event, message);
|
|
21107
|
+
}
|
|
21108
|
+
if (channel.type === "webhook") {
|
|
21109
|
+
return dispatchWebhook(channel, event, message);
|
|
21110
|
+
}
|
|
21111
|
+
return dispatchCommand(channel, event, message);
|
|
21112
|
+
}
|
|
20749
21113
|
function getDefaultNotificationConfig() {
|
|
20750
21114
|
return {
|
|
20751
21115
|
version: 1,
|
|
@@ -20789,16 +21153,34 @@ function removeNotificationChannel(channelId) {
|
|
|
20789
21153
|
channels: config.channels.filter((channel) => channel.id !== channelId)
|
|
20790
21154
|
});
|
|
20791
21155
|
}
|
|
20792
|
-
function
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
return
|
|
21156
|
+
async function dispatchNotificationEvent(event, message, options = {}) {
|
|
21157
|
+
const channels = readNotificationConfig().channels.filter((channel) => {
|
|
21158
|
+
if (options.channelId && channel.id !== options.channelId) {
|
|
21159
|
+
return false;
|
|
21160
|
+
}
|
|
21161
|
+
return channel.events.includes(event) || event === "manual.test";
|
|
21162
|
+
});
|
|
21163
|
+
const deliveries = [];
|
|
21164
|
+
for (const channel of channels) {
|
|
21165
|
+
try {
|
|
21166
|
+
deliveries.push(await dispatchChannel(channel, event, message));
|
|
21167
|
+
} catch (error) {
|
|
21168
|
+
deliveries.push({
|
|
21169
|
+
channelId: channel.id,
|
|
21170
|
+
event,
|
|
21171
|
+
delivered: false,
|
|
21172
|
+
transport: channel.type,
|
|
21173
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
21174
|
+
});
|
|
21175
|
+
}
|
|
20798
21176
|
}
|
|
20799
|
-
return
|
|
21177
|
+
return {
|
|
21178
|
+
event,
|
|
21179
|
+
message,
|
|
21180
|
+
deliveries
|
|
21181
|
+
};
|
|
20800
21182
|
}
|
|
20801
|
-
function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
21183
|
+
async function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
20802
21184
|
const channel = readNotificationConfig().channels.find((entry) => entry.id === channelId);
|
|
20803
21185
|
if (!channel) {
|
|
20804
21186
|
throw new Error(`Notification channel not found: ${channelId}`);
|
|
@@ -20809,76 +21191,24 @@ function testNotificationChannel(channelId, event = "manual.test", message = "ma
|
|
|
20809
21191
|
channelId,
|
|
20810
21192
|
mode: "plan",
|
|
20811
21193
|
delivered: false,
|
|
20812
|
-
preview
|
|
21194
|
+
preview,
|
|
21195
|
+
detail: "Preview only"
|
|
20813
21196
|
};
|
|
20814
21197
|
}
|
|
20815
21198
|
if (!options.yes) {
|
|
20816
21199
|
throw new Error("Notification test execution requires --yes.");
|
|
20817
21200
|
}
|
|
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
|
-
}
|
|
21201
|
+
const delivery = await dispatchChannel(channel, event, message);
|
|
20828
21202
|
return {
|
|
20829
21203
|
channelId,
|
|
20830
21204
|
mode: "apply",
|
|
20831
|
-
delivered:
|
|
20832
|
-
preview
|
|
21205
|
+
delivered: delivery.delivered,
|
|
21206
|
+
preview,
|
|
21207
|
+
detail: delivery.detail
|
|
20833
21208
|
};
|
|
20834
21209
|
}
|
|
20835
21210
|
// src/commands/ports.ts
|
|
20836
|
-
import { spawnSync as
|
|
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
|
|
20874
|
-
};
|
|
20875
|
-
}
|
|
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
|
-
// src/commands/ports.ts
|
|
21211
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
20882
21212
|
function parseSsOutput(output) {
|
|
20883
21213
|
return output.trim().split(`
|
|
20884
21214
|
`).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
@@ -20920,7 +21250,7 @@ function listPorts(machineId) {
|
|
|
20920
21250
|
const isLocal = targetMachineId === getLocalMachineId();
|
|
20921
21251
|
const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
|
|
20922
21252
|
const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
|
|
20923
|
-
const result =
|
|
21253
|
+
const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
|
|
20924
21254
|
if (result.status !== 0) {
|
|
20925
21255
|
throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
|
|
20926
21256
|
}
|
|
@@ -20930,6 +21260,24 @@ function listPorts(machineId) {
|
|
|
20930
21260
|
listeners: parsePortOutput(result.stdout, format)
|
|
20931
21261
|
};
|
|
20932
21262
|
}
|
|
21263
|
+
// src/version.ts
|
|
21264
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
21265
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
21266
|
+
import { fileURLToPath } from "url";
|
|
21267
|
+
function getPackageVersion() {
|
|
21268
|
+
try {
|
|
21269
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
21270
|
+
const candidates = [join9(here, "..", "package.json"), join9(here, "..", "..", "package.json")];
|
|
21271
|
+
const pkgPath = candidates.find((candidate) => existsSync8(candidate));
|
|
21272
|
+
if (!pkgPath) {
|
|
21273
|
+
return "0.0.0";
|
|
21274
|
+
}
|
|
21275
|
+
return JSON.parse(readFileSync5(pkgPath, "utf8")).version || "0.0.0";
|
|
21276
|
+
} catch {
|
|
21277
|
+
return "0.0.0";
|
|
21278
|
+
}
|
|
21279
|
+
}
|
|
21280
|
+
|
|
20933
21281
|
// src/commands/status.ts
|
|
20934
21282
|
function getStatus() {
|
|
20935
21283
|
const manifest = readManifest();
|
|
@@ -20973,13 +21321,27 @@ function getServeInfo(options = {}) {
|
|
|
20973
21321
|
host,
|
|
20974
21322
|
port,
|
|
20975
21323
|
url: `http://${host}:${port}`,
|
|
20976
|
-
routes: [
|
|
21324
|
+
routes: [
|
|
21325
|
+
"/",
|
|
21326
|
+
"/health",
|
|
21327
|
+
"/api/status",
|
|
21328
|
+
"/api/manifest",
|
|
21329
|
+
"/api/notifications",
|
|
21330
|
+
"/api/doctor",
|
|
21331
|
+
"/api/self-test",
|
|
21332
|
+
"/api/apps/status",
|
|
21333
|
+
"/api/apps/diff",
|
|
21334
|
+
"/api/install-claude/status",
|
|
21335
|
+
"/api/install-claude/diff",
|
|
21336
|
+
"/api/notifications/test"
|
|
21337
|
+
]
|
|
20977
21338
|
};
|
|
20978
21339
|
}
|
|
20979
21340
|
function renderDashboardHtml() {
|
|
20980
21341
|
const status = getStatus();
|
|
20981
21342
|
const manifest = manifestList();
|
|
20982
21343
|
const notifications = listNotificationChannels();
|
|
21344
|
+
const doctor = runDoctor();
|
|
20983
21345
|
return `<!doctype html>
|
|
20984
21346
|
<html lang="en">
|
|
20985
21347
|
<head>
|
|
@@ -20995,21 +21357,26 @@ function renderDashboardHtml() {
|
|
|
20995
21357
|
.card { background: #121933; border: 1px solid #243057; border-radius: 16px; padding: 20px; }
|
|
20996
21358
|
.stat { font-size: 32px; font-weight: 700; margin-top: 8px; }
|
|
20997
21359
|
table { width: 100%; border-collapse: collapse; }
|
|
20998
|
-
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; }
|
|
21360
|
+
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; vertical-align: top; }
|
|
20999
21361
|
code { color: #9ed0ff; }
|
|
21000
21362
|
.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; }
|
|
21363
|
+
.online, .ok { background: #12351f; color: #74f0a7; }
|
|
21364
|
+
.offline, .fail { background: #3b1a1a; color: #ff8c8c; }
|
|
21365
|
+
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
21366
|
+
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
21367
|
+
.muted { color: #9fb0d9; }
|
|
21368
|
+
.refresh { font-size: 12px; color: #6b7fa3; margin-left: auto; }
|
|
21369
|
+
.updated { transition: opacity 0.3s; }
|
|
21004
21370
|
</style>
|
|
21005
21371
|
</head>
|
|
21006
21372
|
<body>
|
|
21007
21373
|
<main>
|
|
21008
|
-
<h1>Machines Dashboard
|
|
21374
|
+
<h1>Machines Dashboard <span class="refresh" id="last-updated"></span></h1>
|
|
21009
21375
|
<div class="grid">
|
|
21010
21376
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
21011
21377
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
21012
21378
|
<section class="card"><div>Notification channels</div><div class="stat">${notifications.channels.length}</div></section>
|
|
21379
|
+
<section class="card"><div>Doctor warnings</div><div class="stat">${doctor.checks.filter((entry) => entry.status !== "ok").length}</div></section>
|
|
21013
21380
|
</div>
|
|
21014
21381
|
|
|
21015
21382
|
<section class="card" style="margin-top:16px">
|
|
@@ -21027,21 +21394,119 @@ function renderDashboardHtml() {
|
|
|
21027
21394
|
</table>
|
|
21028
21395
|
</section>
|
|
21029
21396
|
|
|
21397
|
+
<section class="card" style="margin-top:16px">
|
|
21398
|
+
<h2>Doctor</h2>
|
|
21399
|
+
<table>
|
|
21400
|
+
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
21401
|
+
<tbody id="doctor-tbody">
|
|
21402
|
+
${doctor.checks.map((entry) => `<tr>
|
|
21403
|
+
<td>${escapeHtml(entry.summary)}</td>
|
|
21404
|
+
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
21405
|
+
<td class="muted">${escapeHtml(entry.detail)}</td>
|
|
21406
|
+
</tr>`).join("")}
|
|
21407
|
+
</tbody>
|
|
21408
|
+
</table>
|
|
21409
|
+
</section>
|
|
21410
|
+
|
|
21411
|
+
<section class="card" style="margin-top:16px">
|
|
21412
|
+
<h2>Apps</h2>
|
|
21413
|
+
<p class="muted">Use <code>/api/apps/status</code> for the full app inventory payload.</p>
|
|
21414
|
+
</section>
|
|
21415
|
+
|
|
21416
|
+
<section class="card" style="margin-top:16px">
|
|
21417
|
+
<h2>AI CLIs</h2>
|
|
21418
|
+
<p class="muted">Use <code>/api/install-claude/status</code> for the full CLI inventory payload.</p>
|
|
21419
|
+
</section>
|
|
21420
|
+
|
|
21421
|
+
<section class="card" style="margin-top:16px">
|
|
21422
|
+
<h2>Self Test</h2>
|
|
21423
|
+
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
21424
|
+
</section>
|
|
21425
|
+
|
|
21030
21426
|
<section class="card" style="margin-top:16px">
|
|
21031
21427
|
<h2>Manifest</h2>
|
|
21032
|
-
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
21428
|
+
<pre id="manifest-json">${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
21033
21429
|
</section>
|
|
21034
21430
|
</main>
|
|
21431
|
+
<script>
|
|
21432
|
+
// Auto-refresh dashboard data every 15s
|
|
21433
|
+
const REFRESH_INTERVAL = 15000;
|
|
21434
|
+
async function refreshData() {
|
|
21435
|
+
try {
|
|
21436
|
+
const [statusRes, doctorRes] = await Promise.all([
|
|
21437
|
+
fetch("/api/status"),
|
|
21438
|
+
fetch("/api/doctor"),
|
|
21439
|
+
]);
|
|
21440
|
+
const status = await statusRes.json();
|
|
21441
|
+
const doctor = await doctorRes.json();
|
|
21442
|
+
|
|
21443
|
+
// Update stat cards
|
|
21444
|
+
const stats = document.querySelectorAll(".stat");
|
|
21445
|
+
if (stats[0]) stats[0].textContent = status.manifestMachineCount;
|
|
21446
|
+
if (stats[1]) stats[1].textContent = status.heartbeatCount;
|
|
21447
|
+
|
|
21448
|
+
// Update machine table
|
|
21449
|
+
const tbody = document.querySelector("tbody");
|
|
21450
|
+
if (tbody && status.machines) {
|
|
21451
|
+
tbody.innerHTML = status.machines
|
|
21452
|
+
.map((m) =>
|
|
21453
|
+
"<tr>" +
|
|
21454
|
+
"<td><code>" + m.machineId + "</code></td>" +
|
|
21455
|
+
"<td>" + (m.platform || "unknown") + "</td>" +
|
|
21456
|
+
'<td><span class="badge ' + m.heartbeatStatus + '">' + m.heartbeatStatus + '</span></td>' +
|
|
21457
|
+
"<td>" + (m.lastHeartbeatAt || "\\u2014") + "</td>" +
|
|
21458
|
+
"</tr>"
|
|
21459
|
+
)
|
|
21460
|
+
.join("");
|
|
21461
|
+
}
|
|
21462
|
+
|
|
21463
|
+
// Update doctor table
|
|
21464
|
+
const doctorTbody = document.getElementById("doctor-tbody");
|
|
21465
|
+
if (doctorTbody && doctor.checks) {
|
|
21466
|
+
doctorTbody.innerHTML = doctor.checks
|
|
21467
|
+
.map((c) =>
|
|
21468
|
+
"<tr>" +
|
|
21469
|
+
"<td>" + c.summary + "</td>" +
|
|
21470
|
+
'<td><span class="badge ' + c.status + '">' + c.status + '</span></td>' +
|
|
21471
|
+
'<td class="muted">' + c.detail + "</td>" +
|
|
21472
|
+
"</tr>"
|
|
21473
|
+
)
|
|
21474
|
+
.join("");
|
|
21475
|
+
}
|
|
21476
|
+
|
|
21477
|
+
// Update timestamp
|
|
21478
|
+
document.getElementById("last-updated").textContent =
|
|
21479
|
+
"updated " + new Date().toLocaleTimeString();
|
|
21480
|
+
} catch (e) {
|
|
21481
|
+
// Silently ignore fetch errors during page unload
|
|
21482
|
+
}
|
|
21483
|
+
}
|
|
21484
|
+
document.getElementById("last-updated").textContent =
|
|
21485
|
+
"updated " + new Date().toLocaleTimeString();
|
|
21486
|
+
setInterval(refreshData, REFRESH_INTERVAL);
|
|
21487
|
+
</script>
|
|
21035
21488
|
</body>
|
|
21036
21489
|
</html>`;
|
|
21037
21490
|
}
|
|
21491
|
+
async function parseJsonBody(request) {
|
|
21492
|
+
try {
|
|
21493
|
+
return await request.json();
|
|
21494
|
+
} catch {
|
|
21495
|
+
return {};
|
|
21496
|
+
}
|
|
21497
|
+
}
|
|
21498
|
+
function jsonError(message, status = 400) {
|
|
21499
|
+
return Response.json({ error: message }, { status });
|
|
21500
|
+
}
|
|
21038
21501
|
function startDashboardServer(options = {}) {
|
|
21039
21502
|
const info = getServeInfo(options);
|
|
21040
21503
|
return Bun.serve({
|
|
21041
21504
|
hostname: info.host,
|
|
21042
21505
|
port: info.port,
|
|
21043
|
-
fetch(request) {
|
|
21506
|
+
async fetch(request) {
|
|
21044
21507
|
const url = new URL(request.url);
|
|
21508
|
+
const machineId = url.searchParams.get("machine") || undefined;
|
|
21509
|
+
const tools = url.searchParams.get("tools")?.split(",").map((value) => value.trim()).filter(Boolean);
|
|
21045
21510
|
if (url.pathname === "/health") {
|
|
21046
21511
|
return Response.json({ ok: true, ...getServeInfo(options) });
|
|
21047
21512
|
}
|
|
@@ -21054,6 +21519,43 @@ function startDashboardServer(options = {}) {
|
|
|
21054
21519
|
if (url.pathname === "/api/notifications") {
|
|
21055
21520
|
return Response.json(listNotificationChannels());
|
|
21056
21521
|
}
|
|
21522
|
+
if (url.pathname === "/api/doctor") {
|
|
21523
|
+
return Response.json(runDoctor(machineId));
|
|
21524
|
+
}
|
|
21525
|
+
if (url.pathname === "/api/self-test") {
|
|
21526
|
+
return Response.json(runSelfTest());
|
|
21527
|
+
}
|
|
21528
|
+
if (url.pathname === "/api/apps/status") {
|
|
21529
|
+
return Response.json(getAppsStatus(machineId));
|
|
21530
|
+
}
|
|
21531
|
+
if (url.pathname === "/api/apps/diff") {
|
|
21532
|
+
return Response.json(diffApps(machineId));
|
|
21533
|
+
}
|
|
21534
|
+
if (url.pathname === "/api/install-claude/status") {
|
|
21535
|
+
return Response.json(getClaudeCliStatus(machineId, tools));
|
|
21536
|
+
}
|
|
21537
|
+
if (url.pathname === "/api/install-claude/diff") {
|
|
21538
|
+
return Response.json(diffClaudeCli(machineId, tools));
|
|
21539
|
+
}
|
|
21540
|
+
if (url.pathname === "/api/notifications/test") {
|
|
21541
|
+
if (request.method !== "POST") {
|
|
21542
|
+
return jsonError("Use POST for notification tests.", 405);
|
|
21543
|
+
}
|
|
21544
|
+
const body = await parseJsonBody(request);
|
|
21545
|
+
const channelId = typeof body["channelId"] === "string" ? body["channelId"] : undefined;
|
|
21546
|
+
if (!channelId) {
|
|
21547
|
+
return jsonError("channelId is required.");
|
|
21548
|
+
}
|
|
21549
|
+
const event = typeof body["event"] === "string" ? body["event"] : undefined;
|
|
21550
|
+
const message = typeof body["message"] === "string" ? body["message"] : undefined;
|
|
21551
|
+
const apply = body["apply"] === true;
|
|
21552
|
+
const yes = body["yes"] === true;
|
|
21553
|
+
try {
|
|
21554
|
+
return Response.json(await testNotificationChannel(channelId, event, message, { apply, yes }));
|
|
21555
|
+
} catch (error) {
|
|
21556
|
+
return jsonError(error instanceof Error ? error.message : String(error));
|
|
21557
|
+
}
|
|
21558
|
+
}
|
|
21057
21559
|
return new Response(renderDashboardHtml(), {
|
|
21058
21560
|
headers: {
|
|
21059
21561
|
"content-type": "text/html; charset=utf-8"
|
|
@@ -21062,6 +21564,36 @@ function startDashboardServer(options = {}) {
|
|
|
21062
21564
|
}
|
|
21063
21565
|
});
|
|
21064
21566
|
}
|
|
21567
|
+
|
|
21568
|
+
// src/commands/self-test.ts
|
|
21569
|
+
function check(id, status, summary, detail) {
|
|
21570
|
+
return { id, status, summary, detail };
|
|
21571
|
+
}
|
|
21572
|
+
function runSelfTest() {
|
|
21573
|
+
const version = getPackageVersion();
|
|
21574
|
+
const status = getStatus();
|
|
21575
|
+
const doctor = runDoctor();
|
|
21576
|
+
const serveInfo = getServeInfo();
|
|
21577
|
+
const html = renderDashboardHtml();
|
|
21578
|
+
const notifications = listNotificationChannels();
|
|
21579
|
+
const apps = listApps(status.machineId);
|
|
21580
|
+
const appsDiff = diffApps(status.machineId);
|
|
21581
|
+
const cliPlan = buildClaudeInstallPlan(status.machineId);
|
|
21582
|
+
return {
|
|
21583
|
+
machineId: getLocalMachineId(),
|
|
21584
|
+
checks: [
|
|
21585
|
+
check("package-version", version === "0.0.0" ? "fail" : "ok", "Package version resolves", version),
|
|
21586
|
+
check("status", "ok", "Status loads", JSON.stringify({ machines: status.manifestMachineCount, heartbeats: status.heartbeatCount })),
|
|
21587
|
+
check("doctor", doctor.checks.some((entry) => entry.status === "fail") ? "warn" : "ok", "Doctor completed", `${doctor.checks.length} checks`),
|
|
21588
|
+
check("serve-info", "ok", "Dashboard info renders", `${serveInfo.url} routes=${serveInfo.routes.length}`),
|
|
21589
|
+
check("dashboard-html", html.includes("Machines Dashboard") ? "ok" : "fail", "Dashboard HTML renders", html.slice(0, 80)),
|
|
21590
|
+
check("notifications", "ok", "Notifications config loads", `${notifications.channels.length} channels`),
|
|
21591
|
+
check("apps", "ok", "Apps manifest loads", `${apps.apps.length} apps`),
|
|
21592
|
+
check("apps-diff", appsDiff.missing.length === 0 ? "ok" : "warn", "Apps diff computed", `missing=${appsDiff.missing.length} installed=${appsDiff.installed.length}`),
|
|
21593
|
+
check("install-claude-plan", cliPlan.steps.length > 0 ? "ok" : "warn", "Install plan renders", `${cliPlan.steps.length} steps`)
|
|
21594
|
+
]
|
|
21595
|
+
};
|
|
21596
|
+
}
|
|
21065
21597
|
// src/commands/setup.ts
|
|
21066
21598
|
function quote3(value) {
|
|
21067
21599
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -21177,7 +21709,7 @@ function runSetup(machineId, options = {}) {
|
|
|
21177
21709
|
return summary;
|
|
21178
21710
|
}
|
|
21179
21711
|
// src/commands/sync.ts
|
|
21180
|
-
import { existsSync as
|
|
21712
|
+
import { existsSync as existsSync9, lstatSync, readFileSync as readFileSync6, symlinkSync, copyFileSync as copyFileSync2 } from "fs";
|
|
21181
21713
|
function quote4(value) {
|
|
21182
21714
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21183
21715
|
}
|
|
@@ -21208,12 +21740,12 @@ function packageInstallCommand(machine, packageName, manager = machine.platform
|
|
|
21208
21740
|
function detectPackageActions(machine) {
|
|
21209
21741
|
return (machine.packages || []).map((pkg, index) => {
|
|
21210
21742
|
const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
|
|
21211
|
-
const
|
|
21743
|
+
const check2 = Bun.spawnSync(["bash", "-lc", packageCheckCommand(machine, pkg.name, manager)], {
|
|
21212
21744
|
stdout: "ignore",
|
|
21213
21745
|
stderr: "ignore",
|
|
21214
21746
|
env: process.env
|
|
21215
21747
|
});
|
|
21216
|
-
const installed =
|
|
21748
|
+
const installed = check2.exitCode === 0;
|
|
21217
21749
|
return {
|
|
21218
21750
|
id: `package-${index + 1}`,
|
|
21219
21751
|
title: `${installed ? "Package present" : "Install package"} ${pkg.name}`,
|
|
@@ -21225,15 +21757,15 @@ function detectPackageActions(machine) {
|
|
|
21225
21757
|
}
|
|
21226
21758
|
function detectFileActions(machine) {
|
|
21227
21759
|
return (machine.files || []).map((file, index) => {
|
|
21228
|
-
const sourceExists =
|
|
21229
|
-
const targetExists =
|
|
21760
|
+
const sourceExists = existsSync9(file.source);
|
|
21761
|
+
const targetExists = existsSync9(file.target);
|
|
21230
21762
|
let status = "missing";
|
|
21231
21763
|
if (sourceExists && targetExists) {
|
|
21232
21764
|
if (file.mode === "symlink") {
|
|
21233
21765
|
status = lstatSync(file.target).isSymbolicLink() ? "ok" : "drifted";
|
|
21234
21766
|
} else {
|
|
21235
|
-
const source =
|
|
21236
|
-
const target =
|
|
21767
|
+
const source = readFileSync6(file.source, "utf8");
|
|
21768
|
+
const target = readFileSync6(file.target, "utf8");
|
|
21237
21769
|
status = source === target ? "ok" : "drifted";
|
|
21238
21770
|
}
|
|
21239
21771
|
}
|
|
@@ -25384,7 +25916,7 @@ var ZodType3 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
25384
25916
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
25385
25917
|
inst.safeParseAsync = async (data, params) => safeParseAsync3(inst, data, params);
|
|
25386
25918
|
inst.spa = inst.safeParseAsync;
|
|
25387
|
-
inst.refine = (
|
|
25919
|
+
inst.refine = (check2, params) => inst.check(refine(check2, params));
|
|
25388
25920
|
inst.superRefine = (refinement) => inst.check(superRefine(refinement));
|
|
25389
25921
|
inst.overwrite = (fn) => inst.check(_overwrite(fn));
|
|
25390
25922
|
inst.optional = () => optional(inst);
|
|
@@ -25928,7 +26460,7 @@ var ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
25928
26460
|
$ZodCustom.init(inst, def);
|
|
25929
26461
|
ZodType3.init(inst, def);
|
|
25930
26462
|
});
|
|
25931
|
-
function
|
|
26463
|
+
function check2(fn) {
|
|
25932
26464
|
const ch = new $ZodCheck({
|
|
25933
26465
|
check: "custom"
|
|
25934
26466
|
});
|
|
@@ -25942,7 +26474,7 @@ function refine(fn, _params = {}) {
|
|
|
25942
26474
|
return _refine(ZodCustom, fn, _params);
|
|
25943
26475
|
}
|
|
25944
26476
|
function superRefine(fn) {
|
|
25945
|
-
const ch =
|
|
26477
|
+
const ch = check2((payload) => {
|
|
25946
26478
|
payload.addIssue = (issue2) => {
|
|
25947
26479
|
if (typeof issue2 === "string") {
|
|
25948
26480
|
payload.issues.push(exports_util.issue(issue2, payload.value, ch._zod.def));
|
|
@@ -26941,38 +27473,38 @@ function parseBigintDef(def, refs) {
|
|
|
26941
27473
|
};
|
|
26942
27474
|
if (!def.checks)
|
|
26943
27475
|
return res;
|
|
26944
|
-
for (const
|
|
26945
|
-
switch (
|
|
27476
|
+
for (const check3 of def.checks) {
|
|
27477
|
+
switch (check3.kind) {
|
|
26946
27478
|
case "min":
|
|
26947
27479
|
if (refs.target === "jsonSchema7") {
|
|
26948
|
-
if (
|
|
26949
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27480
|
+
if (check3.inclusive) {
|
|
27481
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
26950
27482
|
} else {
|
|
26951
|
-
setResponseValueAndErrors(res, "exclusiveMinimum",
|
|
27483
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check3.value, check3.message, refs);
|
|
26952
27484
|
}
|
|
26953
27485
|
} else {
|
|
26954
|
-
if (!
|
|
27486
|
+
if (!check3.inclusive) {
|
|
26955
27487
|
res.exclusiveMinimum = true;
|
|
26956
27488
|
}
|
|
26957
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27489
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
26958
27490
|
}
|
|
26959
27491
|
break;
|
|
26960
27492
|
case "max":
|
|
26961
27493
|
if (refs.target === "jsonSchema7") {
|
|
26962
|
-
if (
|
|
26963
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27494
|
+
if (check3.inclusive) {
|
|
27495
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
26964
27496
|
} else {
|
|
26965
|
-
setResponseValueAndErrors(res, "exclusiveMaximum",
|
|
27497
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check3.value, check3.message, refs);
|
|
26966
27498
|
}
|
|
26967
27499
|
} else {
|
|
26968
|
-
if (!
|
|
27500
|
+
if (!check3.inclusive) {
|
|
26969
27501
|
res.exclusiveMaximum = true;
|
|
26970
27502
|
}
|
|
26971
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27503
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
26972
27504
|
}
|
|
26973
27505
|
break;
|
|
26974
27506
|
case "multipleOf":
|
|
26975
|
-
setResponseValueAndErrors(res, "multipleOf",
|
|
27507
|
+
setResponseValueAndErrors(res, "multipleOf", check3.value, check3.message, refs);
|
|
26976
27508
|
break;
|
|
26977
27509
|
}
|
|
26978
27510
|
}
|
|
@@ -27028,13 +27560,13 @@ var integerDateParser = (def, refs) => {
|
|
|
27028
27560
|
if (refs.target === "openApi3") {
|
|
27029
27561
|
return res;
|
|
27030
27562
|
}
|
|
27031
|
-
for (const
|
|
27032
|
-
switch (
|
|
27563
|
+
for (const check3 of def.checks) {
|
|
27564
|
+
switch (check3.kind) {
|
|
27033
27565
|
case "min":
|
|
27034
|
-
setResponseValueAndErrors(res, "minimum",
|
|
27566
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27035
27567
|
break;
|
|
27036
27568
|
case "max":
|
|
27037
|
-
setResponseValueAndErrors(res, "maximum",
|
|
27569
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27038
27570
|
break;
|
|
27039
27571
|
}
|
|
27040
27572
|
}
|
|
@@ -27152,125 +27684,125 @@ function parseStringDef(def, refs) {
|
|
|
27152
27684
|
type: "string"
|
|
27153
27685
|
};
|
|
27154
27686
|
if (def.checks) {
|
|
27155
|
-
for (const
|
|
27156
|
-
switch (
|
|
27687
|
+
for (const check3 of def.checks) {
|
|
27688
|
+
switch (check3.kind) {
|
|
27157
27689
|
case "min":
|
|
27158
|
-
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength,
|
|
27690
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check3.value) : check3.value, check3.message, refs);
|
|
27159
27691
|
break;
|
|
27160
27692
|
case "max":
|
|
27161
|
-
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength,
|
|
27693
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check3.value) : check3.value, check3.message, refs);
|
|
27162
27694
|
break;
|
|
27163
27695
|
case "email":
|
|
27164
27696
|
switch (refs.emailStrategy) {
|
|
27165
27697
|
case "format:email":
|
|
27166
|
-
addFormat(res, "email",
|
|
27698
|
+
addFormat(res, "email", check3.message, refs);
|
|
27167
27699
|
break;
|
|
27168
27700
|
case "format:idn-email":
|
|
27169
|
-
addFormat(res, "idn-email",
|
|
27701
|
+
addFormat(res, "idn-email", check3.message, refs);
|
|
27170
27702
|
break;
|
|
27171
27703
|
case "pattern:zod":
|
|
27172
|
-
addPattern(res, zodPatterns.email,
|
|
27704
|
+
addPattern(res, zodPatterns.email, check3.message, refs);
|
|
27173
27705
|
break;
|
|
27174
27706
|
}
|
|
27175
27707
|
break;
|
|
27176
27708
|
case "url":
|
|
27177
|
-
addFormat(res, "uri",
|
|
27709
|
+
addFormat(res, "uri", check3.message, refs);
|
|
27178
27710
|
break;
|
|
27179
27711
|
case "uuid":
|
|
27180
|
-
addFormat(res, "uuid",
|
|
27712
|
+
addFormat(res, "uuid", check3.message, refs);
|
|
27181
27713
|
break;
|
|
27182
27714
|
case "regex":
|
|
27183
|
-
addPattern(res,
|
|
27715
|
+
addPattern(res, check3.regex, check3.message, refs);
|
|
27184
27716
|
break;
|
|
27185
27717
|
case "cuid":
|
|
27186
|
-
addPattern(res, zodPatterns.cuid,
|
|
27718
|
+
addPattern(res, zodPatterns.cuid, check3.message, refs);
|
|
27187
27719
|
break;
|
|
27188
27720
|
case "cuid2":
|
|
27189
|
-
addPattern(res, zodPatterns.cuid2,
|
|
27721
|
+
addPattern(res, zodPatterns.cuid2, check3.message, refs);
|
|
27190
27722
|
break;
|
|
27191
27723
|
case "startsWith":
|
|
27192
|
-
addPattern(res, RegExp(`^${escapeLiteralCheckValue(
|
|
27724
|
+
addPattern(res, RegExp(`^${escapeLiteralCheckValue(check3.value, refs)}`), check3.message, refs);
|
|
27193
27725
|
break;
|
|
27194
27726
|
case "endsWith":
|
|
27195
|
-
addPattern(res, RegExp(`${escapeLiteralCheckValue(
|
|
27727
|
+
addPattern(res, RegExp(`${escapeLiteralCheckValue(check3.value, refs)}$`), check3.message, refs);
|
|
27196
27728
|
break;
|
|
27197
27729
|
case "datetime":
|
|
27198
|
-
addFormat(res, "date-time",
|
|
27730
|
+
addFormat(res, "date-time", check3.message, refs);
|
|
27199
27731
|
break;
|
|
27200
27732
|
case "date":
|
|
27201
|
-
addFormat(res, "date",
|
|
27733
|
+
addFormat(res, "date", check3.message, refs);
|
|
27202
27734
|
break;
|
|
27203
27735
|
case "time":
|
|
27204
|
-
addFormat(res, "time",
|
|
27736
|
+
addFormat(res, "time", check3.message, refs);
|
|
27205
27737
|
break;
|
|
27206
27738
|
case "duration":
|
|
27207
|
-
addFormat(res, "duration",
|
|
27739
|
+
addFormat(res, "duration", check3.message, refs);
|
|
27208
27740
|
break;
|
|
27209
27741
|
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,
|
|
27742
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check3.value) : check3.value, check3.message, refs);
|
|
27743
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check3.value) : check3.value, check3.message, refs);
|
|
27212
27744
|
break;
|
|
27213
27745
|
case "includes": {
|
|
27214
|
-
addPattern(res, RegExp(escapeLiteralCheckValue(
|
|
27746
|
+
addPattern(res, RegExp(escapeLiteralCheckValue(check3.value, refs)), check3.message, refs);
|
|
27215
27747
|
break;
|
|
27216
27748
|
}
|
|
27217
27749
|
case "ip": {
|
|
27218
|
-
if (
|
|
27219
|
-
addFormat(res, "ipv4",
|
|
27750
|
+
if (check3.version !== "v6") {
|
|
27751
|
+
addFormat(res, "ipv4", check3.message, refs);
|
|
27220
27752
|
}
|
|
27221
|
-
if (
|
|
27222
|
-
addFormat(res, "ipv6",
|
|
27753
|
+
if (check3.version !== "v4") {
|
|
27754
|
+
addFormat(res, "ipv6", check3.message, refs);
|
|
27223
27755
|
}
|
|
27224
27756
|
break;
|
|
27225
27757
|
}
|
|
27226
27758
|
case "base64url":
|
|
27227
|
-
addPattern(res, zodPatterns.base64url,
|
|
27759
|
+
addPattern(res, zodPatterns.base64url, check3.message, refs);
|
|
27228
27760
|
break;
|
|
27229
27761
|
case "jwt":
|
|
27230
|
-
addPattern(res, zodPatterns.jwt,
|
|
27762
|
+
addPattern(res, zodPatterns.jwt, check3.message, refs);
|
|
27231
27763
|
break;
|
|
27232
27764
|
case "cidr": {
|
|
27233
|
-
if (
|
|
27234
|
-
addPattern(res, zodPatterns.ipv4Cidr,
|
|
27765
|
+
if (check3.version !== "v6") {
|
|
27766
|
+
addPattern(res, zodPatterns.ipv4Cidr, check3.message, refs);
|
|
27235
27767
|
}
|
|
27236
|
-
if (
|
|
27237
|
-
addPattern(res, zodPatterns.ipv6Cidr,
|
|
27768
|
+
if (check3.version !== "v4") {
|
|
27769
|
+
addPattern(res, zodPatterns.ipv6Cidr, check3.message, refs);
|
|
27238
27770
|
}
|
|
27239
27771
|
break;
|
|
27240
27772
|
}
|
|
27241
27773
|
case "emoji":
|
|
27242
|
-
addPattern(res, zodPatterns.emoji(),
|
|
27774
|
+
addPattern(res, zodPatterns.emoji(), check3.message, refs);
|
|
27243
27775
|
break;
|
|
27244
27776
|
case "ulid": {
|
|
27245
|
-
addPattern(res, zodPatterns.ulid,
|
|
27777
|
+
addPattern(res, zodPatterns.ulid, check3.message, refs);
|
|
27246
27778
|
break;
|
|
27247
27779
|
}
|
|
27248
27780
|
case "base64": {
|
|
27249
27781
|
switch (refs.base64Strategy) {
|
|
27250
27782
|
case "format:binary": {
|
|
27251
|
-
addFormat(res, "binary",
|
|
27783
|
+
addFormat(res, "binary", check3.message, refs);
|
|
27252
27784
|
break;
|
|
27253
27785
|
}
|
|
27254
27786
|
case "contentEncoding:base64": {
|
|
27255
|
-
setResponseValueAndErrors(res, "contentEncoding", "base64",
|
|
27787
|
+
setResponseValueAndErrors(res, "contentEncoding", "base64", check3.message, refs);
|
|
27256
27788
|
break;
|
|
27257
27789
|
}
|
|
27258
27790
|
case "pattern:zod": {
|
|
27259
|
-
addPattern(res, zodPatterns.base64,
|
|
27791
|
+
addPattern(res, zodPatterns.base64, check3.message, refs);
|
|
27260
27792
|
break;
|
|
27261
27793
|
}
|
|
27262
27794
|
}
|
|
27263
27795
|
break;
|
|
27264
27796
|
}
|
|
27265
27797
|
case "nanoid": {
|
|
27266
|
-
addPattern(res, zodPatterns.nanoid,
|
|
27798
|
+
addPattern(res, zodPatterns.nanoid, check3.message, refs);
|
|
27267
27799
|
}
|
|
27268
27800
|
case "toLowerCase":
|
|
27269
27801
|
case "toUpperCase":
|
|
27270
27802
|
case "trim":
|
|
27271
27803
|
break;
|
|
27272
27804
|
default:
|
|
27273
|
-
((_) => {})(
|
|
27805
|
+
((_) => {})(check3);
|
|
27274
27806
|
}
|
|
27275
27807
|
}
|
|
27276
27808
|
}
|
|
@@ -27639,42 +28171,42 @@ function parseNumberDef(def, refs) {
|
|
|
27639
28171
|
};
|
|
27640
28172
|
if (!def.checks)
|
|
27641
28173
|
return res;
|
|
27642
|
-
for (const
|
|
27643
|
-
switch (
|
|
28174
|
+
for (const check3 of def.checks) {
|
|
28175
|
+
switch (check3.kind) {
|
|
27644
28176
|
case "int":
|
|
27645
28177
|
res.type = "integer";
|
|
27646
|
-
addErrorMessage(res, "type",
|
|
28178
|
+
addErrorMessage(res, "type", check3.message, refs);
|
|
27647
28179
|
break;
|
|
27648
28180
|
case "min":
|
|
27649
28181
|
if (refs.target === "jsonSchema7") {
|
|
27650
|
-
if (
|
|
27651
|
-
setResponseValueAndErrors(res, "minimum",
|
|
28182
|
+
if (check3.inclusive) {
|
|
28183
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27652
28184
|
} else {
|
|
27653
|
-
setResponseValueAndErrors(res, "exclusiveMinimum",
|
|
28185
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check3.value, check3.message, refs);
|
|
27654
28186
|
}
|
|
27655
28187
|
} else {
|
|
27656
|
-
if (!
|
|
28188
|
+
if (!check3.inclusive) {
|
|
27657
28189
|
res.exclusiveMinimum = true;
|
|
27658
28190
|
}
|
|
27659
|
-
setResponseValueAndErrors(res, "minimum",
|
|
28191
|
+
setResponseValueAndErrors(res, "minimum", check3.value, check3.message, refs);
|
|
27660
28192
|
}
|
|
27661
28193
|
break;
|
|
27662
28194
|
case "max":
|
|
27663
28195
|
if (refs.target === "jsonSchema7") {
|
|
27664
|
-
if (
|
|
27665
|
-
setResponseValueAndErrors(res, "maximum",
|
|
28196
|
+
if (check3.inclusive) {
|
|
28197
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27666
28198
|
} else {
|
|
27667
|
-
setResponseValueAndErrors(res, "exclusiveMaximum",
|
|
28199
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check3.value, check3.message, refs);
|
|
27668
28200
|
}
|
|
27669
28201
|
} else {
|
|
27670
|
-
if (!
|
|
28202
|
+
if (!check3.inclusive) {
|
|
27671
28203
|
res.exclusiveMaximum = true;
|
|
27672
28204
|
}
|
|
27673
|
-
setResponseValueAndErrors(res, "maximum",
|
|
28205
|
+
setResponseValueAndErrors(res, "maximum", check3.value, check3.message, refs);
|
|
27674
28206
|
}
|
|
27675
28207
|
break;
|
|
27676
28208
|
case "multipleOf":
|
|
27677
|
-
setResponseValueAndErrors(res, "multipleOf",
|
|
28209
|
+
setResponseValueAndErrors(res, "multipleOf", check3.value, check3.message, refs);
|
|
27678
28210
|
break;
|
|
27679
28211
|
}
|
|
27680
28212
|
}
|
|
@@ -30238,7 +30770,11 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30238
30770
|
// src/mcp/server.ts
|
|
30239
30771
|
var MACHINE_MCP_TOOL_NAMES = [
|
|
30240
30772
|
"machines_status",
|
|
30773
|
+
"machines_doctor",
|
|
30774
|
+
"machines_self_test",
|
|
30241
30775
|
"machines_apps_list",
|
|
30776
|
+
"machines_apps_status",
|
|
30777
|
+
"machines_apps_diff",
|
|
30242
30778
|
"machines_apps_plan",
|
|
30243
30779
|
"machines_apps_apply",
|
|
30244
30780
|
"machines_manifest",
|
|
@@ -30254,6 +30790,8 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
30254
30790
|
"machines_diff",
|
|
30255
30791
|
"machines_install_tailscale_preview",
|
|
30256
30792
|
"machines_install_tailscale_apply",
|
|
30793
|
+
"machines_install_claude_status",
|
|
30794
|
+
"machines_install_claude_diff",
|
|
30257
30795
|
"machines_install_claude_preview",
|
|
30258
30796
|
"machines_install_claude_apply",
|
|
30259
30797
|
"machines_ssh_resolve",
|
|
@@ -30268,30 +30806,25 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
30268
30806
|
"machines_notifications_add",
|
|
30269
30807
|
"machines_notifications_list",
|
|
30270
30808
|
"machines_notifications_test",
|
|
30809
|
+
"machines_notifications_dispatch",
|
|
30271
30810
|
"machines_notifications_remove",
|
|
30272
30811
|
"machines_serve_info",
|
|
30273
30812
|
"machines_serve_dashboard"
|
|
30274
30813
|
];
|
|
30275
30814
|
function createMcpServer(version2) {
|
|
30276
|
-
const server = new McpServer({
|
|
30277
|
-
name: "machines",
|
|
30278
|
-
version: version2
|
|
30279
|
-
});
|
|
30815
|
+
const server = new McpServer({ name: "machines", version: version2 });
|
|
30280
30816
|
server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
|
|
30281
30817
|
content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
|
|
30282
30818
|
}));
|
|
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) }]
|
|
30819
|
+
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) }] }));
|
|
30820
|
+
server.tool("machines_self_test", "Run local package smoke checks.", {}, async () => ({
|
|
30821
|
+
content: [{ type: "text", text: JSON.stringify(runSelfTest(), null, 2) }]
|
|
30294
30822
|
}));
|
|
30823
|
+
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) }] }));
|
|
30824
|
+
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) }] }));
|
|
30825
|
+
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) }] }));
|
|
30826
|
+
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) }] }));
|
|
30827
|
+
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
30828
|
server.tool("machines_manifest", "Read the current fleet manifest.", {}, async () => ({
|
|
30296
30829
|
content: [{ type: "text", text: JSON.stringify(manifestList(), null, 2) }]
|
|
30297
30830
|
}));
|
|
@@ -30301,45 +30834,33 @@ function createMcpServer(version2) {
|
|
|
30301
30834
|
server.tool("machines_manifest_bootstrap", "Detect and upsert the current machine into the fleet manifest.", {}, async () => ({
|
|
30302
30835
|
content: [{ type: "text", text: JSON.stringify(manifestBootstrapCurrentMachine(), null, 2) }]
|
|
30303
30836
|
}));
|
|
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
|
-
}));
|
|
30837
|
+
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) }] }));
|
|
30838
|
+
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
30839
|
server.tool("machines_agent_status", "List current machine agent heartbeats.", {}, async () => ({
|
|
30311
30840
|
content: [{ type: "text", text: JSON.stringify(getAgentStatus(), null, 2) }]
|
|
30312
30841
|
}));
|
|
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
|
-
}));
|
|
30842
|
+
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) }] }));
|
|
30843
|
+
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) }] }));
|
|
30844
|
+
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) }] }));
|
|
30845
|
+
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
30846
|
server.tool("machines_diff", "Show manifest differences between two machines.", {
|
|
30332
30847
|
left_machine_id: exports_external2.string().describe("Left machine identifier"),
|
|
30333
30848
|
right_machine_id: exports_external2.string().optional().describe("Right machine identifier")
|
|
30334
30849
|
}, async ({ left_machine_id, right_machine_id }) => ({
|
|
30335
30850
|
content: [{ type: "text", text: JSON.stringify(diffMachines(left_machine_id, right_machine_id), null, 2) }]
|
|
30336
30851
|
}));
|
|
30852
|
+
server.tool("machines_install_claude_status", "Check installed state for Claude, Codex, and Gemini CLIs.", {
|
|
30853
|
+
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30854
|
+
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
30855
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(getClaudeCliStatus(machine_id, tools), null, 2) }] }));
|
|
30856
|
+
server.tool("machines_install_claude_diff", "Show missing and installed Claude, Codex, and Gemini CLIs.", {
|
|
30857
|
+
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30858
|
+
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
30859
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(diffClaudeCli(machine_id, tools), null, 2) }] }));
|
|
30337
30860
|
server.tool("machines_install_claude_preview", "Preview Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
30338
30861
|
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30339
30862
|
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
|
-
}));
|
|
30863
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(buildClaudeInstallPlan(machine_id, tools), null, 2) }] }));
|
|
30343
30864
|
server.tool("machines_install_claude_apply", "Execute Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
30344
30865
|
machine_id: exports_external2.string().optional().describe("Machine identifier"),
|
|
30345
30866
|
tools: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to install"),
|
|
@@ -30347,73 +30868,21 @@ function createMcpServer(version2) {
|
|
|
30347
30868
|
}, async ({ machine_id, tools, yes }) => ({
|
|
30348
30869
|
content: [{ type: "text", text: JSON.stringify(runClaudeInstall(machine_id, tools, { apply: true, yes }), null, 2) }]
|
|
30349
30870
|
}));
|
|
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) }]
|
|
30358
|
-
}));
|
|
30359
|
-
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", {
|
|
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
|
-
]
|
|
30871
|
+
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) }] }));
|
|
30872
|
+
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) }] }));
|
|
30873
|
+
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 }) => ({
|
|
30874
|
+
content: [{ type: "text", text: JSON.stringify({ resolved: resolveSshTarget(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
30372
30875
|
}));
|
|
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 }) => ({
|
|
30876
|
+
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external2.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
30376
30877
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
30377
30878
|
}));
|
|
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
|
-
}));
|
|
30879
|
+
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) }] }));
|
|
30880
|
+
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) }] }));
|
|
30881
|
+
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) }] }));
|
|
30882
|
+
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) }] }));
|
|
30883
|
+
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) }] }));
|
|
30884
|
+
server.tool("machines_dns_list", "List local domain mappings.", {}, async () => ({ content: [{ type: "text", text: JSON.stringify(listDomainMappings(), null, 2) }] }));
|
|
30885
|
+
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
30886
|
server.tool("machines_notifications_add", "Add or replace a notification channel.", {
|
|
30418
30887
|
channel_id: exports_external2.string().describe("Channel identifier"),
|
|
30419
30888
|
type: exports_external2.enum(["email", "webhook", "command"]).describe("Notification transport"),
|
|
@@ -30421,68 +30890,22 @@ function createMcpServer(version2) {
|
|
|
30421
30890
|
events: exports_external2.array(exports_external2.string()).describe("Events routed to this channel"),
|
|
30422
30891
|
enabled: exports_external2.boolean().optional().describe("Whether the channel is enabled")
|
|
30423
30892
|
}, 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
|
-
]
|
|
30893
|
+
content: [{ type: "text", text: JSON.stringify(addNotificationChannel({ id: channel_id, type, target, events, enabled: enabled ?? true }), null, 2) }]
|
|
30436
30894
|
}));
|
|
30437
30895
|
server.tool("machines_notifications_list", "List notification channels.", {}, async () => ({
|
|
30438
30896
|
content: [{ type: "text", text: JSON.stringify(listNotificationChannels(), null, 2) }]
|
|
30439
30897
|
}));
|
|
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) }]
|
|
30898
|
+
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 }) => ({
|
|
30899
|
+
content: [{ type: "text", text: JSON.stringify(await testNotificationChannel(channel_id, event, message, { apply: Boolean(yes), yes }), null, 2) }]
|
|
30463
30900
|
}));
|
|
30901
|
+
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) }] }));
|
|
30902
|
+
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) }] }));
|
|
30903
|
+
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
30904
|
server.tool("machines_serve_dashboard", "Render the current dashboard HTML.", {}, async () => ({
|
|
30465
30905
|
content: [{ type: "text", text: renderDashboardHtml() }]
|
|
30466
30906
|
}));
|
|
30467
30907
|
return server;
|
|
30468
30908
|
}
|
|
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
30909
|
export {
|
|
30487
30910
|
writeNotificationConfig,
|
|
30488
30911
|
writeManifest,
|
|
@@ -30495,6 +30918,8 @@ export {
|
|
|
30495
30918
|
runTailscaleInstall,
|
|
30496
30919
|
runSync,
|
|
30497
30920
|
runSetup,
|
|
30921
|
+
runSelfTest,
|
|
30922
|
+
runDoctor,
|
|
30498
30923
|
runClaudeInstall,
|
|
30499
30924
|
runCertPlan,
|
|
30500
30925
|
runBackup,
|
|
@@ -30534,12 +30959,19 @@ export {
|
|
|
30534
30959
|
getDbPath,
|
|
30535
30960
|
getDb,
|
|
30536
30961
|
getDataDir,
|
|
30962
|
+
getClipboardKeyPath,
|
|
30963
|
+
getClipboardHistoryPath,
|
|
30964
|
+
getClaudeCliStatus,
|
|
30965
|
+
getAppsStatus,
|
|
30537
30966
|
getAgentStatus,
|
|
30538
30967
|
getAdapter,
|
|
30539
30968
|
fleetSchema,
|
|
30540
30969
|
ensureParentDir,
|
|
30541
30970
|
ensureDataDir,
|
|
30971
|
+
dispatchNotificationEvent,
|
|
30542
30972
|
diffMachines,
|
|
30973
|
+
diffClaudeCli,
|
|
30974
|
+
diffApps,
|
|
30543
30975
|
detectCurrentMachineManifest,
|
|
30544
30976
|
createMcpServer,
|
|
30545
30977
|
countRuns,
|
|
@@ -30553,5 +30985,6 @@ export {
|
|
|
30553
30985
|
buildAppsPlan,
|
|
30554
30986
|
addNotificationChannel,
|
|
30555
30987
|
addDomainMapping,
|
|
30556
|
-
MACHINE_MCP_TOOL_NAMES
|
|
30988
|
+
MACHINE_MCP_TOOL_NAMES,
|
|
30989
|
+
CROSSREFS_KEY
|
|
30557
30990
|
};
|