@cryptiklemur/lattice 1.35.0 → 1.36.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.
|
@@ -111,7 +111,7 @@ function ProjectButton(props: ProjectButtonProps) {
|
|
|
111
111
|
|
|
112
112
|
{hovered && (
|
|
113
113
|
<div
|
|
114
|
-
className="pointer-events-none z-[9000] bg-base-300 border border-base-content/20 rounded px-2 py-1
|
|
114
|
+
className="pointer-events-none z-[9000] bg-base-300 border border-base-content/20 rounded-lg px-2.5 py-1.5 shadow-xl"
|
|
115
115
|
style={{
|
|
116
116
|
position: "fixed",
|
|
117
117
|
left: "calc(64px + 8px)",
|
|
@@ -119,7 +119,18 @@ function ProjectButton(props: ProjectButtonProps) {
|
|
|
119
119
|
transform: "translateY(-50%)",
|
|
120
120
|
}}
|
|
121
121
|
>
|
|
122
|
-
{props.group.title}
|
|
122
|
+
<div className="text-[12px] font-bold text-base-content whitespace-nowrap">{props.group.title}</div>
|
|
123
|
+
{props.group.nodes.map(function (n) {
|
|
124
|
+
return (
|
|
125
|
+
<div key={n.nodeId} className="flex items-center gap-1.5 mt-0.5">
|
|
126
|
+
<div className={"w-[6px] h-[6px] rounded-full flex-shrink-0 " + (n.online ? "bg-success" : "bg-error")} />
|
|
127
|
+
<span className="text-[10px] text-base-content/50 whitespace-nowrap">
|
|
128
|
+
{n.nodeName}
|
|
129
|
+
{n.path ? " \u00B7 " + n.path : ""}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
123
134
|
</div>
|
|
124
135
|
)}
|
|
125
136
|
</div>
|
|
@@ -331,6 +331,11 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
|
|
|
331
331
|
>
|
|
332
332
|
<span className="text-[13px] font-mono font-bold text-base-content/90">
|
|
333
333
|
{activeProject?.title ?? "No Project"}
|
|
334
|
+
{activeProject?.isRemote && (
|
|
335
|
+
<span className="ml-1.5 text-[10px] font-normal text-base-content/30">
|
|
336
|
+
on {activeProject.nodeName}
|
|
337
|
+
</span>
|
|
338
|
+
)}
|
|
334
339
|
</span>
|
|
335
340
|
<ChevronDown size={14} className="text-base-content/30" />
|
|
336
341
|
</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.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>",
|
package/server/src/daemon.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { addClient, removeClient, routeMessage } from "./ws/server";
|
|
|
8
8
|
import { broadcast, sendTo } from "./ws/broadcast";
|
|
9
9
|
import { buildNodesMessage } from "./handlers/mesh";
|
|
10
10
|
import { startDiscovery } from "./mesh/discovery";
|
|
11
|
-
import { startMeshConnections, onPeerConnected, onPeerDisconnected, onPeerMessage } from "./mesh/connector";
|
|
11
|
+
import { startMeshConnections, onPeerConnected, onPeerDisconnected, onPeerMessage, getAllRemoteProjects } from "./mesh/connector";
|
|
12
12
|
import { handleProxyRequest, handleProxyResponse } from "./mesh/proxy";
|
|
13
13
|
import { verifyPassphrase, generateSessionToken, addSession, isValidSession } from "./auth/passphrase";
|
|
14
14
|
import { ensureCerts } from "./tls";
|
|
@@ -395,11 +395,13 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
|
|
|
395
395
|
var currentConfig = loadConfig();
|
|
396
396
|
var currentIdentity = loadOrCreateIdentity();
|
|
397
397
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
398
|
+
var localProjects = currentConfig.projects.map(function (p: typeof currentConfig.projects[number]) {
|
|
399
|
+
return { slug: p.slug, path: p.path, title: p.title, nodeId: currentIdentity.id, nodeName: currentConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path) };
|
|
400
|
+
});
|
|
401
|
+
var remoteProjects = getAllRemoteProjects(currentIdentity.id);
|
|
398
402
|
broadcast({
|
|
399
403
|
type: "projects:list",
|
|
400
|
-
projects:
|
|
401
|
-
return { slug: p.slug, path: p.path, title: p.title, nodeId: currentIdentity.id, nodeName: currentConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path) };
|
|
402
|
-
}),
|
|
404
|
+
projects: localProjects.concat(remoteProjects as typeof localProjects),
|
|
403
405
|
});
|
|
404
406
|
var updateInfo = getCachedUpdateInfo();
|
|
405
407
|
if (updateInfo && updateInfo.updateAvailable) {
|
|
@@ -5,7 +5,7 @@ import { loadConfig } from "../config";
|
|
|
5
5
|
import { loadOrCreateIdentity } from "../identity";
|
|
6
6
|
import { generateInviteCode, parseInviteCode, validatePairingToken, consumePairingToken } from "../mesh/pairing";
|
|
7
7
|
import { addPeer, removePeer, loadPeers, getPeer } from "../mesh/peers";
|
|
8
|
-
import { getConnectedPeerIds, connectToPeer, reconnectPeer, getPeerConnection, disconnectPeer } from "../mesh/connector";
|
|
8
|
+
import { getConnectedPeerIds, connectToPeer, reconnectPeer, getPeerConnection, disconnectPeer, getConnectedPeerProjects } from "../mesh/connector";
|
|
9
9
|
import type { PeerInfo } from "@lattice/shared";
|
|
10
10
|
import { networkInterfaces } from "node:os";
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -74,6 +74,13 @@ function getWindowsHostAddresses(): Array<{ name: string; address: string }> {
|
|
|
74
74
|
return results;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function getLocalProjectsList(): Array<{ slug: string; title: string }> {
|
|
78
|
+
var config = loadConfig();
|
|
79
|
+
return config.projects.map(function (p: typeof config.projects[number]) {
|
|
80
|
+
return { slug: p.slug, title: p.title };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
export function buildNodesMessage(): NodeInfo[] {
|
|
78
85
|
var peers = loadPeers();
|
|
79
86
|
var config = loadConfig();
|
|
@@ -95,6 +102,7 @@ export function buildNodesMessage(): NodeInfo[] {
|
|
|
95
102
|
};
|
|
96
103
|
|
|
97
104
|
var remotes: NodeInfo[] = peers.map(function (peer) {
|
|
105
|
+
var peerProjects = getConnectedPeerProjects(peer.id);
|
|
98
106
|
return {
|
|
99
107
|
id: peer.id,
|
|
100
108
|
name: peer.name,
|
|
@@ -103,7 +111,9 @@ export function buildNodesMessage(): NodeInfo[] {
|
|
|
103
111
|
port: 0,
|
|
104
112
|
online: connectedIds.has(peer.id),
|
|
105
113
|
isLocal: false,
|
|
106
|
-
projects:
|
|
114
|
+
projects: peerProjects.map(function (p) {
|
|
115
|
+
return { slug: p.slug, path: "", title: p.title, nodeId: peer.id };
|
|
116
|
+
}),
|
|
107
117
|
};
|
|
108
118
|
});
|
|
109
119
|
|
|
@@ -159,7 +169,7 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
|
|
|
159
169
|
token: parsed!.token,
|
|
160
170
|
port: pairConfig.port,
|
|
161
171
|
addresses: getAllAddresses().map(function (a) { return a.address + ":" + pairConfig.port; }),
|
|
162
|
-
projects:
|
|
172
|
+
projects: getLocalProjectsList(),
|
|
163
173
|
}));
|
|
164
174
|
});
|
|
165
175
|
|
|
@@ -189,6 +199,7 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
|
|
|
189
199
|
|
|
190
200
|
connectToPeer(peer.id, peerAddr);
|
|
191
201
|
|
|
202
|
+
var remoteProjectsList = (data as any).projects ?? [];
|
|
192
203
|
var nodeInfo: NodeInfo = {
|
|
193
204
|
id: peer.id,
|
|
194
205
|
name: peer.name,
|
|
@@ -197,7 +208,9 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
|
|
|
197
208
|
port: parsed!.port,
|
|
198
209
|
online: true,
|
|
199
210
|
isLocal: false,
|
|
200
|
-
projects:
|
|
211
|
+
projects: remoteProjectsList.map(function (rp: { slug: string; title: string }) {
|
|
212
|
+
return { slug: rp.slug, path: "", title: rp.title, nodeId: peer.id };
|
|
213
|
+
}),
|
|
201
214
|
};
|
|
202
215
|
sendTo(clientId, { type: "mesh:paired", node: nodeInfo });
|
|
203
216
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
@@ -230,7 +243,7 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
|
|
|
230
243
|
nodeId: identity.id,
|
|
231
244
|
name: loadConfig().name,
|
|
232
245
|
publicKey: identity.publicKey,
|
|
233
|
-
projects:
|
|
246
|
+
projects: getLocalProjectsList(),
|
|
234
247
|
});
|
|
235
248
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
236
249
|
return;
|
|
@@ -295,6 +295,33 @@ export function onPeerMessage(callback: (nodeId: string, msg: MeshMessage) => vo
|
|
|
295
295
|
messageCallbacks.push(callback);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
export function getConnectedPeerProjects(nodeId: string): Array<{ slug: string; title: string }> {
|
|
299
|
+
var conn = connections.get(nodeId);
|
|
300
|
+
if (!conn || conn.ws.readyState !== WebSocket.OPEN) return [];
|
|
301
|
+
return conn.projects;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getAllRemoteProjects(localNodeId: string): Array<{ slug: string; path: string; title: string; nodeId: string; nodeName: string; isRemote: boolean }> {
|
|
305
|
+
var results: Array<{ slug: string; path: string; title: string; nodeId: string; nodeName: string; isRemote: boolean }> = [];
|
|
306
|
+
for (var [nodeId, conn] of connections) {
|
|
307
|
+
if (conn.ws.readyState !== WebSocket.OPEN) continue;
|
|
308
|
+
var peers = require("./peers") as typeof import("./peers");
|
|
309
|
+
var peer = peers.getPeer(nodeId);
|
|
310
|
+
var peerName = peer ? peer.name : nodeId;
|
|
311
|
+
for (var i = 0; i < conn.projects.length; i++) {
|
|
312
|
+
results.push({
|
|
313
|
+
slug: conn.projects[i].slug,
|
|
314
|
+
path: "",
|
|
315
|
+
title: conn.projects[i].title,
|
|
316
|
+
nodeId: nodeId,
|
|
317
|
+
nodeName: peerName,
|
|
318
|
+
isRemote: true,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return results;
|
|
323
|
+
}
|
|
324
|
+
|
|
298
325
|
export function findNodeForProject(projectSlug: string): string | undefined {
|
|
299
326
|
for (var [nodeId, conn] of connections) {
|
|
300
327
|
if (conn.ws.readyState !== WebSocket.OPEN) {
|