@cryptiklemur/lattice 1.28.2 → 1.29.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.
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback, useRef } from "react";
1
+ import { useState, useEffect, useCallback, useRef, memo } from "react";
2
2
  import { X, Copy, Check, Loader2 } from "lucide-react";
3
3
  import { useWebSocket } from "../../hooks/useWebSocket";
4
4
  import { useMesh } from "../../hooks/useMesh";
@@ -13,7 +13,7 @@ interface PairingDialogProps {
13
13
  onClose: () => void;
14
14
  }
15
15
 
16
- export function PairingDialog(props: PairingDialogProps) {
16
+ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps) {
17
17
  var ws = useWebSocket();
18
18
  var mesh = useMesh();
19
19
  var [tab, setTab] = useState<Tab>("generate");
@@ -198,17 +198,6 @@ export function PairingDialog(props: PairingDialogProps) {
198
198
  </button>
199
199
  </div>
200
200
 
201
- {mesh.inviteQr && (
202
- <div className="flex justify-center mb-4">
203
- <img
204
- src={mesh.inviteQr}
205
- alt="QR code for invite"
206
- className="w-40 h-40 rounded border border-base-300"
207
- style={{ imageRendering: "pixelated" }}
208
- />
209
- </div>
210
- )}
211
-
212
201
  <button
213
202
  onClick={handleGenerateInvite}
214
203
  className="text-[12px] text-base-content/40 underline cursor-pointer"
@@ -231,7 +220,15 @@ export function PairingDialog(props: PairingDialogProps) {
231
220
  type="text"
232
221
  value={pairCode}
233
222
  onChange={function (e) {
234
- setPairCode(e.target.value);
223
+ var raw = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "");
224
+ if (raw.startsWith("LTCE")) raw = raw.slice(4);
225
+ if (raw.length > 16) raw = raw.slice(0, 16);
226
+ var chunks: string[] = [];
227
+ for (var i = 0; i < raw.length; i += 4) {
228
+ chunks.push(raw.slice(i, i + 4));
229
+ }
230
+ var formatted = chunks.length > 0 ? "LTCE-" + chunks.join("-") : "";
231
+ setPairCode(formatted);
235
232
  if (pairStatus !== "idle") {
236
233
  setPairStatus("idle");
237
234
  setPairError(null);
@@ -242,7 +239,8 @@ export function PairingDialog(props: PairingDialogProps) {
242
239
  handlePair();
243
240
  }
244
241
  }}
245
- placeholder="LTCE-XXXX-XXXX"
242
+ placeholder="LTCE-XXXX-XXXX-XXXX-XXXX"
243
+ maxLength={24}
246
244
  autoFocus
247
245
  disabled={pairStatus === "connecting" || pairStatus === "paired"}
248
246
  className="input input-bordered w-full bg-base-100 text-base-content font-mono text-[14px] tracking-[0.06em] mb-3 focus:border-primary"
@@ -287,4 +285,4 @@ export function PairingDialog(props: PairingDialogProps) {
287
285
  </div>
288
286
  </div>
289
287
  );
290
- }
288
+ });
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useState, useCallback, memo } from "react";
2
2
  import { Plus, CircleDot, Circle } from "lucide-react";
3
3
  import { useWebSocket } from "../../hooks/useWebSocket";
4
4
  import { useMesh } from "../../hooks/useMesh";
@@ -83,6 +83,8 @@ export function MeshStatus() {
83
83
  var { nodes } = useMesh();
84
84
  var [pairingOpen, setPairingOpen] = useState(false);
85
85
 
86
+ var handleClosePairing = useCallback(function () { setPairingOpen(false); }, []);
87
+
86
88
  function handleUnpair(nodeId: string) {
87
89
  ws.send({ type: "mesh:unpair", nodeId });
88
90
  }
@@ -138,7 +140,7 @@ export function MeshStatus() {
138
140
 
139
141
  <PairingDialog
140
142
  isOpen={pairingOpen}
141
- onClose={function () { setPairingOpen(false); }}
143
+ onClose={handleClosePairing}
142
144
  />
143
145
  </div>
144
146
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.28.2",
3
+ "version": "1.29.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>",
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import QRCode from "qrcode";
3
2
 
4
3
  var BASE62_CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
4
+ var CODE_LENGTH = 16;
5
5
 
6
6
  var PAIRING_TOKEN_TTL = 300000;
7
7
  var CLEANUP_INTERVAL = 60000;
@@ -16,7 +16,10 @@ function base62Encode(buf: Buffer): string {
16
16
  result = BASE62_CHARS[Number(n % base)] + result;
17
17
  n = n / base;
18
18
  }
19
- return result || BASE62_CHARS[0];
19
+ while (result.length < CODE_LENGTH) {
20
+ result = BASE62_CHARS[0] + result;
21
+ }
22
+ return result;
20
23
  }
21
24
 
22
25
  function base62Decode(s: string): Buffer {
@@ -36,11 +39,29 @@ function base62Decode(s: string): Buffer {
36
39
  return Buffer.from(hex, "hex");
37
40
  }
38
41
 
42
+ function packPayload(address: string, port: number, token: Buffer): Buffer {
43
+ var parts = address.split(".");
44
+ var buf = Buffer.alloc(4 + 2 + token.length);
45
+ for (var i = 0; i < 4; i++) {
46
+ buf[i] = parseInt(parts[i] || "0", 10);
47
+ }
48
+ buf.writeUInt16BE(port, 4);
49
+ token.copy(buf, 6);
50
+ return buf;
51
+ }
52
+
53
+ function unpackPayload(buf: Buffer): { address: string; port: number; token: string } | null {
54
+ if (buf.length < 8) return null;
55
+ var address = buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3];
56
+ var port = buf.readUInt16BE(4);
57
+ var token = buf.subarray(6).toString("hex");
58
+ return { address, port, token };
59
+ }
60
+
39
61
  function formatCode(raw: string): string {
40
- var upper = raw.toUpperCase();
41
62
  var chunks: string[] = [];
42
- for (var i = 0; i < upper.length; i += 4) {
43
- chunks.push(upper.slice(i, i + 4));
63
+ for (var i = 0; i < raw.length; i += 4) {
64
+ chunks.push(raw.slice(i, i + 4));
44
65
  }
45
66
  return "LTCE-" + chunks.join("-");
46
67
  }
@@ -53,17 +74,15 @@ export async function generateInviteCode(
53
74
  address: string,
54
75
  port: number
55
76
  ): Promise<{ code: string; token: string; qrDataUrl: string }> {
56
- var token = randomBytes(8).toString("hex");
57
- var payload = Buffer.from(address + ":" + port + ":" + token, "utf-8");
77
+ var tokenBuf = randomBytes(4);
78
+ var token = tokenBuf.toString("hex");
79
+ var payload = packPayload(address, port, tokenBuf);
58
80
  var encoded = base62Encode(payload);
59
81
  var code = formatCode(encoded);
60
82
 
61
83
  pendingTokens.set(token, Date.now());
62
84
 
63
- var qrSvg = await QRCode.toString(code, { type: "svg" });
64
- var qrDataUrl = "data:image/svg+xml;base64," + Buffer.from(qrSvg).toString("base64");
65
-
66
- return { code, token, qrDataUrl };
85
+ return { code, token, qrDataUrl: "" };
67
86
  }
68
87
 
69
88
  export function parseInviteCode(
@@ -71,19 +90,8 @@ export function parseInviteCode(
71
90
  ): { address: string; port: number; token: string } | null {
72
91
  try {
73
92
  var stripped = stripCode(code);
74
- var decoded = base62Decode(stripped).toString("utf-8");
75
- var parts = decoded.split(":");
76
- if (parts.length < 3) {
77
- return null;
78
- }
79
- var token = parts[parts.length - 1];
80
- var portStr = parts[parts.length - 2];
81
- var address = parts.slice(0, parts.length - 2).join(":");
82
- var port = parseInt(portStr, 10);
83
- if (isNaN(port)) {
84
- return null;
85
- }
86
- return { address, port, token };
93
+ var decoded = base62Decode(stripped);
94
+ return unpackPayload(decoded);
87
95
  } catch {
88
96
  return null;
89
97
  }