@hasna/machines 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +589 -14
- package/dist/commands/clipboard-daemon.d.ts +6 -0
- package/dist/commands/clipboard-daemon.d.ts.map +1 -0
- 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/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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -4
- package/dist/mcp/index.js +72 -3
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6599,6 +6599,12 @@ function getManifestPath() {
|
|
|
6599
6599
|
function getNotificationsPath() {
|
|
6600
6600
|
return process.env["HASNA_MACHINES_NOTIFICATIONS_PATH"] || join2(getDataDir(), "notifications.json");
|
|
6601
6601
|
}
|
|
6602
|
+
function getClipboardKeyPath() {
|
|
6603
|
+
return process.env["HASNA_MACHINES_CLIPBOARD_KEY_PATH"] || join2(getDataDir(), "clipboard.key");
|
|
6604
|
+
}
|
|
6605
|
+
function getClipboardHistoryPath() {
|
|
6606
|
+
return process.env["HASNA_MACHINES_CLIPBOARD_HISTORY_PATH"] || join2(getDataDir(), "clipboard-history.json");
|
|
6607
|
+
}
|
|
6602
6608
|
function ensureParentDir(filePath) {
|
|
6603
6609
|
if (filePath === ":memory:")
|
|
6604
6610
|
return;
|
|
@@ -17667,11 +17673,13 @@ function renderDashboardHtml() {
|
|
|
17667
17673
|
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
17668
17674
|
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
17669
17675
|
.muted { color: #9fb0d9; }
|
|
17676
|
+
.refresh { font-size: 12px; color: #6b7fa3; margin-left: auto; }
|
|
17677
|
+
.updated { transition: opacity 0.3s; }
|
|
17670
17678
|
</style>
|
|
17671
17679
|
</head>
|
|
17672
17680
|
<body>
|
|
17673
17681
|
<main>
|
|
17674
|
-
<h1>Machines Dashboard
|
|
17682
|
+
<h1>Machines Dashboard <span class="refresh" id="last-updated"></span></h1>
|
|
17675
17683
|
<div class="grid">
|
|
17676
17684
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
17677
17685
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
@@ -17698,7 +17706,7 @@ function renderDashboardHtml() {
|
|
|
17698
17706
|
<h2>Doctor</h2>
|
|
17699
17707
|
<table>
|
|
17700
17708
|
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
17701
|
-
<tbody>
|
|
17709
|
+
<tbody id="doctor-tbody">
|
|
17702
17710
|
${doctor.checks.map((entry) => `<tr>
|
|
17703
17711
|
<td>${escapeHtml(entry.summary)}</td>
|
|
17704
17712
|
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
@@ -17708,6 +17716,16 @@ function renderDashboardHtml() {
|
|
|
17708
17716
|
</table>
|
|
17709
17717
|
</section>
|
|
17710
17718
|
|
|
17719
|
+
<section class="card" style="margin-top:16px">
|
|
17720
|
+
<h2>Apps</h2>
|
|
17721
|
+
<p class="muted">Use <code>/api/apps/status</code> for the full app inventory payload.</p>
|
|
17722
|
+
</section>
|
|
17723
|
+
|
|
17724
|
+
<section class="card" style="margin-top:16px">
|
|
17725
|
+
<h2>AI CLIs</h2>
|
|
17726
|
+
<p class="muted">Use <code>/api/install-claude/status</code> for the full CLI inventory payload.</p>
|
|
17727
|
+
</section>
|
|
17728
|
+
|
|
17711
17729
|
<section class="card" style="margin-top:16px">
|
|
17712
17730
|
<h2>Self Test</h2>
|
|
17713
17731
|
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
@@ -17715,9 +17733,66 @@ function renderDashboardHtml() {
|
|
|
17715
17733
|
|
|
17716
17734
|
<section class="card" style="margin-top:16px">
|
|
17717
17735
|
<h2>Manifest</h2>
|
|
17718
|
-
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
17736
|
+
<pre id="manifest-json">${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
17719
17737
|
</section>
|
|
17720
17738
|
</main>
|
|
17739
|
+
<script>
|
|
17740
|
+
// Auto-refresh dashboard data every 15s
|
|
17741
|
+
const REFRESH_INTERVAL = 15000;
|
|
17742
|
+
async function refreshData() {
|
|
17743
|
+
try {
|
|
17744
|
+
const [statusRes, doctorRes] = await Promise.all([
|
|
17745
|
+
fetch("/api/status"),
|
|
17746
|
+
fetch("/api/doctor"),
|
|
17747
|
+
]);
|
|
17748
|
+
const status = await statusRes.json();
|
|
17749
|
+
const doctor = await doctorRes.json();
|
|
17750
|
+
|
|
17751
|
+
// Update stat cards
|
|
17752
|
+
const stats = document.querySelectorAll(".stat");
|
|
17753
|
+
if (stats[0]) stats[0].textContent = status.manifestMachineCount;
|
|
17754
|
+
if (stats[1]) stats[1].textContent = status.heartbeatCount;
|
|
17755
|
+
|
|
17756
|
+
// Update machine table
|
|
17757
|
+
const tbody = document.querySelector("tbody");
|
|
17758
|
+
if (tbody && status.machines) {
|
|
17759
|
+
tbody.innerHTML = status.machines
|
|
17760
|
+
.map((m) =>
|
|
17761
|
+
"<tr>" +
|
|
17762
|
+
"<td><code>" + m.machineId + "</code></td>" +
|
|
17763
|
+
"<td>" + (m.platform || "unknown") + "</td>" +
|
|
17764
|
+
'<td><span class="badge ' + m.heartbeatStatus + '">' + m.heartbeatStatus + '</span></td>' +
|
|
17765
|
+
"<td>" + (m.lastHeartbeatAt || "\\u2014") + "</td>" +
|
|
17766
|
+
"</tr>"
|
|
17767
|
+
)
|
|
17768
|
+
.join("");
|
|
17769
|
+
}
|
|
17770
|
+
|
|
17771
|
+
// Update doctor table
|
|
17772
|
+
const doctorTbody = document.getElementById("doctor-tbody");
|
|
17773
|
+
if (doctorTbody && doctor.checks) {
|
|
17774
|
+
doctorTbody.innerHTML = doctor.checks
|
|
17775
|
+
.map((c) =>
|
|
17776
|
+
"<tr>" +
|
|
17777
|
+
"<td>" + c.summary + "</td>" +
|
|
17778
|
+
'<td><span class="badge ' + c.status + '">' + c.status + '</span></td>' +
|
|
17779
|
+
'<td class="muted">' + c.detail + "</td>" +
|
|
17780
|
+
"</tr>"
|
|
17781
|
+
)
|
|
17782
|
+
.join("");
|
|
17783
|
+
}
|
|
17784
|
+
|
|
17785
|
+
// Update timestamp
|
|
17786
|
+
document.getElementById("last-updated").textContent =
|
|
17787
|
+
"updated " + new Date().toLocaleTimeString();
|
|
17788
|
+
} catch (e) {
|
|
17789
|
+
// Silently ignore fetch errors during page unload
|
|
17790
|
+
}
|
|
17791
|
+
}
|
|
17792
|
+
document.getElementById("last-updated").textContent =
|
|
17793
|
+
"updated " + new Date().toLocaleTimeString();
|
|
17794
|
+
setInterval(refreshData, REFRESH_INTERVAL);
|
|
17795
|
+
</script>
|
|
17721
17796
|
</body>
|
|
17722
17797
|
</html>`;
|
|
17723
17798
|
}
|
|
@@ -17828,6 +17903,426 @@ function runSelfTest() {
|
|
|
17828
17903
|
};
|
|
17829
17904
|
}
|
|
17830
17905
|
|
|
17906
|
+
// src/commands/clipboard.ts
|
|
17907
|
+
import { createHash } from "crypto";
|
|
17908
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
17909
|
+
import { join as join9 } from "path";
|
|
17910
|
+
var DEFAULT_CONFIG = {
|
|
17911
|
+
version: 1,
|
|
17912
|
+
enabled: true,
|
|
17913
|
+
port: 19452,
|
|
17914
|
+
maxHistory: 500,
|
|
17915
|
+
maxSizeBytes: 10 * 1024 * 1024,
|
|
17916
|
+
skipPatterns: [
|
|
17917
|
+
"password",
|
|
17918
|
+
"secret",
|
|
17919
|
+
"token",
|
|
17920
|
+
"-----BEGIN",
|
|
17921
|
+
"AKIA"
|
|
17922
|
+
]
|
|
17923
|
+
};
|
|
17924
|
+
function resolveConfigPath(configPath) {
|
|
17925
|
+
if (configPath)
|
|
17926
|
+
return configPath;
|
|
17927
|
+
return join9(getDataDir(), "clipboard-config.json");
|
|
17928
|
+
}
|
|
17929
|
+
function resolveHistoryPath(historyPath) {
|
|
17930
|
+
if (historyPath)
|
|
17931
|
+
return historyPath;
|
|
17932
|
+
return getClipboardHistoryPath();
|
|
17933
|
+
}
|
|
17934
|
+
function getDefaultConfig() {
|
|
17935
|
+
return { ...DEFAULT_CONFIG, skipPatterns: [...DEFAULT_CONFIG.skipPatterns] };
|
|
17936
|
+
}
|
|
17937
|
+
function readConfig(configPath) {
|
|
17938
|
+
const path = resolveConfigPath(configPath);
|
|
17939
|
+
if (!existsSync8(path)) {
|
|
17940
|
+
return getDefaultConfig();
|
|
17941
|
+
}
|
|
17942
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
17943
|
+
return { ...getDefaultConfig(), ...parsed };
|
|
17944
|
+
}
|
|
17945
|
+
function writeConfig(config, configPath) {
|
|
17946
|
+
const path = resolveConfigPath(configPath);
|
|
17947
|
+
ensureParentDir(path);
|
|
17948
|
+
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
17949
|
+
`, "utf8");
|
|
17950
|
+
}
|
|
17951
|
+
function readHistory(historyPath) {
|
|
17952
|
+
const path = resolveHistoryPath(historyPath);
|
|
17953
|
+
if (!existsSync8(path)) {
|
|
17954
|
+
return [];
|
|
17955
|
+
}
|
|
17956
|
+
try {
|
|
17957
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
17958
|
+
} catch {
|
|
17959
|
+
return [];
|
|
17960
|
+
}
|
|
17961
|
+
}
|
|
17962
|
+
function writeHistory(entries, historyPath) {
|
|
17963
|
+
const path = resolveHistoryPath(historyPath);
|
|
17964
|
+
ensureParentDir(path);
|
|
17965
|
+
writeFileSync5(path, `${JSON.stringify(entries, null, 2)}
|
|
17966
|
+
`, "utf8");
|
|
17967
|
+
}
|
|
17968
|
+
function computeHash(content) {
|
|
17969
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
17970
|
+
}
|
|
17971
|
+
function shouldSkipContent(content, skipPatterns) {
|
|
17972
|
+
const lower = content.toLowerCase();
|
|
17973
|
+
return skipPatterns.some((pattern) => lower.includes(pattern.toLowerCase()));
|
|
17974
|
+
}
|
|
17975
|
+
function sanitizeClipboardForRead(content, maxSizeBytes, skipPatterns) {
|
|
17976
|
+
if (Buffer.byteLength(content, "utf8") > maxSizeBytes) {
|
|
17977
|
+
return { ok: false, reason: "content exceeds size limit" };
|
|
17978
|
+
}
|
|
17979
|
+
if (shouldSkipContent(content, skipPatterns)) {
|
|
17980
|
+
return { ok: false, reason: "content matches skip pattern" };
|
|
17981
|
+
}
|
|
17982
|
+
return { ok: true };
|
|
17983
|
+
}
|
|
17984
|
+
function getOrCreateClipboardKey() {
|
|
17985
|
+
const keyPath = getClipboardKeyPath();
|
|
17986
|
+
if (existsSync8(keyPath)) {
|
|
17987
|
+
return readFileSync7(keyPath, "utf8").trim();
|
|
17988
|
+
}
|
|
17989
|
+
const key = createHash("sha256").update(crypto.randomUUID()).digest("hex").slice(0, 32);
|
|
17990
|
+
ensureParentDir(keyPath);
|
|
17991
|
+
writeFileSync5(keyPath, `${key}
|
|
17992
|
+
`, "utf8");
|
|
17993
|
+
return key;
|
|
17994
|
+
}
|
|
17995
|
+
function getDefaultClipboardConfig() {
|
|
17996
|
+
return getDefaultConfig();
|
|
17997
|
+
}
|
|
17998
|
+
function getConfigPath2(configPath) {
|
|
17999
|
+
return resolveConfigPath(configPath);
|
|
18000
|
+
}
|
|
18001
|
+
function readClipboardConfig(configPath) {
|
|
18002
|
+
return readConfig(configPath);
|
|
18003
|
+
}
|
|
18004
|
+
function writeClipboardConfig(config, configPath) {
|
|
18005
|
+
writeConfig(config, configPath);
|
|
18006
|
+
}
|
|
18007
|
+
function readClipboardHistory(historyPath) {
|
|
18008
|
+
return readHistory(historyPath);
|
|
18009
|
+
}
|
|
18010
|
+
function addClipboardEntry(entry, historyPath) {
|
|
18011
|
+
const entries = readHistory(historyPath);
|
|
18012
|
+
const existing = entries.find((e) => e.hash === entry.hash);
|
|
18013
|
+
if (existing) {
|
|
18014
|
+
existing.timestamp = entry.timestamp;
|
|
18015
|
+
} else {
|
|
18016
|
+
entries.unshift(entry);
|
|
18017
|
+
}
|
|
18018
|
+
const config = readConfig();
|
|
18019
|
+
if (entries.length > config.maxHistory) {
|
|
18020
|
+
entries.length = config.maxHistory;
|
|
18021
|
+
}
|
|
18022
|
+
writeHistory(entries, historyPath);
|
|
18023
|
+
}
|
|
18024
|
+
function clearClipboardHistory(historyPath) {
|
|
18025
|
+
const path = resolveHistoryPath(historyPath);
|
|
18026
|
+
if (existsSync8(path)) {
|
|
18027
|
+
rmSync(path);
|
|
18028
|
+
}
|
|
18029
|
+
}
|
|
18030
|
+
function getClipboardStatus(historyPath) {
|
|
18031
|
+
const history = readHistory(historyPath);
|
|
18032
|
+
const config = readConfig();
|
|
18033
|
+
return {
|
|
18034
|
+
running: false,
|
|
18035
|
+
port: config.port,
|
|
18036
|
+
historyCount: history.length
|
|
18037
|
+
};
|
|
18038
|
+
}
|
|
18039
|
+
|
|
18040
|
+
// src/commands/clipboard-daemon.ts
|
|
18041
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
18042
|
+
import { join as join10 } from "path";
|
|
18043
|
+
import { createHash as createHash3 } from "crypto";
|
|
18044
|
+
|
|
18045
|
+
// src/commands/clipboard-server.ts
|
|
18046
|
+
import { createServer } from "http";
|
|
18047
|
+
import { createHash as createHash2 } from "crypto";
|
|
18048
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
18049
|
+
function readLocalClipboardSync() {
|
|
18050
|
+
const platform4 = process.platform;
|
|
18051
|
+
if (platform4 === "darwin") {
|
|
18052
|
+
const result = Bun.spawnSync(["pbpaste"], { stdout: "pipe", stderr: "pipe" });
|
|
18053
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18054
|
+
}
|
|
18055
|
+
if (platform4 === "linux") {
|
|
18056
|
+
if (hasCommand2("wl-paste")) {
|
|
18057
|
+
const result = Bun.spawnSync(["wl-paste"], { stdout: "pipe", stderr: "pipe" });
|
|
18058
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18059
|
+
}
|
|
18060
|
+
if (hasCommand2("xclip")) {
|
|
18061
|
+
const result = Bun.spawnSync(["xclip", "-selection", "clipboard", "-o"], { stdout: "pipe", stderr: "pipe" });
|
|
18062
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18063
|
+
}
|
|
18064
|
+
return "";
|
|
18065
|
+
}
|
|
18066
|
+
return "";
|
|
18067
|
+
}
|
|
18068
|
+
function writeLocalClipboardSync(content) {
|
|
18069
|
+
const platform4 = process.platform;
|
|
18070
|
+
if (platform4 === "darwin") {
|
|
18071
|
+
const result = Bun.spawnSync(["pbcopy"], { stdin: new TextEncoder().encode(content), stdout: "ignore", stderr: "ignore" });
|
|
18072
|
+
return result.exitCode === 0;
|
|
18073
|
+
}
|
|
18074
|
+
if (platform4 === "linux") {
|
|
18075
|
+
if (hasCommand2("wl-copy")) {
|
|
18076
|
+
const result = Bun.spawnSync(["wl-copy"], { stdin: new TextEncoder().encode(content), stdout: "ignore", stderr: "ignore" });
|
|
18077
|
+
return result.exitCode === 0;
|
|
18078
|
+
}
|
|
18079
|
+
if (hasCommand2("xclip")) {
|
|
18080
|
+
const result = Bun.spawnSync(["xclip", "-selection", "clipboard"], { stdin: new TextEncoder().encode(content), stdout: "ignore", stderr: "ignore" });
|
|
18081
|
+
return result.exitCode === 0;
|
|
18082
|
+
}
|
|
18083
|
+
return false;
|
|
18084
|
+
}
|
|
18085
|
+
return false;
|
|
18086
|
+
}
|
|
18087
|
+
function hasCommand2(binary) {
|
|
18088
|
+
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], { stdout: "ignore", stderr: "ignore", env: process.env });
|
|
18089
|
+
return result.exitCode === 0;
|
|
18090
|
+
}
|
|
18091
|
+
function loadSharedSecret() {
|
|
18092
|
+
const keyPath = getClipboardKeyPath();
|
|
18093
|
+
try {
|
|
18094
|
+
return readFileSync8(keyPath, "utf8").trim();
|
|
18095
|
+
} catch {
|
|
18096
|
+
return "";
|
|
18097
|
+
}
|
|
18098
|
+
}
|
|
18099
|
+
function authenticate(request) {
|
|
18100
|
+
const authHeader = request.headers["authorization"];
|
|
18101
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
18102
|
+
return false;
|
|
18103
|
+
}
|
|
18104
|
+
const token = authHeader.slice(7);
|
|
18105
|
+
const secret = loadSharedSecret();
|
|
18106
|
+
if (!secret)
|
|
18107
|
+
return false;
|
|
18108
|
+
return createHash2("sha256").update(token).digest("hex") === createHash2("sha256").update(secret).digest("hex");
|
|
18109
|
+
}
|
|
18110
|
+
function jsonResponse(response, status, data) {
|
|
18111
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
18112
|
+
response.end(JSON.stringify(data));
|
|
18113
|
+
}
|
|
18114
|
+
var currentContentHash = null;
|
|
18115
|
+
function startClipboardServer(options = {}) {
|
|
18116
|
+
const config = options.config || readClipboardConfig();
|
|
18117
|
+
const port = options.port || config.port;
|
|
18118
|
+
const server = createServer(async (request, response) => {
|
|
18119
|
+
if (!authenticate(request)) {
|
|
18120
|
+
return jsonResponse(response, 401, { error: "unauthorized" });
|
|
18121
|
+
}
|
|
18122
|
+
const url = new URL(request.url || "/", `http://${request.headers.host || "localhost"}`);
|
|
18123
|
+
if (url.pathname === "/clipboard" && request.method === "POST") {
|
|
18124
|
+
return handleReceiveClipboard(request, response, config);
|
|
18125
|
+
}
|
|
18126
|
+
if (url.pathname === "/clipboard" && request.method === "GET") {
|
|
18127
|
+
return handleGetClipboard(response, config);
|
|
18128
|
+
}
|
|
18129
|
+
if (url.pathname === "/health" && request.method === "GET") {
|
|
18130
|
+
return jsonResponse(response, 200, { ok: true, machineId: process.env["HASNA_MACHINES_MACHINE_ID"] || "unknown" });
|
|
18131
|
+
}
|
|
18132
|
+
jsonResponse(response, 404, { error: "not found" });
|
|
18133
|
+
});
|
|
18134
|
+
server.listen(port, "0.0.0.0", () => {});
|
|
18135
|
+
server.on("error", (error) => {
|
|
18136
|
+
console.error(`clipboard server error: ${error.message}`);
|
|
18137
|
+
});
|
|
18138
|
+
return {
|
|
18139
|
+
server,
|
|
18140
|
+
port,
|
|
18141
|
+
close: async () => {
|
|
18142
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
18143
|
+
}
|
|
18144
|
+
};
|
|
18145
|
+
}
|
|
18146
|
+
function handleReceiveClipboard(request, response, config) {
|
|
18147
|
+
let body = "";
|
|
18148
|
+
request.on("data", (chunk) => {
|
|
18149
|
+
body += chunk;
|
|
18150
|
+
});
|
|
18151
|
+
request.on("end", () => {
|
|
18152
|
+
try {
|
|
18153
|
+
const parsed = JSON.parse(body);
|
|
18154
|
+
const content = parsed.content || "";
|
|
18155
|
+
const contentType = parsed.contentType || "text";
|
|
18156
|
+
const sourceMachine = parsed.sourceMachine || "unknown";
|
|
18157
|
+
if (!content) {
|
|
18158
|
+
return jsonResponse(response, 400, { error: "empty content" });
|
|
18159
|
+
}
|
|
18160
|
+
const hash = computeHash(content);
|
|
18161
|
+
if (hash === currentContentHash) {
|
|
18162
|
+
return jsonResponse(response, 200, { received: false, reason: "loop detected" });
|
|
18163
|
+
}
|
|
18164
|
+
const check2 = sanitizeClipboardForRead(content, config.maxSizeBytes, config.skipPatterns);
|
|
18165
|
+
if (!check2.ok) {
|
|
18166
|
+
return jsonResponse(response, 200, { received: false, reason: check2.reason });
|
|
18167
|
+
}
|
|
18168
|
+
writeLocalClipboardSync(content);
|
|
18169
|
+
currentContentHash = hash;
|
|
18170
|
+
addClipboardEntry({
|
|
18171
|
+
hash,
|
|
18172
|
+
content,
|
|
18173
|
+
contentType,
|
|
18174
|
+
sourceMachine,
|
|
18175
|
+
timestamp: new Date().toISOString()
|
|
18176
|
+
});
|
|
18177
|
+
return jsonResponse(response, 200, { received: true, hash });
|
|
18178
|
+
} catch {
|
|
18179
|
+
return jsonResponse(response, 400, { error: "invalid JSON" });
|
|
18180
|
+
}
|
|
18181
|
+
});
|
|
18182
|
+
}
|
|
18183
|
+
function handleGetClipboard(response, config) {
|
|
18184
|
+
const content = readLocalClipboardSync();
|
|
18185
|
+
if (!content) {
|
|
18186
|
+
return jsonResponse(response, 200, { content: "", hash: null });
|
|
18187
|
+
}
|
|
18188
|
+
const hash = computeHash(content);
|
|
18189
|
+
return jsonResponse(response, 200, { content, hash, contentType: "text" });
|
|
18190
|
+
}
|
|
18191
|
+
|
|
18192
|
+
// src/commands/clipboard-daemon.ts
|
|
18193
|
+
var DAEMON_PID_PATH = join10(getDataDir(), "clipboard-daemon.pid");
|
|
18194
|
+
function readLocalClipboardSync2() {
|
|
18195
|
+
const platform4 = process.platform;
|
|
18196
|
+
if (platform4 === "darwin") {
|
|
18197
|
+
const result = Bun.spawnSync(["pbpaste"], { stdout: "pipe", stderr: "pipe" });
|
|
18198
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18199
|
+
}
|
|
18200
|
+
if (platform4 === "linux") {
|
|
18201
|
+
if (hasCommand3("wl-paste")) {
|
|
18202
|
+
const result = Bun.spawnSync(["wl-paste"], { stdout: "pipe", stderr: "pipe" });
|
|
18203
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18204
|
+
}
|
|
18205
|
+
if (hasCommand3("xclip")) {
|
|
18206
|
+
const result = Bun.spawnSync(["xclip", "-selection", "clipboard", "-o"], { stdout: "pipe", stderr: "pipe" });
|
|
18207
|
+
return result.exitCode === 0 ? result.stdout.toString("utf8").trim() : "";
|
|
18208
|
+
}
|
|
18209
|
+
return "";
|
|
18210
|
+
}
|
|
18211
|
+
return "";
|
|
18212
|
+
}
|
|
18213
|
+
function hasCommand3(binary) {
|
|
18214
|
+
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], { stdout: "ignore", stderr: "ignore", env: process.env });
|
|
18215
|
+
return result.exitCode === 0;
|
|
18216
|
+
}
|
|
18217
|
+
function computeHash2(content) {
|
|
18218
|
+
return createHash3("sha256").update(content).digest("hex").slice(0, 16);
|
|
18219
|
+
}
|
|
18220
|
+
function loadSharedSecret2() {
|
|
18221
|
+
try {
|
|
18222
|
+
return readFileSync9(getClipboardKeyPath(), "utf8").trim();
|
|
18223
|
+
} catch {
|
|
18224
|
+
return "";
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
function writePid(pid) {
|
|
18228
|
+
writeFileSync6(DAEMON_PID_PATH, `${pid}
|
|
18229
|
+
`);
|
|
18230
|
+
}
|
|
18231
|
+
function readPid() {
|
|
18232
|
+
try {
|
|
18233
|
+
const pid = Number.parseInt(readFileSync9(DAEMON_PID_PATH, "utf8").trim());
|
|
18234
|
+
return Number.isFinite(pid) ? pid : null;
|
|
18235
|
+
} catch {
|
|
18236
|
+
return null;
|
|
18237
|
+
}
|
|
18238
|
+
}
|
|
18239
|
+
function isProcessRunning(pid) {
|
|
18240
|
+
try {
|
|
18241
|
+
process.kill(pid, 0);
|
|
18242
|
+
return true;
|
|
18243
|
+
} catch {
|
|
18244
|
+
return false;
|
|
18245
|
+
}
|
|
18246
|
+
}
|
|
18247
|
+
function stopClipboardDaemon() {
|
|
18248
|
+
const pid = readPid();
|
|
18249
|
+
if (pid && isProcessRunning(pid)) {
|
|
18250
|
+
process.kill(pid, "SIGTERM");
|
|
18251
|
+
return { stopped: true, pid };
|
|
18252
|
+
}
|
|
18253
|
+
return { stopped: false, pid };
|
|
18254
|
+
}
|
|
18255
|
+
function startClipboardDaemon(port) {
|
|
18256
|
+
const config = readClipboardConfig();
|
|
18257
|
+
const daemonPort = port || config.port;
|
|
18258
|
+
const { server, close } = startClipboardServer({ port: daemonPort });
|
|
18259
|
+
server.on("listening", () => {
|
|
18260
|
+
console.log(`clipboard daemon started on port ${daemonPort} (pid ${process.pid})`);
|
|
18261
|
+
writePid(process.pid);
|
|
18262
|
+
});
|
|
18263
|
+
let lastHash = "";
|
|
18264
|
+
const secret = loadSharedSecret2();
|
|
18265
|
+
const machineId = process.env["HASNA_MACHINES_MACHINE_ID"] || "unknown";
|
|
18266
|
+
setInterval(async () => {
|
|
18267
|
+
const content = readLocalClipboardSync2();
|
|
18268
|
+
if (!content)
|
|
18269
|
+
return;
|
|
18270
|
+
const hash = computeHash2(content);
|
|
18271
|
+
if (hash === lastHash)
|
|
18272
|
+
return;
|
|
18273
|
+
lastHash = hash;
|
|
18274
|
+
const peers = await discoverPeers();
|
|
18275
|
+
for (const peer of peers) {
|
|
18276
|
+
try {
|
|
18277
|
+
const res = await fetch(`http://${peer.host}:${peer.port}/clipboard`, {
|
|
18278
|
+
method: "POST",
|
|
18279
|
+
headers: {
|
|
18280
|
+
"content-type": "application/json",
|
|
18281
|
+
authorization: `Bearer ${secret}`
|
|
18282
|
+
},
|
|
18283
|
+
body: JSON.stringify({
|
|
18284
|
+
content,
|
|
18285
|
+
contentType: "text",
|
|
18286
|
+
sourceMachine: machineId
|
|
18287
|
+
}),
|
|
18288
|
+
signal: AbortSignal.timeout(2000)
|
|
18289
|
+
});
|
|
18290
|
+
if (res.ok) {
|
|
18291
|
+
const data = await res.json();
|
|
18292
|
+
if (data["received"] === true) {
|
|
18293
|
+
console.log(`clipboard sent to ${peer.host}`);
|
|
18294
|
+
}
|
|
18295
|
+
}
|
|
18296
|
+
} catch {}
|
|
18297
|
+
}
|
|
18298
|
+
}, 500);
|
|
18299
|
+
}
|
|
18300
|
+
async function discoverPeers() {
|
|
18301
|
+
const config = readClipboardConfig();
|
|
18302
|
+
const peers = [];
|
|
18303
|
+
try {
|
|
18304
|
+
const result = Bun.spawnSync(["tailscale", "status", "--json"], { stdout: "pipe", stderr: "pipe", env: process.env });
|
|
18305
|
+
if (result.exitCode === 0) {
|
|
18306
|
+
const status = JSON.parse(result.stdout.toString("utf8"));
|
|
18307
|
+
const peers_map = status["Peer"] || {};
|
|
18308
|
+
for (const [, peerInfo] of Object.entries(peers_map)) {
|
|
18309
|
+
for (const ip of peerInfo.TailscaleIPs) {
|
|
18310
|
+
if (ip.includes(".") && !ip.endsWith(".1")) {
|
|
18311
|
+
peers.push({ host: ip, port: config.port });
|
|
18312
|
+
}
|
|
18313
|
+
}
|
|
18314
|
+
}
|
|
18315
|
+
}
|
|
18316
|
+
} catch {}
|
|
18317
|
+
const knownPeers = ["100.82.44.120", "100.100.226.69", "100.71.123.34", "100.85.234.92"];
|
|
18318
|
+
for (const ip of knownPeers) {
|
|
18319
|
+
if (!peers.some((p) => p.host === ip)) {
|
|
18320
|
+
peers.push({ host: ip, port: config.port });
|
|
18321
|
+
}
|
|
18322
|
+
}
|
|
18323
|
+
return peers;
|
|
18324
|
+
}
|
|
18325
|
+
|
|
17831
18326
|
// src/cli-utils.ts
|
|
17832
18327
|
function parseIntegerOption(value, label, constraints = {}) {
|
|
17833
18328
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -17858,9 +18353,11 @@ ${items.map((item) => `- ${item}`).join(`
|
|
|
17858
18353
|
}
|
|
17859
18354
|
|
|
17860
18355
|
// src/cli/index.ts
|
|
18356
|
+
import { rmSync as rmSync2 } from "fs";
|
|
18357
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
17861
18358
|
var program2 = new Command;
|
|
17862
18359
|
function printJsonOrText(data, text, json = false) {
|
|
17863
|
-
if (json) {
|
|
18360
|
+
if (json || program2.opts().quiet) {
|
|
17864
18361
|
console.log(JSON.stringify(data, null, 2));
|
|
17865
18362
|
return;
|
|
17866
18363
|
}
|
|
@@ -17965,10 +18462,11 @@ function renderFleetStatus(status) {
|
|
|
17965
18462
|
].join(`
|
|
17966
18463
|
`);
|
|
17967
18464
|
}
|
|
17968
|
-
program2.name("machines").description("Machine fleet management CLI + MCP for developers").version(getPackageVersion());
|
|
18465
|
+
program2.name("machines").description("Machine fleet management CLI + MCP for developers").version(getPackageVersion()).option("-q, --quiet", "Suppress non-essential output");
|
|
17969
18466
|
var manifestCommand = program2.command("manifest").description("Manage the fleet manifest");
|
|
17970
18467
|
var appsCommand = program2.command("apps").description("Manage installed applications per machine");
|
|
17971
18468
|
var notificationsCommand = program2.command("notifications").description("Manage fleet alert delivery channels");
|
|
18469
|
+
var clipboardCommand = program2.command("clipboard").description("Real-time clipboard sync across fleet machines");
|
|
17972
18470
|
var installClaudeCommand = program2.command("install-claude").description("Install or inspect Claude, Codex, and Gemini CLIs");
|
|
17973
18471
|
manifestCommand.command("init").description("Create an empty fleet manifest").action(() => {
|
|
17974
18472
|
console.log(manifestInit());
|
|
@@ -17997,7 +18495,17 @@ manifestCommand.command("get").description("Print a single machine from the mani
|
|
|
17997
18495
|
manifestCommand.command("remove").description("Remove a machine from the manifest").argument("<id>", "Machine identifier").action((id) => {
|
|
17998
18496
|
console.log(JSON.stringify(manifestRemove(id), null, 2));
|
|
17999
18497
|
});
|
|
18000
|
-
manifestCommand.command("add").description("Add or replace a machine in the fleet manifest").
|
|
18498
|
+
manifestCommand.command("add").description("Add or replace a machine in the fleet manifest").option("--id <id>", "Machine identifier").option("--platform <platform>", "linux | macos | windows").option("--workspace-path <path>", "Primary workspace path").option("--hostname <hostname>", "Machine hostname").option("--ssh-address <sshAddress>", "Machine SSH address").option("--tailscale-name <tailscaleName>", "Machine Tailscale DNS name").option("--connection <connection>", "local | ssh | tailscale").option("--bun-path <path>", "Bun executable directory").option("--tag <tag...>", "Machine tags").option("--package <name...>", "Desired packages").option("--app <spec...>", "Desired apps as name[:manager[:packageName]]").option("--file <spec...>", "File sync spec source:target[:copy|symlink]").option("--metadata <json>", "Machine metadata as JSON").option("--from-stdin", "Read the full MachineManifest JSON from stdin").action((options) => {
|
|
18499
|
+
if (options["from-stdin"]) {
|
|
18500
|
+
if (process.stdin.isTTY) {
|
|
18501
|
+
console.error("error: --from-stdin requires piped input");
|
|
18502
|
+
process.exit(1);
|
|
18503
|
+
}
|
|
18504
|
+
const input = readFileSync10(0, "utf8");
|
|
18505
|
+
const machine2 = JSON.parse(input);
|
|
18506
|
+
console.log(JSON.stringify(manifestAdd(machine2), null, 2));
|
|
18507
|
+
return;
|
|
18508
|
+
}
|
|
18001
18509
|
const packages = Array.isArray(options["package"]) ? options["package"].map((name) => ({ name: String(name) })) : undefined;
|
|
18002
18510
|
const files = Array.isArray(options["file"]) ? options["file"].map((value) => {
|
|
18003
18511
|
const [source, target, mode] = String(value).split(":");
|
|
@@ -18012,6 +18520,7 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
|
|
|
18012
18520
|
packageName
|
|
18013
18521
|
};
|
|
18014
18522
|
}) : undefined;
|
|
18523
|
+
const metadata = typeof options["metadata"] === "string" ? JSON.parse(options["metadata"]) : undefined;
|
|
18015
18524
|
const machine = {
|
|
18016
18525
|
id: String(options["id"]),
|
|
18017
18526
|
hostname: options["hostname"] ? String(options["hostname"]) : undefined,
|
|
@@ -18022,6 +18531,7 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
|
|
|
18022
18531
|
workspacePath: String(options["workspacePath"]),
|
|
18023
18532
|
bunPath: options["bunPath"] ? String(options["bunPath"]) : undefined,
|
|
18024
18533
|
tags: Array.isArray(options["tag"]) ? options["tag"].map(String) : undefined,
|
|
18534
|
+
metadata,
|
|
18025
18535
|
packages,
|
|
18026
18536
|
apps,
|
|
18027
18537
|
files
|
|
@@ -18040,13 +18550,13 @@ appsCommand.command("diff").description("Show missing and installed manifest-man
|
|
|
18040
18550
|
const result = diffApps(options.machine);
|
|
18041
18551
|
printJsonOrText(result, renderAppsDiffResult(result), options.json);
|
|
18042
18552
|
});
|
|
18043
|
-
appsCommand.command("plan").description("Preview app install steps for a machine").option("--machine <id>", "Machine identifier").
|
|
18553
|
+
appsCommand.command("plan").description("Preview app install steps for a machine").option("--machine <id>", "Machine identifier").action((options) => {
|
|
18044
18554
|
const result = buildAppsPlan(options.machine);
|
|
18045
|
-
console.log(
|
|
18555
|
+
console.log(JSON.stringify(result, null, 2));
|
|
18046
18556
|
});
|
|
18047
|
-
appsCommand.command("apply").description("Install manifest-managed apps for a machine").option("--machine <id>", "Machine identifier").option("--yes", "Confirm execution", false).
|
|
18557
|
+
appsCommand.command("apply").description("Install manifest-managed apps for a machine").option("--machine <id>", "Machine identifier").option("--yes", "Confirm execution", false).action((options) => {
|
|
18048
18558
|
const result = runAppsInstall(options.machine, { apply: true, yes: options.yes });
|
|
18049
|
-
console.log(
|
|
18559
|
+
console.log(JSON.stringify(result, null, 2));
|
|
18050
18560
|
});
|
|
18051
18561
|
program2.command("setup").description("Prepare a machine from the fleet manifest").option("--machine <id>", "Machine identifier").option("--apply", "Execute provisioning commands instead of previewing the plan", false).option("--yes", "Confirm execution when using --apply", false).option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18052
18562
|
const result = options.apply ? runSetup(options.machine, { apply: true, yes: options.yes }) : buildSetupPlan(options.machine);
|
|
@@ -18109,6 +18619,75 @@ notificationsCommand.command("remove").description("Remove a notification channe
|
|
|
18109
18619
|
const result = removeNotificationChannel(id);
|
|
18110
18620
|
printJsonOrText(result, renderNotificationConfigResult(result), options.json);
|
|
18111
18621
|
});
|
|
18622
|
+
clipboardCommand.command("init").description("Initialize clipboard sync (generate shared secret)").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18623
|
+
const key = getOrCreateClipboardKey();
|
|
18624
|
+
const config = getDefaultClipboardConfig();
|
|
18625
|
+
writeClipboardConfig(config);
|
|
18626
|
+
const result = { keyPath: getClipboardKeyPath(), key, configPath: getConfigPath2(), config };
|
|
18627
|
+
printJsonOrText(result, `clipboard initialized
|
|
18628
|
+
key: ${key}
|
|
18629
|
+
port: ${config.port}`, options.json);
|
|
18630
|
+
});
|
|
18631
|
+
clipboardCommand.command("status").description("Check clipboard sync status").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18632
|
+
const status = getClipboardStatus();
|
|
18633
|
+
const config = readClipboardConfig();
|
|
18634
|
+
const result = { ...status, enabled: config.enabled };
|
|
18635
|
+
printJsonOrText(result, `clipboard sync ${config.enabled ? source_default.green("enabled") : source_default.yellow("disabled")} (port ${status.port}, ${status.historyCount} entries)`, options.json);
|
|
18636
|
+
});
|
|
18637
|
+
clipboardCommand.command("config").description("View or set clipboard sync config").option("--set <json>", "Set config values as JSON").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18638
|
+
if (options.set) {
|
|
18639
|
+
const partial = JSON.parse(options.set);
|
|
18640
|
+
const config2 = { ...readClipboardConfig(), ...partial };
|
|
18641
|
+
writeClipboardConfig(config2);
|
|
18642
|
+
}
|
|
18643
|
+
const config = readClipboardConfig();
|
|
18644
|
+
printJsonOrText(config, renderKeyValueTable([
|
|
18645
|
+
["enabled", String(config.enabled)],
|
|
18646
|
+
["port", String(config.port)],
|
|
18647
|
+
["maxHistory", String(config.maxHistory)],
|
|
18648
|
+
["maxSizeBytes", `${config.maxSizeBytes} bytes`],
|
|
18649
|
+
["skipPatterns", config.skipPatterns.join(", ")]
|
|
18650
|
+
]), options.json);
|
|
18651
|
+
});
|
|
18652
|
+
clipboardCommand.command("history").description("Show clipboard sync history").option("-j, --json", "Print JSON output", false).option("--limit <n>", "Show only the last N entries", "20").action((options) => {
|
|
18653
|
+
const limit = parseIntegerOption(options.limit, "limit", { min: 1, max: 100 });
|
|
18654
|
+
const entries = readClipboardHistory().slice(0, limit);
|
|
18655
|
+
if (options.json) {
|
|
18656
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
18657
|
+
return;
|
|
18658
|
+
}
|
|
18659
|
+
if (entries.length === 0) {
|
|
18660
|
+
console.log("clipboard history: empty");
|
|
18661
|
+
return;
|
|
18662
|
+
}
|
|
18663
|
+
for (const entry of entries) {
|
|
18664
|
+
const preview = entry.content.length > 80 ? `${entry.content.slice(0, 80)}...` : entry.content;
|
|
18665
|
+
console.log(`${source_default.dim(entry.timestamp)} ${entry.sourceMachine.padEnd(12)} ${entry.contentType.padEnd(5)} ${preview.replace(/\n/g, " ")}`);
|
|
18666
|
+
}
|
|
18667
|
+
});
|
|
18668
|
+
clipboardCommand.command("clear-history").description("Clear clipboard sync history").option("--yes", "Confirm without prompt", false).action((options) => {
|
|
18669
|
+
if (!options.yes) {
|
|
18670
|
+
console.error("error: this command requires --yes");
|
|
18671
|
+
process.exit(1);
|
|
18672
|
+
}
|
|
18673
|
+
clearClipboardHistory();
|
|
18674
|
+
console.log("clipboard history cleared");
|
|
18675
|
+
});
|
|
18676
|
+
clipboardCommand.command("key").description("Show or rotate the shared secret key").option("--rotate", "Generate a new key", false).option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18677
|
+
if (options.rotate) {
|
|
18678
|
+
rmSync2(getClipboardKeyPath(), { force: true });
|
|
18679
|
+
}
|
|
18680
|
+
const key = getOrCreateClipboardKey();
|
|
18681
|
+
printJsonOrText({ key }, key, options.json);
|
|
18682
|
+
});
|
|
18683
|
+
clipboardCommand.command("start").description("Start clipboard sync daemon").option("--port <port>", "Port to listen on").action((options) => {
|
|
18684
|
+
const port = options.port ? Number(options.port) : undefined;
|
|
18685
|
+
startClipboardDaemon(port);
|
|
18686
|
+
});
|
|
18687
|
+
clipboardCommand.command("stop").description("Stop clipboard sync daemon").action(() => {
|
|
18688
|
+
const result = stopClipboardDaemon();
|
|
18689
|
+
console.log(result.stopped ? `daemon stopped (pid ${result.pid})` : "daemon not running");
|
|
18690
|
+
});
|
|
18112
18691
|
installClaudeCommand.command("status").description("Check installed state for Claude, Codex, and Gemini CLIs").option("--machine <id>", "Machine identifier").option("--tool <name...>", "CLI tools to inspect (claude, codex, gemini)").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18113
18692
|
const result = getClaudeCliStatus(options.machine, options.tool);
|
|
18114
18693
|
printJsonOrText(result, renderClaudeStatusResult(result), options.json);
|
|
@@ -18125,10 +18704,6 @@ installClaudeCommand.command("apply").description("Install or update the request
|
|
|
18125
18704
|
const result = runClaudeInstall(options.machine, options.tool, { apply: true, yes: options.yes });
|
|
18126
18705
|
console.log(JSON.stringify(result, null, 2));
|
|
18127
18706
|
});
|
|
18128
|
-
installClaudeCommand.option("--machine <id>", "Machine identifier").option("--tool <name...>", "CLI tools to install (claude, codex, gemini)").option("--apply", "Execute installation commands instead of previewing the plan", false).option("--yes", "Confirm execution when using --apply", false).option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18129
|
-
const result = options.apply ? runClaudeInstall(options.machine, options.tool, { apply: true, yes: options.yes }) : buildClaudeInstallPlan(options.machine, options.tool);
|
|
18130
|
-
console.log(JSON.stringify(result, null, 2));
|
|
18131
|
-
});
|
|
18132
18707
|
program2.command("install-tailscale").description("Install Tailscale on a machine").option("--machine <id>", "Machine identifier").option("--apply", "Execute installation commands instead of previewing the plan", false).option("--yes", "Confirm execution when using --apply", false).option("-j, --json", "Print JSON output", false).action((options) => {
|
|
18133
18708
|
const result = options.apply ? runTailscaleInstall(options.machine, { apply: true, yes: options.yes }) : buildTailscaleInstallPlan(options.machine);
|
|
18134
18709
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard-daemon.d.ts","sourceRoot":"","sources":["../../src/commands/clipboard-daemon.ts"],"names":[],"mappings":"AAwFA,wBAAgB,mBAAmB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAO9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAqDxD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import type { ClipboardConfig } from "../types.js";
|
|
3
|
+
export interface ClipboardServerOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
config?: ClipboardConfig;
|
|
6
|
+
}
|
|
7
|
+
export interface ClipboardServerHandle {
|
|
8
|
+
server: ReturnType<typeof createServer>;
|
|
9
|
+
port: number;
|
|
10
|
+
close: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function startClipboardServer(options?: ClipboardServerOptions): ClipboardServerHandle;
|
|
13
|
+
export declare function pushClipboardToPeer(host: string, port: number, token: string): Promise<{
|
|
14
|
+
sent: boolean;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=clipboard-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard-server.d.ts","sourceRoot":"","sources":["../../src/commands/clipboard-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AAKpF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,aAAa,CAAC;AAuFnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAID,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,CA6ChG;AA6DD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA4ChI"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClipboardConfig, ClipboardEntry, ClipboardStatus } from "../types.js";
|
|
2
|
+
export declare function resolveConfigPath(configPath?: string): string;
|
|
3
|
+
export declare function resolveHistoryPath(historyPath?: string): string;
|
|
4
|
+
export declare function computeHash(content: string): string;
|
|
5
|
+
export declare function shouldSkipContent(content: string, skipPatterns: string[]): boolean;
|
|
6
|
+
export declare function sanitizeClipboardForRead(content: string, maxSizeBytes: number, skipPatterns: string[]): {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
reason?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function getOrCreateClipboardKey(): string;
|
|
11
|
+
export declare function getDefaultClipboardConfig(): ClipboardConfig;
|
|
12
|
+
export declare function getConfigPath(configPath?: string): string;
|
|
13
|
+
export declare function readClipboardConfig(configPath?: string): ClipboardConfig;
|
|
14
|
+
export declare function writeClipboardConfig(config: ClipboardConfig, configPath?: string): void;
|
|
15
|
+
export declare function readClipboardHistory(historyPath?: string): ClipboardEntry[];
|
|
16
|
+
export declare function addClipboardEntry(entry: ClipboardEntry, historyPath?: string): void;
|
|
17
|
+
export declare function clearClipboardHistory(historyPath?: string): void;
|
|
18
|
+
export declare function getClipboardStatus(historyPath?: string): ClipboardStatus;
|
|
19
|
+
//# sourceMappingURL=clipboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/commands/clipboard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAiBpF,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/D;AAuCD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAGlF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAQxI;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAShD;AAED,wBAAgB,yBAAyB,IAAI,eAAe,CAE3D;AAED,wBAAgB,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,eAAe,CAExE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAEvF;AAED,wBAAgB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAenF;AAED,wBAAgB,qBAAqB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAKhE;AAED,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,CAQxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAWD,wBAAgB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,SAAS,CAsBlE;AAED,wBAAgB,mBAAmB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAWD,wBAAgB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,SAAS,CAsBlE;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CA+J5C;AAcD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,YAAiB,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAkE7F"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cross-project types for open-knowledge, open-crm, and open-machines.
|
|
3
|
+
*
|
|
4
|
+
* Each project vendors this file. Keep it small — add fields only when all three
|
|
5
|
+
* projects need them.
|
|
6
|
+
*/
|
|
7
|
+
export type ProjectName = "open-knowledge" | "open-crm" | "open-machines";
|
|
8
|
+
/**
|
|
9
|
+
* A reference from a record in one project to a record in another.
|
|
10
|
+
* Stored in `metadata._crossRefs` on the owning entity.
|
|
11
|
+
*/
|
|
12
|
+
export interface CrossRef {
|
|
13
|
+
source_project: ProjectName;
|
|
14
|
+
source_id: string;
|
|
15
|
+
target_project: ProjectName;
|
|
16
|
+
target_id: string;
|
|
17
|
+
/** Optional human description of the relationship. */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Agent or user that created this reference. */
|
|
20
|
+
created_by?: string;
|
|
21
|
+
/** ISO timestamp of creation. */
|
|
22
|
+
created_at?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Reserved key inside `metadata` for cross-project references. */
|
|
25
|
+
export declare const CROSSREFS_KEY: "_crossRefs";
|
|
26
|
+
//# sourceMappingURL=cross-project-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-project-types.d.ts","sourceRoot":"","sources":["../src/cross-project-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,UAAU,GAAG,eAAe,CAAC;AAE1E;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,cAAc,EAAE,WAAW,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,WAAW,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAG,YAAqB,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
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;
|
|
@@ -21357,11 +21365,13 @@ function renderDashboardHtml() {
|
|
|
21357
21365
|
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
21358
21366
|
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
21359
21367
|
.muted { color: #9fb0d9; }
|
|
21368
|
+
.refresh { font-size: 12px; color: #6b7fa3; margin-left: auto; }
|
|
21369
|
+
.updated { transition: opacity 0.3s; }
|
|
21360
21370
|
</style>
|
|
21361
21371
|
</head>
|
|
21362
21372
|
<body>
|
|
21363
21373
|
<main>
|
|
21364
|
-
<h1>Machines Dashboard
|
|
21374
|
+
<h1>Machines Dashboard <span class="refresh" id="last-updated"></span></h1>
|
|
21365
21375
|
<div class="grid">
|
|
21366
21376
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
21367
21377
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
@@ -21388,7 +21398,7 @@ function renderDashboardHtml() {
|
|
|
21388
21398
|
<h2>Doctor</h2>
|
|
21389
21399
|
<table>
|
|
21390
21400
|
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
21391
|
-
<tbody>
|
|
21401
|
+
<tbody id="doctor-tbody">
|
|
21392
21402
|
${doctor.checks.map((entry) => `<tr>
|
|
21393
21403
|
<td>${escapeHtml(entry.summary)}</td>
|
|
21394
21404
|
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
@@ -21398,6 +21408,16 @@ function renderDashboardHtml() {
|
|
|
21398
21408
|
</table>
|
|
21399
21409
|
</section>
|
|
21400
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
|
+
|
|
21401
21421
|
<section class="card" style="margin-top:16px">
|
|
21402
21422
|
<h2>Self Test</h2>
|
|
21403
21423
|
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
@@ -21405,9 +21425,66 @@ function renderDashboardHtml() {
|
|
|
21405
21425
|
|
|
21406
21426
|
<section class="card" style="margin-top:16px">
|
|
21407
21427
|
<h2>Manifest</h2>
|
|
21408
|
-
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
21428
|
+
<pre id="manifest-json">${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
21409
21429
|
</section>
|
|
21410
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>
|
|
21411
21488
|
</body>
|
|
21412
21489
|
</html>`;
|
|
21413
21490
|
}
|
|
@@ -30882,6 +30959,8 @@ export {
|
|
|
30882
30959
|
getDbPath,
|
|
30883
30960
|
getDb,
|
|
30884
30961
|
getDataDir,
|
|
30962
|
+
getClipboardKeyPath,
|
|
30963
|
+
getClipboardHistoryPath,
|
|
30885
30964
|
getClaudeCliStatus,
|
|
30886
30965
|
getAppsStatus,
|
|
30887
30966
|
getAgentStatus,
|
|
@@ -30906,5 +30985,6 @@ export {
|
|
|
30906
30985
|
buildAppsPlan,
|
|
30907
30986
|
addNotificationChannel,
|
|
30908
30987
|
addDomainMapping,
|
|
30909
|
-
MACHINE_MCP_TOOL_NAMES
|
|
30988
|
+
MACHINE_MCP_TOOL_NAMES,
|
|
30989
|
+
CROSSREFS_KEY
|
|
30910
30990
|
};
|
package/dist/mcp/index.js
CHANGED
|
@@ -14866,11 +14866,13 @@ function renderDashboardHtml() {
|
|
|
14866
14866
|
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
14867
14867
|
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
14868
14868
|
.muted { color: #9fb0d9; }
|
|
14869
|
+
.refresh { font-size: 12px; color: #6b7fa3; margin-left: auto; }
|
|
14870
|
+
.updated { transition: opacity 0.3s; }
|
|
14869
14871
|
</style>
|
|
14870
14872
|
</head>
|
|
14871
14873
|
<body>
|
|
14872
14874
|
<main>
|
|
14873
|
-
<h1>Machines Dashboard
|
|
14875
|
+
<h1>Machines Dashboard <span class="refresh" id="last-updated"></span></h1>
|
|
14874
14876
|
<div class="grid">
|
|
14875
14877
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
14876
14878
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
@@ -14897,7 +14899,7 @@ function renderDashboardHtml() {
|
|
|
14897
14899
|
<h2>Doctor</h2>
|
|
14898
14900
|
<table>
|
|
14899
14901
|
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
14900
|
-
<tbody>
|
|
14902
|
+
<tbody id="doctor-tbody">
|
|
14901
14903
|
${doctor.checks.map((entry) => `<tr>
|
|
14902
14904
|
<td>${escapeHtml(entry.summary)}</td>
|
|
14903
14905
|
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
@@ -14907,6 +14909,16 @@ function renderDashboardHtml() {
|
|
|
14907
14909
|
</table>
|
|
14908
14910
|
</section>
|
|
14909
14911
|
|
|
14912
|
+
<section class="card" style="margin-top:16px">
|
|
14913
|
+
<h2>Apps</h2>
|
|
14914
|
+
<p class="muted">Use <code>/api/apps/status</code> for the full app inventory payload.</p>
|
|
14915
|
+
</section>
|
|
14916
|
+
|
|
14917
|
+
<section class="card" style="margin-top:16px">
|
|
14918
|
+
<h2>AI CLIs</h2>
|
|
14919
|
+
<p class="muted">Use <code>/api/install-claude/status</code> for the full CLI inventory payload.</p>
|
|
14920
|
+
</section>
|
|
14921
|
+
|
|
14910
14922
|
<section class="card" style="margin-top:16px">
|
|
14911
14923
|
<h2>Self Test</h2>
|
|
14912
14924
|
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
@@ -14914,9 +14926,66 @@ function renderDashboardHtml() {
|
|
|
14914
14926
|
|
|
14915
14927
|
<section class="card" style="margin-top:16px">
|
|
14916
14928
|
<h2>Manifest</h2>
|
|
14917
|
-
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
14929
|
+
<pre id="manifest-json">${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
14918
14930
|
</section>
|
|
14919
14931
|
</main>
|
|
14932
|
+
<script>
|
|
14933
|
+
// Auto-refresh dashboard data every 15s
|
|
14934
|
+
const REFRESH_INTERVAL = 15000;
|
|
14935
|
+
async function refreshData() {
|
|
14936
|
+
try {
|
|
14937
|
+
const [statusRes, doctorRes] = await Promise.all([
|
|
14938
|
+
fetch("/api/status"),
|
|
14939
|
+
fetch("/api/doctor"),
|
|
14940
|
+
]);
|
|
14941
|
+
const status = await statusRes.json();
|
|
14942
|
+
const doctor = await doctorRes.json();
|
|
14943
|
+
|
|
14944
|
+
// Update stat cards
|
|
14945
|
+
const stats = document.querySelectorAll(".stat");
|
|
14946
|
+
if (stats[0]) stats[0].textContent = status.manifestMachineCount;
|
|
14947
|
+
if (stats[1]) stats[1].textContent = status.heartbeatCount;
|
|
14948
|
+
|
|
14949
|
+
// Update machine table
|
|
14950
|
+
const tbody = document.querySelector("tbody");
|
|
14951
|
+
if (tbody && status.machines) {
|
|
14952
|
+
tbody.innerHTML = status.machines
|
|
14953
|
+
.map((m) =>
|
|
14954
|
+
"<tr>" +
|
|
14955
|
+
"<td><code>" + m.machineId + "</code></td>" +
|
|
14956
|
+
"<td>" + (m.platform || "unknown") + "</td>" +
|
|
14957
|
+
'<td><span class="badge ' + m.heartbeatStatus + '">' + m.heartbeatStatus + '</span></td>' +
|
|
14958
|
+
"<td>" + (m.lastHeartbeatAt || "\\u2014") + "</td>" +
|
|
14959
|
+
"</tr>"
|
|
14960
|
+
)
|
|
14961
|
+
.join("");
|
|
14962
|
+
}
|
|
14963
|
+
|
|
14964
|
+
// Update doctor table
|
|
14965
|
+
const doctorTbody = document.getElementById("doctor-tbody");
|
|
14966
|
+
if (doctorTbody && doctor.checks) {
|
|
14967
|
+
doctorTbody.innerHTML = doctor.checks
|
|
14968
|
+
.map((c) =>
|
|
14969
|
+
"<tr>" +
|
|
14970
|
+
"<td>" + c.summary + "</td>" +
|
|
14971
|
+
'<td><span class="badge ' + c.status + '">' + c.status + '</span></td>' +
|
|
14972
|
+
'<td class="muted">' + c.detail + "</td>" +
|
|
14973
|
+
"</tr>"
|
|
14974
|
+
)
|
|
14975
|
+
.join("");
|
|
14976
|
+
}
|
|
14977
|
+
|
|
14978
|
+
// Update timestamp
|
|
14979
|
+
document.getElementById("last-updated").textContent =
|
|
14980
|
+
"updated " + new Date().toLocaleTimeString();
|
|
14981
|
+
} catch (e) {
|
|
14982
|
+
// Silently ignore fetch errors during page unload
|
|
14983
|
+
}
|
|
14984
|
+
}
|
|
14985
|
+
document.getElementById("last-updated").textContent =
|
|
14986
|
+
"updated " + new Date().toLocaleTimeString();
|
|
14987
|
+
setInterval(refreshData, REFRESH_INTERVAL);
|
|
14988
|
+
</script>
|
|
14920
14989
|
</body>
|
|
14921
14990
|
</html>`;
|
|
14922
14991
|
}
|
package/dist/paths.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export declare function getDataDir(): string;
|
|
|
2
2
|
export declare function getDbPath(): string;
|
|
3
3
|
export declare function getManifestPath(): string;
|
|
4
4
|
export declare function getNotificationsPath(): string;
|
|
5
|
+
export declare function getClipboardKeyPath(): string;
|
|
6
|
+
export declare function getClipboardHistoryPath(): string;
|
|
5
7
|
export declare function ensureParentDir(filePath: string): void;
|
|
6
8
|
export declare function ensureDataDir(): string;
|
|
7
9
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMtD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAItC"}
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMtD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAItC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface MachineManifest {
|
|
|
25
25
|
workspacePath: string;
|
|
26
26
|
bunPath?: string;
|
|
27
27
|
tags?: string[];
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
28
29
|
packages?: ManifestPackageSpec[];
|
|
29
30
|
apps?: ManifestAppSpec[];
|
|
30
31
|
files?: ManifestFileSyncSpec[];
|
|
@@ -184,4 +185,33 @@ export interface SelfTestResult {
|
|
|
184
185
|
machineId: string;
|
|
185
186
|
checks: SelfTestCheck[];
|
|
186
187
|
}
|
|
188
|
+
export interface ClipboardEntry {
|
|
189
|
+
hash: string;
|
|
190
|
+
content: string;
|
|
191
|
+
contentType: "text" | "rich" | "url";
|
|
192
|
+
sourceMachine: string;
|
|
193
|
+
timestamp: string;
|
|
194
|
+
}
|
|
195
|
+
export interface ClipboardConfig {
|
|
196
|
+
version: 1;
|
|
197
|
+
enabled: boolean;
|
|
198
|
+
port: number;
|
|
199
|
+
maxHistory: number;
|
|
200
|
+
maxSizeBytes: number;
|
|
201
|
+
skipPatterns: string[];
|
|
202
|
+
}
|
|
203
|
+
export interface ClipboardStatus {
|
|
204
|
+
running: boolean;
|
|
205
|
+
pid?: number;
|
|
206
|
+
port: number;
|
|
207
|
+
lastSync?: string;
|
|
208
|
+
historyCount: number;
|
|
209
|
+
}
|
|
210
|
+
export interface ClipboardSyncEvent {
|
|
211
|
+
hash: string;
|
|
212
|
+
content: string;
|
|
213
|
+
contentType: "text" | "rich" | "url";
|
|
214
|
+
sourceMachine: string;
|
|
215
|
+
timestamp: string;
|
|
216
|
+
}
|
|
187
217
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|