@cryptiklemur/lattice 5.1.1 → 5.3.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.
@@ -404,12 +404,27 @@ export async function startDaemon(portOverride, tlsOverride) {
404
404
  cert: readFileSync(certs.cert),
405
405
  key: readFileSync(certs.key),
406
406
  };
407
- log.server("TLS enabled");
407
+ log.server("TLS enabled (cert: %s)", certs.cert);
408
408
  }
409
409
  catch (err) {
410
- console.error("[lattice] Failed to load TLS certs, falling back to HTTP:", err);
410
+ if (err?.code === "EACCES") {
411
+ console.error("[lattice] Permission denied reading TLS certs. Run 'lattice setup-tls' to fix permissions.");
412
+ }
413
+ else {
414
+ console.error("[lattice] Failed to load TLS certs, falling back to HTTP:", err);
415
+ }
411
416
  }
412
417
  }
418
+ if (tlsOptions) {
419
+ var certsDir = join(getLatticeHome(), "certs");
420
+ log.server("TLS cert: %s", join(certsDir, "cert.pem"));
421
+ log.server("To trust the self-signed cert:");
422
+ log.server(" Linux: sudo cp %s /usr/local/share/ca-certificates/lattice.crt && sudo update-ca-certificates", join(certsDir, "cert.pem"));
423
+ log.server(" macOS: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain %s", join(certsDir, "cert.pem"));
424
+ log.server(" Or use Tailscale HTTPS (no trust needed):");
425
+ log.server(" tailscale cert $(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\\.$//')");
426
+ log.server(" Then copy: cp <hostname>.crt %s && cp <hostname>.key %s", join(certsDir, "cert.pem"), join(certsDir, "key.pem"));
427
+ }
413
428
  var protocol = tlsOptions ? "https" : "http";
414
429
  var httpServer = tlsOptions
415
430
  ? createHttpsServer(tlsOptions, app)
@@ -18,7 +18,7 @@ function getCurrentVersion() {
18
18
  }
19
19
  }
20
20
  var args = process.argv.slice(2);
21
- var command = "start";
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, "daemon", "--port", String(port)];
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, "daemon", "--port", String(port)];
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;
@@ -141,6 +142,9 @@ switch (command) {
141
142
  case "config":
142
143
  runConfigInfo();
143
144
  break;
145
+ case "setup-tls":
146
+ await runSetupTls();
147
+ break;
144
148
  case "help":
145
149
  case "--help":
146
150
  case "-h":
@@ -260,7 +264,8 @@ function runHelp() {
260
264
  console.log(" Usage: lattice [command] [options]");
261
265
  console.log("");
262
266
  console.log(" Commands:");
263
- console.log(" start Start the daemon and open the UI (default)");
267
+ console.log(" start Start the daemon and open the UI");
268
+ console.log(" run Run the daemon in the foreground");
264
269
  console.log(" stop Stop the running daemon");
265
270
  console.log(" restart Stop and restart the daemon");
266
271
  console.log(" status Show daemon status and connection info");
@@ -269,7 +274,8 @@ function runHelp() {
269
274
  console.log(" logs Tail the daemon log");
270
275
  console.log(" open Open the UI in the browser");
271
276
  console.log(" config Show configuration paths and settings");
272
- console.log(" help Show this help message");
277
+ console.log(" setup-tls Set up HTTPS (Tailscale or self-signed)");
278
+ console.log(" help Show this help message (default)");
273
279
  console.log("");
274
280
  console.log(" Options:");
275
281
  console.log(" --port=N Override the server port");
@@ -281,6 +287,165 @@ function runHelp() {
281
287
  console.log(" DEBUG Enable debug logging (e.g. DEBUG=lattice:*)");
282
288
  console.log("");
283
289
  }
290
+ async function runSetupTls() {
291
+ var { spawnSync } = await import("node:child_process");
292
+ var { createInterface } = await import("node:readline");
293
+ var { mkdirSync, chmodSync, chownSync, statSync } = await import("node:fs");
294
+ var certsDir = join(getLatticeHome(), "certs");
295
+ var certPath = join(certsDir, "cert.pem");
296
+ var keyPath = join(certsDir, "key.pem");
297
+ function prompt(question) {
298
+ var rl = createInterface({ input: process.stdin, output: process.stdout });
299
+ return new Promise(function (resolve) {
300
+ rl.question(question, function (answer) {
301
+ rl.close();
302
+ resolve(answer.trim().toLowerCase());
303
+ });
304
+ });
305
+ }
306
+ console.log("");
307
+ console.log(" lattice — TLS Setup");
308
+ console.log("");
309
+ var hasTailscale = false;
310
+ var hostname = "";
311
+ try {
312
+ spawnSync("tailscale", ["version"], { stdio: "ignore" });
313
+ hasTailscale = true;
314
+ var statusResult = spawnSync("tailscale", ["status", "--json"], { encoding: "utf-8" });
315
+ if (statusResult.status === 0) {
316
+ hostname = JSON.parse(statusResult.stdout).Self.DNSName.replace(/\.$/, "");
317
+ }
318
+ }
319
+ catch { }
320
+ if (hasTailscale && hostname) {
321
+ console.log(" Tailscale detected: " + hostname);
322
+ console.log("");
323
+ var answer = await prompt(" Set up Tailscale HTTPS certs automatically? [Y/n] ");
324
+ if (answer !== "n" && answer !== "no") {
325
+ if (!existsSync(certsDir)) {
326
+ mkdirSync(certsDir, { recursive: true });
327
+ }
328
+ console.log("");
329
+ console.log(" Generating Tailscale cert (sudo required)...");
330
+ console.log("");
331
+ var certResult = spawnSync("sudo", ["tailscale", "cert", "--cert-file", certPath, "--key-file", keyPath, hostname], { stdio: "inherit" });
332
+ if (certResult.status !== 0) {
333
+ console.error("");
334
+ console.error(" Failed to generate Tailscale certs.");
335
+ console.error(" You can run manually:");
336
+ console.error(" sudo tailscale cert --cert-file " + certPath + " --key-file " + keyPath + " " + hostname);
337
+ process.exit(1);
338
+ }
339
+ var uid = process.getuid ? process.getuid() : 0;
340
+ var gid = process.getgid ? process.getgid() : 0;
341
+ try {
342
+ chownSync(certPath, uid, gid);
343
+ chownSync(keyPath, uid, gid);
344
+ chmodSync(certPath, 0o644);
345
+ chmodSync(keyPath, 0o600);
346
+ }
347
+ catch {
348
+ console.log(" Fixing permissions with sudo...");
349
+ spawnSync("sudo", ["chown", uid + ":" + gid, certPath, keyPath], { stdio: "inherit" });
350
+ spawnSync("sudo", ["chmod", "644", certPath], { stdio: "inherit" });
351
+ spawnSync("sudo", ["chmod", "600", keyPath], { stdio: "inherit" });
352
+ }
353
+ console.log("");
354
+ console.log(" Certs installed and permissions fixed.");
355
+ var config = loadConfig();
356
+ if (!config.tls) {
357
+ var { saveConfig: saveCfg } = await import("./config.js");
358
+ config.tls = true;
359
+ saveCfg(config);
360
+ console.log(" TLS enabled in config.");
361
+ }
362
+ var pid = readPid();
363
+ if (pid !== null && isDaemonRunning(pid)) {
364
+ var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
365
+ if (restartAnswer !== "n" && restartAnswer !== "no") {
366
+ console.log(" Restarting daemon...");
367
+ try {
368
+ process.kill(pid, "SIGTERM");
369
+ }
370
+ catch { }
371
+ removePid();
372
+ await new Promise(function (r) { setTimeout(r, 1500); });
373
+ var port = portOverride ?? config.port;
374
+ var childPid = spawnDaemon(port, true);
375
+ writePid(childPid);
376
+ console.log(" Daemon restarted (PID " + childPid + ")");
377
+ }
378
+ }
379
+ console.log("");
380
+ console.log(" Access at: https://" + hostname + ":" + loadConfig().port);
381
+ console.log("");
382
+ return;
383
+ }
384
+ }
385
+ else if (hasTailscale) {
386
+ console.log(" Tailscale detected but could not determine hostname.");
387
+ console.log(" Run 'tailscale status' to verify your connection.");
388
+ console.log("");
389
+ }
390
+ console.log(" ── Self-signed certificate ──");
391
+ console.log("");
392
+ if (existsSync(certPath) && existsSync(keyPath)) {
393
+ console.log(" Cert exists: " + certPath);
394
+ console.log(" To regenerate: rm -rf " + certsDir + " && lattice setup-tls");
395
+ }
396
+ else {
397
+ var selfSignAnswer = await prompt(" Generate a self-signed certificate? [Y/n] ");
398
+ if (selfSignAnswer !== "n" && selfSignAnswer !== "no") {
399
+ var { ensureCerts } = await import("./tls.js");
400
+ try {
401
+ var certs = ensureCerts();
402
+ console.log(" Certificate generated: " + certs.cert);
403
+ var config = loadConfig();
404
+ if (!config.tls) {
405
+ var { saveConfig: saveCfg } = await import("./config.js");
406
+ config.tls = true;
407
+ saveCfg(config);
408
+ console.log(" TLS enabled in config.");
409
+ }
410
+ var pid = readPid();
411
+ if (pid !== null && isDaemonRunning(pid)) {
412
+ var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
413
+ if (restartAnswer !== "n" && restartAnswer !== "no") {
414
+ console.log(" Restarting daemon...");
415
+ try {
416
+ process.kill(pid, "SIGTERM");
417
+ }
418
+ catch { }
419
+ removePid();
420
+ await new Promise(function (r) { setTimeout(r, 1500); });
421
+ var port = portOverride ?? config.port;
422
+ var childPid = spawnDaemon(port, true);
423
+ writePid(childPid);
424
+ console.log(" Daemon restarted (PID " + childPid + ")");
425
+ }
426
+ }
427
+ }
428
+ catch (err) {
429
+ console.error(" Failed to generate certificate:", err);
430
+ process.exit(1);
431
+ }
432
+ }
433
+ }
434
+ console.log("");
435
+ console.log(" To trust the self-signed cert (removes browser warnings):");
436
+ console.log("");
437
+ console.log(" Linux:");
438
+ console.log(" sudo cp " + certPath + " /usr/local/share/ca-certificates/lattice.crt");
439
+ console.log(" sudo update-ca-certificates");
440
+ console.log("");
441
+ console.log(" macOS:");
442
+ console.log(" sudo security add-trusted-cert -d -r trustRoot \\");
443
+ console.log(" -k /Library/Keychains/System.keychain " + certPath);
444
+ console.log("");
445
+ console.log(" Windows (PowerShell as admin):");
446
+ console.log(" Import-Certificate -FilePath " + certPath + " -CertStoreLocation Cert:\\LocalMachine\\Root");
447
+ console.log("");
448
+ }
284
449
  async function runRestart() {
285
450
  var pid = readPid();
286
451
  if (pid !== null && isDaemonRunning(pid)) {
@@ -1,6 +1,7 @@
1
- import { existsSync, mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
+ import { networkInterfaces } from "node:os";
4
5
  import { getLatticeHome } from "./config.js";
5
6
  export function getCertsDir() {
6
7
  var certsDir = join(getLatticeHome(), "certs");
@@ -33,6 +34,20 @@ export function ensureCerts() {
33
34
  return { cert: certPath, key: keyPath };
34
35
  }
35
36
  console.log("[lattice] Generating self-signed TLS certificate...");
37
+ var sans = ["DNS:lattice", "DNS:localhost", "IP:127.0.0.1", "IP:::1"];
38
+ var ifaces = networkInterfaces();
39
+ for (var name in ifaces) {
40
+ var addrs = ifaces[name];
41
+ if (!addrs)
42
+ continue;
43
+ for (var i = 0; i < addrs.length; i++) {
44
+ if (!addrs[i].internal) {
45
+ sans.push("IP:" + addrs[i].address);
46
+ }
47
+ }
48
+ }
49
+ var extFile = join(certsDir, "openssl-san.cnf");
50
+ writeFileSync(extFile, "[req]\ndistinguished_name=dn\nx509_extensions=v3\nprompt=no\n[dn]\nCN=lattice\n[v3]\nsubjectAltName=" + sans.join(",") + "\n");
36
51
  var result = spawnSync("openssl", [
37
52
  "req", "-x509",
38
53
  "-newkey", "rsa:2048",
@@ -40,7 +55,7 @@ export function ensureCerts() {
40
55
  "-out", certPath,
41
56
  "-days", "365",
42
57
  "-nodes",
43
- "-subj", "/CN=lattice",
58
+ "-config", extFile,
44
59
  ], { encoding: "utf-8" });
45
60
  if (result.status !== 0) {
46
61
  throw new Error("[lattice] Failed to generate TLS certificates: " + (result.stderr || result.error?.message || "unknown error"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "5.1.1",
3
+ "version": "5.3.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>",