@cryptiklemur/lattice 1.29.2 → 1.30.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.
@@ -22,6 +22,8 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
22
22
  var [pairError, setPairError] = useState<string | null>(null);
23
23
  var [copied, setCopied] = useState(false);
24
24
  var [generating, setGenerating] = useState(false);
25
+ var [addresses, setAddresses] = useState<Array<{ name: string; address: string }>>([]);
26
+ var [selectedAddress, setSelectedAddress] = useState("");
25
27
  var modalRef = useRef<HTMLDivElement>(null);
26
28
  var inputRef = useRef<HTMLInputElement>(null);
27
29
 
@@ -33,14 +35,32 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
33
35
  setPairError(null);
34
36
  setCopied(false);
35
37
  setGenerating(false);
38
+ setAddresses([]);
39
+ setSelectedAddress("");
36
40
  setTab("generate");
37
41
  return;
38
42
  }
43
+
44
+ function handleAddresses(msg: ServerMessage) {
45
+ if ((msg as any).type !== "mesh:addresses_result") return;
46
+ var data = msg as any as { addresses: Array<{ name: string; address: string }> };
47
+ setAddresses(data.addresses);
48
+ if (data.addresses.length > 0) {
49
+ setSelectedAddress(data.addresses[0].address);
50
+ }
51
+ }
52
+
53
+ ws.subscribe("mesh:addresses_result", handleAddresses);
54
+ ws.send({ type: "mesh:addresses" } as any);
55
+
39
56
  function handleKeyDown(e: KeyboardEvent) {
40
57
  if (e.key === "Escape") props.onClose();
41
58
  }
42
59
  document.addEventListener("keydown", handleKeyDown);
43
- return function () { document.removeEventListener("keydown", handleKeyDown); };
60
+ return function () {
61
+ document.removeEventListener("keydown", handleKeyDown);
62
+ ws.unsubscribe("mesh:addresses_result", handleAddresses);
63
+ };
44
64
  }, [props.isOpen]);
45
65
 
46
66
  useEffect(function () {
@@ -77,7 +97,7 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
77
97
  function handleGenerateInvite() {
78
98
  clearInvite();
79
99
  setGenerating(true);
80
- mesh.generateInvite();
100
+ ws.send({ type: "mesh:generate_invite", address: selectedAddress } as any);
81
101
  }
82
102
 
83
103
  function handlePair() {
@@ -172,9 +192,31 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
172
192
  The code encodes this node&apos;s address and a one-time auth token.
173
193
  </div>
174
194
 
195
+ {!mesh.inviteCode && !generating && addresses.length > 1 && (
196
+ <div className="mb-3">
197
+ <label className="text-[11px] font-mono text-base-content/35 uppercase tracking-wider mb-1.5 block">
198
+ Network interface
199
+ </label>
200
+ <select
201
+ value={selectedAddress}
202
+ onChange={function (e) { setSelectedAddress(e.target.value); }}
203
+ className="select select-bordered select-sm w-full bg-base-100 text-base-content text-[13px] font-mono"
204
+ >
205
+ {addresses.map(function (a) {
206
+ return (
207
+ <option key={a.address} value={a.address}>
208
+ {a.address} ({a.name})
209
+ </option>
210
+ );
211
+ })}
212
+ </select>
213
+ </div>
214
+ )}
215
+
175
216
  {!mesh.inviteCode && !generating && (
176
217
  <button
177
218
  onClick={handleGenerateInvite}
219
+ disabled={!selectedAddress && addresses.length > 0}
178
220
  className="btn btn-primary btn-sm"
179
221
  >
180
222
  Generate Invite Code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.29.2",
3
+ "version": "1.30.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>",
@@ -9,18 +9,24 @@ import type { PeerInfo } from "@lattice/shared";
9
9
  import { networkInterfaces } from "node:os";
10
10
 
11
11
  function getLocalAddress(): string {
12
+ var all = getAllAddresses();
13
+ return all.length > 0 ? all[0].address : "localhost";
14
+ }
15
+
16
+ function getAllAddresses(): Array<{ name: string; address: string }> {
12
17
  var interfaces = networkInterfaces();
13
18
  var keys = Object.keys(interfaces);
19
+ var results: Array<{ name: string; address: string }> = [];
14
20
  for (var i = 0; i < keys.length; i++) {
15
21
  var addrs = interfaces[keys[i]];
16
22
  if (!addrs) continue;
17
23
  for (var j = 0; j < addrs.length; j++) {
18
24
  if (!addrs[j].internal && addrs[j].family === "IPv4") {
19
- return addrs[j].address;
25
+ results.push({ name: keys[i], address: addrs[j].address });
20
26
  }
21
27
  }
22
28
  }
23
- return "localhost";
29
+ return results;
24
30
  }
25
31
 
26
32
  export function buildNodesMessage(): NodeInfo[] {
@@ -57,8 +63,9 @@ export function buildNodesMessage(): NodeInfo[] {
57
63
 
58
64
  registerHandler("mesh", function (clientId: string, message: ClientMessage) {
59
65
  if (message.type === "mesh:generate_invite") {
66
+ var genMsg = message as any as { type: "mesh:generate_invite"; address?: string };
60
67
  var config = loadConfig();
61
- var address = getLocalAddress();
68
+ var address = genMsg.address || getLocalAddress();
62
69
  generateInviteCode(address, config.port).then(function (result) {
63
70
  sendTo(clientId, {
64
71
  type: "mesh:invite_code",
@@ -71,6 +78,12 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
71
78
  return;
72
79
  }
73
80
 
81
+ if ((message as any).type === "mesh:addresses") {
82
+ var addresses = getAllAddresses();
83
+ sendTo(clientId, { type: "mesh:addresses_result" as any, addresses: addresses });
84
+ return;
85
+ }
86
+
74
87
  if (message.type === "mesh:pair") {
75
88
  var pairMsg = message as MeshPairMessage;
76
89
  var parsed = parseInviteCode(pairMsg.code);