@cryptiklemur/lattice 5.2.0 → 5.3.1
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/server/daemon.js +21 -20
- package/dist/server/index.js +139 -31
- package/dist/server/tui.js +9 -5
- package/package.json +1 -1
package/dist/server/daemon.js
CHANGED
|
@@ -14,7 +14,6 @@ import { startDiscovery } from "./mesh/discovery.js";
|
|
|
14
14
|
import { startMeshConnections, onPeerConnected, onPeerDisconnected, onPeerMessage, getAllRemoteProjects } from "./mesh/connector.js";
|
|
15
15
|
import { handleProxyRequest, handleProxyResponse } from "./mesh/proxy.js";
|
|
16
16
|
import { verifyPassphrase, generateSessionToken, addSession, isValidSession } from "./auth/passphrase.js";
|
|
17
|
-
import { ensureCerts } from "./tls.js";
|
|
18
17
|
import { log } from "./logger.js";
|
|
19
18
|
import { detectIdeProjectName } from "./handlers/settings.js";
|
|
20
19
|
import "./handlers/session.js";
|
|
@@ -398,28 +397,30 @@ export async function startDaemon(portOverride, tlsOverride) {
|
|
|
398
397
|
});
|
|
399
398
|
var tlsOptions;
|
|
400
399
|
if (config.tls) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
400
|
+
var certsDir = join(getLatticeHome(), "certs");
|
|
401
|
+
var certPath = join(certsDir, "cert.pem");
|
|
402
|
+
var keyPath = join(certsDir, "key.pem");
|
|
403
|
+
if (existsSync(certPath) && existsSync(keyPath)) {
|
|
404
|
+
try {
|
|
405
|
+
tlsOptions = {
|
|
406
|
+
cert: readFileSync(certPath),
|
|
407
|
+
key: readFileSync(keyPath),
|
|
408
|
+
};
|
|
409
|
+
log.server("TLS enabled (cert: %s)", certPath);
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
if (err?.code === "EACCES") {
|
|
413
|
+
console.error("[lattice] Permission denied reading TLS certs. Run 'lattice setup-tls' to fix permissions.");
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.error("[lattice] Failed to load TLS certs, falling back to HTTP:", err);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
408
419
|
}
|
|
409
|
-
|
|
410
|
-
console.error("[lattice]
|
|
420
|
+
else {
|
|
421
|
+
console.error("[lattice] TLS enabled but no certs found. Run 'lattice setup-tls' to generate them.");
|
|
411
422
|
}
|
|
412
423
|
}
|
|
413
|
-
if (tlsOptions) {
|
|
414
|
-
var certsDir = join(getLatticeHome(), "certs");
|
|
415
|
-
log.server("TLS cert: %s", join(certsDir, "cert.pem"));
|
|
416
|
-
log.server("To trust the self-signed cert:");
|
|
417
|
-
log.server(" Linux: sudo cp %s /usr/local/share/ca-certificates/lattice.crt && sudo update-ca-certificates", join(certsDir, "cert.pem"));
|
|
418
|
-
log.server(" macOS: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain %s", join(certsDir, "cert.pem"));
|
|
419
|
-
log.server(" Or use Tailscale HTTPS (no trust needed):");
|
|
420
|
-
log.server(" tailscale cert $(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\\.$//')");
|
|
421
|
-
log.server(" Then copy: cp <hostname>.crt %s && cp <hostname>.key %s", join(certsDir, "cert.pem"), join(certsDir, "key.pem"));
|
|
422
|
-
}
|
|
423
424
|
var protocol = tlsOptions ? "https" : "http";
|
|
424
425
|
var httpServer = tlsOptions
|
|
425
426
|
? createHttpsServer(tlsOptions, app)
|
package/dist/server/index.js
CHANGED
|
@@ -4,7 +4,7 @@ delete process.env.CLAUDE_CODE_ENTRYPOINT;
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, openSync } from "node:fs";
|
|
5
5
|
import { join, dirname } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
7
|
+
import { spawn, execSync } from "node:child_process";
|
|
8
8
|
import { getLatticeHome, loadConfig } from "./config.js";
|
|
9
9
|
var __filename_local = fileURLToPath(import.meta.url);
|
|
10
10
|
var __dirname_local = dirname(__filename_local);
|
|
@@ -18,7 +18,7 @@ function getCurrentVersion() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
var args = process.argv.slice(2);
|
|
21
|
-
var command = "
|
|
21
|
+
var command = "help";
|
|
22
22
|
var portOverride = null;
|
|
23
23
|
var tlsOverride = null;
|
|
24
24
|
for (var i = 0; i < args.length; i++) {
|
|
@@ -92,11 +92,11 @@ function spawnDaemon(port, tls) {
|
|
|
92
92
|
var pkgRoot = join(__dirname_local, "..");
|
|
93
93
|
var localTsx = join(pkgRoot, "node_modules", ".bin", "tsx");
|
|
94
94
|
spawnCmd = existsSync(localTsx) ? localTsx : "tsx";
|
|
95
|
-
spawnArgs = ["--tsconfig", join(pkgRoot, "tsconfig.json"), __filename_local, "
|
|
95
|
+
spawnArgs = ["--tsconfig", join(pkgRoot, "tsconfig.json"), __filename_local, "run", "--port", String(port)];
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
98
|
spawnCmd = process.execPath;
|
|
99
|
-
spawnArgs = [__filename_local, "
|
|
99
|
+
spawnArgs = [__filename_local, "run", "--port", String(port)];
|
|
100
100
|
}
|
|
101
101
|
if (tls === true)
|
|
102
102
|
spawnArgs.push("--tls");
|
|
@@ -111,6 +111,7 @@ function spawnDaemon(port, tls) {
|
|
|
111
111
|
return child.pid;
|
|
112
112
|
}
|
|
113
113
|
switch (command) {
|
|
114
|
+
case "run":
|
|
114
115
|
case "daemon":
|
|
115
116
|
await runDaemon();
|
|
116
117
|
break;
|
|
@@ -208,9 +209,18 @@ async function runDaemon() {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
catch { }
|
|
212
|
+
var tailscaleUrl;
|
|
213
|
+
try {
|
|
214
|
+
var tsResult = execSync("tailscale status --json", { encoding: "utf-8" });
|
|
215
|
+
var tsHostname = JSON.parse(tsResult).Self.DNSName.replace(/\.$/, "");
|
|
216
|
+
if (tsHostname) {
|
|
217
|
+
tailscaleUrl = protocol + "://" + tsHostname + ":" + config.port;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch { }
|
|
211
221
|
printBanner();
|
|
212
222
|
await printQrCode(url);
|
|
213
|
-
printStatus(config, version, projectCount, sessionCount);
|
|
223
|
+
printStatus(config, version, projectCount, sessionCount, tailscaleUrl);
|
|
214
224
|
}
|
|
215
225
|
async function runStart() {
|
|
216
226
|
var pid = readPid();
|
|
@@ -263,7 +273,8 @@ function runHelp() {
|
|
|
263
273
|
console.log(" Usage: lattice [command] [options]");
|
|
264
274
|
console.log("");
|
|
265
275
|
console.log(" Commands:");
|
|
266
|
-
console.log(" start Start the daemon and open the UI
|
|
276
|
+
console.log(" start Start the daemon and open the UI");
|
|
277
|
+
console.log(" run Run the daemon in the foreground");
|
|
267
278
|
console.log(" stop Stop the running daemon");
|
|
268
279
|
console.log(" restart Stop and restart the daemon");
|
|
269
280
|
console.log(" status Show daemon status and connection info");
|
|
@@ -273,7 +284,7 @@ function runHelp() {
|
|
|
273
284
|
console.log(" open Open the UI in the browser");
|
|
274
285
|
console.log(" config Show configuration paths and settings");
|
|
275
286
|
console.log(" setup-tls Set up HTTPS (Tailscale or self-signed)");
|
|
276
|
-
console.log(" help Show this help message");
|
|
287
|
+
console.log(" help Show this help message (default)");
|
|
277
288
|
console.log("");
|
|
278
289
|
console.log(" Options:");
|
|
279
290
|
console.log(" --port=N Override the server port");
|
|
@@ -286,51 +297,148 @@ function runHelp() {
|
|
|
286
297
|
console.log("");
|
|
287
298
|
}
|
|
288
299
|
async function runSetupTls() {
|
|
289
|
-
var {
|
|
300
|
+
var { spawnSync } = await import("node:child_process");
|
|
301
|
+
var { createInterface } = await import("node:readline");
|
|
302
|
+
var { mkdirSync, chmodSync, chownSync, statSync } = await import("node:fs");
|
|
290
303
|
var certsDir = join(getLatticeHome(), "certs");
|
|
291
304
|
var certPath = join(certsDir, "cert.pem");
|
|
292
305
|
var keyPath = join(certsDir, "key.pem");
|
|
293
|
-
|
|
306
|
+
function prompt(question) {
|
|
307
|
+
var rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
308
|
+
return new Promise(function (resolve) {
|
|
309
|
+
rl.question(question, function (answer) {
|
|
310
|
+
rl.close();
|
|
311
|
+
resolve(answer.trim().toLowerCase());
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
console.log("");
|
|
316
|
+
console.log(" lattice — TLS Setup");
|
|
294
317
|
console.log("");
|
|
295
318
|
var hasTailscale = false;
|
|
319
|
+
var hostname = "";
|
|
296
320
|
try {
|
|
297
|
-
|
|
321
|
+
spawnSync("tailscale", ["version"], { stdio: "ignore" });
|
|
298
322
|
hasTailscale = true;
|
|
323
|
+
var statusResult = spawnSync("tailscale", ["status", "--json"], { encoding: "utf-8" });
|
|
324
|
+
if (statusResult.status === 0) {
|
|
325
|
+
hostname = JSON.parse(statusResult.stdout).Self.DNSName.replace(/\.$/, "");
|
|
326
|
+
}
|
|
299
327
|
}
|
|
300
328
|
catch { }
|
|
301
|
-
if (hasTailscale) {
|
|
302
|
-
console.log(" Tailscale detected
|
|
329
|
+
if (hasTailscale && hostname) {
|
|
330
|
+
console.log(" Tailscale detected: " + hostname);
|
|
303
331
|
console.log("");
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
332
|
+
var answer = await prompt(" Set up Tailscale HTTPS certs automatically? [Y/n] ");
|
|
333
|
+
if (answer !== "n" && answer !== "no") {
|
|
334
|
+
if (!existsSync(certsDir)) {
|
|
335
|
+
mkdirSync(certsDir, { recursive: true });
|
|
336
|
+
}
|
|
309
337
|
console.log("");
|
|
310
|
-
console.log("
|
|
311
|
-
console.log(" lattice restart --tls");
|
|
338
|
+
console.log(" Generating Tailscale cert (sudo required)...");
|
|
312
339
|
console.log("");
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
340
|
+
var certResult = spawnSync("sudo", ["tailscale", "cert", "--cert-file", certPath, "--key-file", keyPath, hostname], { stdio: "inherit" });
|
|
341
|
+
if (certResult.status !== 0) {
|
|
342
|
+
console.error("");
|
|
343
|
+
console.error(" Failed to generate Tailscale certs.");
|
|
344
|
+
console.error(" You can run manually:");
|
|
345
|
+
console.error(" sudo tailscale cert --cert-file " + certPath + " --key-file " + keyPath + " " + hostname);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
var uid = process.getuid ? process.getuid() : 0;
|
|
349
|
+
var gid = process.getgid ? process.getgid() : 0;
|
|
350
|
+
try {
|
|
351
|
+
chownSync(certPath, uid, gid);
|
|
352
|
+
chownSync(keyPath, uid, gid);
|
|
353
|
+
chmodSync(certPath, 0o644);
|
|
354
|
+
chmodSync(keyPath, 0o600);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
console.log(" Fixing permissions with sudo...");
|
|
358
|
+
spawnSync("sudo", ["chown", uid + ":" + gid, certPath, keyPath], { stdio: "inherit" });
|
|
359
|
+
spawnSync("sudo", ["chmod", "644", certPath], { stdio: "inherit" });
|
|
360
|
+
spawnSync("sudo", ["chmod", "600", keyPath], { stdio: "inherit" });
|
|
361
|
+
}
|
|
362
|
+
console.log("");
|
|
363
|
+
console.log(" Certs installed and permissions fixed.");
|
|
364
|
+
var config = loadConfig();
|
|
365
|
+
if (!config.tls) {
|
|
366
|
+
var { saveConfig: saveCfg } = await import("./config.js");
|
|
367
|
+
config.tls = true;
|
|
368
|
+
saveCfg(config);
|
|
369
|
+
console.log(" TLS enabled in config.");
|
|
370
|
+
}
|
|
371
|
+
var pid = readPid();
|
|
372
|
+
if (pid !== null && isDaemonRunning(pid)) {
|
|
373
|
+
var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
|
|
374
|
+
if (restartAnswer !== "n" && restartAnswer !== "no") {
|
|
375
|
+
console.log(" Restarting daemon...");
|
|
376
|
+
try {
|
|
377
|
+
process.kill(pid, "SIGTERM");
|
|
378
|
+
}
|
|
379
|
+
catch { }
|
|
380
|
+
removePid();
|
|
381
|
+
await new Promise(function (r) { setTimeout(r, 1500); });
|
|
382
|
+
var port = portOverride ?? config.port;
|
|
383
|
+
var childPid = spawnDaemon(port, true);
|
|
384
|
+
writePid(childPid);
|
|
385
|
+
console.log(" Daemon restarted (PID " + childPid + ")");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
console.log("");
|
|
389
|
+
console.log(" Access at: https://" + hostname + ":" + loadConfig().port);
|
|
390
|
+
console.log("");
|
|
391
|
+
return;
|
|
319
392
|
}
|
|
320
393
|
}
|
|
321
|
-
else {
|
|
322
|
-
console.log(" Tailscale
|
|
394
|
+
else if (hasTailscale) {
|
|
395
|
+
console.log(" Tailscale detected but could not determine hostname.");
|
|
396
|
+
console.log(" Run 'tailscale status' to verify your connection.");
|
|
323
397
|
console.log("");
|
|
324
398
|
}
|
|
325
|
-
console.log("");
|
|
326
399
|
console.log(" ── Self-signed certificate ──");
|
|
327
400
|
console.log("");
|
|
328
|
-
if (existsSync(certPath)) {
|
|
401
|
+
if (existsSync(certPath) && existsSync(keyPath)) {
|
|
329
402
|
console.log(" Cert exists: " + certPath);
|
|
330
|
-
console.log(" To regenerate: rm -rf " + certsDir + " && lattice
|
|
403
|
+
console.log(" To regenerate: rm -rf " + certsDir + " && lattice setup-tls");
|
|
331
404
|
}
|
|
332
405
|
else {
|
|
333
|
-
|
|
406
|
+
var selfSignAnswer = await prompt(" Generate a self-signed certificate? [Y/n] ");
|
|
407
|
+
if (selfSignAnswer !== "n" && selfSignAnswer !== "no") {
|
|
408
|
+
var { ensureCerts } = await import("./tls.js");
|
|
409
|
+
try {
|
|
410
|
+
var certs = ensureCerts();
|
|
411
|
+
console.log(" Certificate generated: " + certs.cert);
|
|
412
|
+
var config = loadConfig();
|
|
413
|
+
if (!config.tls) {
|
|
414
|
+
var { saveConfig: saveCfg } = await import("./config.js");
|
|
415
|
+
config.tls = true;
|
|
416
|
+
saveCfg(config);
|
|
417
|
+
console.log(" TLS enabled in config.");
|
|
418
|
+
}
|
|
419
|
+
var pid = readPid();
|
|
420
|
+
if (pid !== null && isDaemonRunning(pid)) {
|
|
421
|
+
var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
|
|
422
|
+
if (restartAnswer !== "n" && restartAnswer !== "no") {
|
|
423
|
+
console.log(" Restarting daemon...");
|
|
424
|
+
try {
|
|
425
|
+
process.kill(pid, "SIGTERM");
|
|
426
|
+
}
|
|
427
|
+
catch { }
|
|
428
|
+
removePid();
|
|
429
|
+
await new Promise(function (r) { setTimeout(r, 1500); });
|
|
430
|
+
var port = portOverride ?? config.port;
|
|
431
|
+
var childPid = spawnDaemon(port, true);
|
|
432
|
+
writePid(childPid);
|
|
433
|
+
console.log(" Daemon restarted (PID " + childPid + ")");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
console.error(" Failed to generate certificate:", err);
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
334
442
|
}
|
|
335
443
|
console.log("");
|
|
336
444
|
console.log(" To trust the self-signed cert (removes browser warnings):");
|
package/dist/server/tui.js
CHANGED
|
@@ -14,14 +14,18 @@ var BANNER = `
|
|
|
14
14
|
export function printBanner() {
|
|
15
15
|
console.log(BANNER);
|
|
16
16
|
}
|
|
17
|
-
export function printStatus(config, version, projectCount, sessionCount) {
|
|
17
|
+
export function printStatus(config, version, projectCount, sessionCount, tailscaleUrl) {
|
|
18
18
|
var protocol = config.tls ? "https" : "http";
|
|
19
19
|
var url = protocol + "://localhost:" + config.port;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
var lines = "lattice v" + version + " — " + url + "\n";
|
|
21
|
+
if (tailscaleUrl) {
|
|
22
|
+
lines += tailscaleUrl + "\n";
|
|
23
|
+
}
|
|
24
|
+
lines += projectCount + " project" + (projectCount !== 1 ? "s" : "") +
|
|
23
25
|
" · " + sessionCount + " session" + (sessionCount !== 1 ? "s" : "") + "\n" +
|
|
24
|
-
"Press Ctrl+C to stop"
|
|
26
|
+
"Press Ctrl+C to stop";
|
|
27
|
+
console.log("");
|
|
28
|
+
p.note(lines, "Running");
|
|
25
29
|
console.log("");
|
|
26
30
|
}
|
|
27
31
|
export async function printQrCode(url) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.1",
|
|
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>",
|