@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.
@@ -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
- try {
402
- var certs = ensureCerts();
403
- tlsOptions = {
404
- cert: readFileSync(certs.cert),
405
- key: readFileSync(certs.key),
406
- };
407
- log.server("TLS enabled (cert: %s)", certs.cert);
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
- catch (err) {
410
- console.error("[lattice] Failed to load TLS certs, falling back to HTTP:", err);
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)
@@ -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 = "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;
@@ -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 (default)");
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 { execSync: exec } = await import("node:child_process");
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
- console.log("[lattice] TLS Setup");
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
- exec("tailscale version", { stdio: "ignore" });
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. To use trusted Tailscale HTTPS certs:");
329
+ if (hasTailscale && hostname) {
330
+ console.log(" Tailscale detected: " + hostname);
303
331
  console.log("");
304
- try {
305
- var dnsName = exec("tailscale status --json", { encoding: "utf-8" });
306
- var hostname = JSON.parse(dnsName).Self.DNSName.replace(/\.$/, "");
307
- console.log(" 1. Generate cert:");
308
- console.log(" sudo tailscale cert --cert-file " + certPath + " --key-file " + keyPath + " " + hostname);
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(" 2. Enable TLS and restart:");
311
- console.log(" lattice restart --tls");
338
+ console.log(" Generating Tailscale cert (sudo required)...");
312
339
  console.log("");
313
- console.log(" 3. Access at:");
314
- console.log(" https://" + hostname + ":" + loadConfig().port);
315
- }
316
- catch {
317
- console.log(" 1. Run: sudo tailscale cert --cert-file " + certPath + " --key-file " + keyPath + " <your-hostname>.ts.net");
318
- console.log(" 2. Run: lattice restart --tls");
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 not found. Using self-signed certificate.");
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 restart --tls");
403
+ console.log(" To regenerate: rm -rf " + certsDir + " && lattice setup-tls");
331
404
  }
332
405
  else {
333
- console.log(" No cert yet. Run 'lattice restart --tls' to auto-generate one.");
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):");
@@ -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
- console.log("");
21
- p.note("lattice v" + version + " — " + url + "\n" +
22
- projectCount + " project" + (projectCount !== 1 ? "s" : "") +
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", "Running");
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.2.0",
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>",