@cryptiklemur/lattice 1.37.1 → 1.38.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|
|
@@ -5,7 +5,8 @@ import { loadConfig } from "../config";
|
|
|
5
5
|
import { loadOrCreateIdentity } from "../identity";
|
|
6
6
|
import { generateInviteCode, parseInviteCode, validatePairingToken, consumePairingToken } from "../mesh/pairing";
|
|
7
7
|
import { addPeer, removePeer, loadPeers, getPeer } from "../mesh/peers";
|
|
8
|
-
import { getConnectedPeerIds, connectToPeer, reconnectPeer, getPeerConnection, disconnectPeer, getConnectedPeerProjects } from "../mesh/connector";
|
|
8
|
+
import { getConnectedPeerIds, connectToPeer, reconnectPeer, getPeerConnection, disconnectPeer, getConnectedPeerProjects, registerInboundPeer } from "../mesh/connector";
|
|
9
|
+
import { getClientWebSocket } from "../ws/broadcast";
|
|
9
10
|
import type { PeerInfo } from "@lattice/shared";
|
|
10
11
|
import { networkInterfaces } from "node:os";
|
|
11
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -237,6 +238,12 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
|
|
|
237
238
|
sendTo(clientId, { type: "mesh:hello_rejected" as any, error: "Public key mismatch — possible impersonation" });
|
|
238
239
|
return;
|
|
239
240
|
}
|
|
241
|
+
|
|
242
|
+
var inboundWs = getClientWebSocket(clientId);
|
|
243
|
+
if (inboundWs) {
|
|
244
|
+
registerInboundPeer(hello.nodeId, inboundWs as any);
|
|
245
|
+
}
|
|
246
|
+
|
|
240
247
|
var identity = loadOrCreateIdentity();
|
|
241
248
|
sendTo(clientId, {
|
|
242
249
|
type: "mesh:hello" as any,
|
package/server/src/index.ts
CHANGED
|
@@ -72,15 +72,35 @@ switch (command) {
|
|
|
72
72
|
case "stop":
|
|
73
73
|
runStop();
|
|
74
74
|
break;
|
|
75
|
+
case "restart":
|
|
76
|
+
runRestart();
|
|
77
|
+
break;
|
|
75
78
|
case "status":
|
|
76
79
|
runStatus();
|
|
77
80
|
break;
|
|
78
81
|
case "update":
|
|
79
82
|
await runUpdate();
|
|
80
83
|
break;
|
|
84
|
+
case "version":
|
|
85
|
+
await runVersion();
|
|
86
|
+
break;
|
|
87
|
+
case "logs":
|
|
88
|
+
runLogs();
|
|
89
|
+
break;
|
|
90
|
+
case "open":
|
|
91
|
+
runOpen();
|
|
92
|
+
break;
|
|
93
|
+
case "config":
|
|
94
|
+
runConfigInfo();
|
|
95
|
+
break;
|
|
96
|
+
case "help":
|
|
97
|
+
case "--help":
|
|
98
|
+
case "-h":
|
|
99
|
+
runHelp();
|
|
100
|
+
break;
|
|
81
101
|
default:
|
|
82
102
|
console.log("[lattice] Unknown command: " + command);
|
|
83
|
-
|
|
103
|
+
runHelp();
|
|
84
104
|
process.exit(1);
|
|
85
105
|
}
|
|
86
106
|
|
|
@@ -180,6 +200,139 @@ function runStop(): void {
|
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
|
|
203
|
+
function runHelp(): void {
|
|
204
|
+
console.log("");
|
|
205
|
+
console.log(" lattice — Multi-machine agentic dashboard for Claude Code");
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log(" Usage: lattice [command] [options]");
|
|
208
|
+
console.log("");
|
|
209
|
+
console.log(" Commands:");
|
|
210
|
+
console.log(" start Start the daemon and open the UI (default)");
|
|
211
|
+
console.log(" stop Stop the running daemon");
|
|
212
|
+
console.log(" restart Stop and restart the daemon");
|
|
213
|
+
console.log(" status Show daemon status and connection info");
|
|
214
|
+
console.log(" update Check for updates and install the latest version");
|
|
215
|
+
console.log(" version Show current and latest version");
|
|
216
|
+
console.log(" logs Tail the daemon log");
|
|
217
|
+
console.log(" open Open the UI in the browser");
|
|
218
|
+
console.log(" config Show configuration paths and settings");
|
|
219
|
+
console.log(" help Show this help message");
|
|
220
|
+
console.log("");
|
|
221
|
+
console.log(" Options:");
|
|
222
|
+
console.log(" --port=N Override the server port");
|
|
223
|
+
console.log("");
|
|
224
|
+
console.log(" Environment:");
|
|
225
|
+
console.log(" LATTICE_HOME Data directory (default: ~/.lattice)");
|
|
226
|
+
console.log(" LATTICE_PORT Server port (default: 7654)");
|
|
227
|
+
console.log("");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function runRestart(): void {
|
|
231
|
+
var pid = readPid();
|
|
232
|
+
if (pid !== null && isDaemonRunning(pid)) {
|
|
233
|
+
console.log("[lattice] Stopping daemon (PID " + pid + ")...");
|
|
234
|
+
try {
|
|
235
|
+
process.kill(pid, "SIGTERM");
|
|
236
|
+
} catch {}
|
|
237
|
+
removePid();
|
|
238
|
+
|
|
239
|
+
var waited = 0;
|
|
240
|
+
while (waited < 5000) {
|
|
241
|
+
try {
|
|
242
|
+
process.kill(pid, 0);
|
|
243
|
+
Bun.sleepSync(200);
|
|
244
|
+
waited += 200;
|
|
245
|
+
} catch {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log("[lattice] Starting daemon...");
|
|
252
|
+
var logPath = join(getLatticeHome(), "daemon.log");
|
|
253
|
+
|
|
254
|
+
var spawnArgs = IS_COMPILED
|
|
255
|
+
? [process.execPath, "daemon"]
|
|
256
|
+
: ["bun", import.meta.path, "daemon"];
|
|
257
|
+
|
|
258
|
+
if (portOverride) {
|
|
259
|
+
spawnArgs.push("--port", String(portOverride));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
var child = Bun.spawn(spawnArgs, {
|
|
263
|
+
detached: true,
|
|
264
|
+
stdio: ["ignore", Bun.file(logPath), Bun.file(logPath)],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
child.unref();
|
|
268
|
+
writePid(child.pid);
|
|
269
|
+
console.log("[lattice] Daemon started (PID " + child.pid + ")");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function runVersion(): Promise<void> {
|
|
273
|
+
var { checkForUpdate } = await import("./update-checker");
|
|
274
|
+
var info = await checkForUpdate(true);
|
|
275
|
+
console.log("[lattice] Current: v" + info.currentVersion);
|
|
276
|
+
if (info.latestVersion) {
|
|
277
|
+
if (info.updateAvailable) {
|
|
278
|
+
console.log("[lattice] Latest: v" + info.latestVersion + " (update available)");
|
|
279
|
+
console.log("[lattice] Run 'lattice update' to install");
|
|
280
|
+
} else {
|
|
281
|
+
console.log("[lattice] Latest: v" + info.latestVersion + " (up to date)");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log("[lattice] Mode: " + info.installMode);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function runLogs(): void {
|
|
288
|
+
var logPath = join(getLatticeHome(), "daemon.log");
|
|
289
|
+
if (!existsSync(logPath)) {
|
|
290
|
+
console.log("[lattice] No log file found at " + logPath);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
console.log("[lattice] Tailing " + logPath + " (Ctrl+C to stop)");
|
|
294
|
+
var proc = Bun.spawn(["tail", "-f", "-n", "50", logPath], {
|
|
295
|
+
stdout: "inherit",
|
|
296
|
+
stderr: "inherit",
|
|
297
|
+
});
|
|
298
|
+
process.on("SIGINT", function () {
|
|
299
|
+
proc.kill();
|
|
300
|
+
process.exit(0);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function runOpen(): void {
|
|
305
|
+
var config = loadConfig();
|
|
306
|
+
var pid = readPid();
|
|
307
|
+
if (pid === null || !isDaemonRunning(pid)) {
|
|
308
|
+
console.log("[lattice] Daemon is not running. Start it with 'lattice start'");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
var url = (config.tls ? "https" : "http") + "://localhost:" + config.port;
|
|
312
|
+
console.log("[lattice] Opening " + url);
|
|
313
|
+
openBrowser(url);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function runConfigInfo(): void {
|
|
317
|
+
var config = loadConfig();
|
|
318
|
+
var home = getLatticeHome();
|
|
319
|
+
console.log("[lattice] Home: " + home);
|
|
320
|
+
console.log("[lattice] Config: " + join(home, "config.json"));
|
|
321
|
+
console.log("[lattice] Port: " + config.port);
|
|
322
|
+
console.log("[lattice] Name: " + config.name);
|
|
323
|
+
console.log("[lattice] TLS: " + (config.tls ? "enabled" : "disabled"));
|
|
324
|
+
console.log("[lattice] Projects: " + config.projects.length);
|
|
325
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
326
|
+
console.log(" " + config.projects[i].slug + " → " + config.projects[i].path);
|
|
327
|
+
}
|
|
328
|
+
if (config.passphraseHash) {
|
|
329
|
+
console.log("[lattice] Passphrase: set");
|
|
330
|
+
}
|
|
331
|
+
if (config.costBudget) {
|
|
332
|
+
console.log("[lattice] Budget: $" + config.costBudget.dailyLimit + "/day (" + config.costBudget.enforcement + ")");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
183
336
|
function runStatus(): void {
|
|
184
337
|
var pid = readPid();
|
|
185
338
|
if (pid === null) {
|
|
@@ -239,6 +239,52 @@ export function getPeerConnection(nodeId: string): WebSocket | undefined {
|
|
|
239
239
|
return conn.ws;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
export function registerInboundPeer(nodeId: string, ws: { send: (data: string) => void; readyState: number }): void {
|
|
243
|
+
var existing = connections.get(nodeId);
|
|
244
|
+
if (existing && !existing.dead && existing.ws.readyState === WebSocket.OPEN) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (existing) {
|
|
249
|
+
existing.dead = true;
|
|
250
|
+
if (existing.retryTimer !== null) {
|
|
251
|
+
clearTimeout(existing.retryTimer);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
circuitBreakers.delete(nodeId);
|
|
256
|
+
|
|
257
|
+
var conn: PeerConnection = {
|
|
258
|
+
nodeId: nodeId,
|
|
259
|
+
ws: ws as WebSocket,
|
|
260
|
+
backoffMs: 1000,
|
|
261
|
+
retryTimer: null,
|
|
262
|
+
dead: false,
|
|
263
|
+
projects: [],
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
connections.set(nodeId, conn);
|
|
267
|
+
|
|
268
|
+
var peers = loadPeers();
|
|
269
|
+
var peer = peers.find(function (p) { return p.id === nodeId; });
|
|
270
|
+
if (peer) {
|
|
271
|
+
var identity = loadOrCreateIdentity();
|
|
272
|
+
var config = loadConfig();
|
|
273
|
+
var projects = config.projects || [];
|
|
274
|
+
conn.projects = [];
|
|
275
|
+
|
|
276
|
+
ws.send(JSON.stringify({
|
|
277
|
+
type: "mesh:hello",
|
|
278
|
+
nodeId: identity.id,
|
|
279
|
+
name: config.name,
|
|
280
|
+
publicKey: identity.publicKey,
|
|
281
|
+
projects: projects.map(function (p: { slug: string; title: string }) {
|
|
282
|
+
return { slug: p.slug, title: p.title };
|
|
283
|
+
}),
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
242
288
|
export function disconnectPeer(nodeId: string): void {
|
|
243
289
|
var existing = connections.get(nodeId);
|
|
244
290
|
if (existing) {
|
|
@@ -40,6 +40,10 @@ export function sendTo(id: string, message: object): void {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export function getClientWebSocket(id: string): ServerWebSocket<{ id: string }> | undefined {
|
|
44
|
+
return clients.get(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
export function getClientCount(): number {
|
|
44
48
|
return clients.size;
|
|
45
49
|
}
|