@cryptiklemur/lattice 5.10.0 → 5.11.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.
- package/dist/client/assets/{angular-html-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -4,13 +4,13 @@ import { broadcast } from "../ws/broadcast.js";
|
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { log } from "../logger.js";
|
|
6
6
|
import { upsertFromSnapshot, addToolEventToHistory, markSessionEnded } from "../features/session-history.js";
|
|
7
|
-
|
|
7
|
+
const sessionProjectMap = new Map();
|
|
8
8
|
function matchCwdToProject(cwd) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
for (
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const config = loadConfig();
|
|
10
|
+
const resolved = resolve(cwd);
|
|
11
|
+
for (let i = 0; i < config.projects.length; i++) {
|
|
12
|
+
const p = config.projects[i];
|
|
13
|
+
const projectPath = resolve(p.path);
|
|
14
14
|
if (resolved === projectPath || resolved.startsWith(projectPath + "/")) {
|
|
15
15
|
return { projectName: p.title, projectSlug: p.slug };
|
|
16
16
|
}
|
|
@@ -57,7 +57,7 @@ export function handleHookStatusline(req, res) {
|
|
|
57
57
|
cacheReadTokens,
|
|
58
58
|
cacheCreationTokens,
|
|
59
59
|
}, contextWindow);
|
|
60
|
-
|
|
60
|
+
const statusProject = sessionProjectMap.get(body.session_id) || null;
|
|
61
61
|
broadcast({
|
|
62
62
|
type: "context:statusline",
|
|
63
63
|
hookSessionId: body.session_id,
|
|
@@ -157,7 +157,7 @@ export function handleHookEvent(req, res) {
|
|
|
157
157
|
function estimateTokens(value) {
|
|
158
158
|
if (value == null)
|
|
159
159
|
return 0;
|
|
160
|
-
|
|
160
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
161
161
|
return Math.ceil(text.length / 4);
|
|
162
162
|
}
|
|
163
163
|
function summarizeInput(toolName, input) {
|
|
@@ -166,7 +166,7 @@ function summarizeInput(toolName, input) {
|
|
|
166
166
|
if (typeof input === "string")
|
|
167
167
|
return input.slice(0, 120);
|
|
168
168
|
if (typeof input === "object") {
|
|
169
|
-
|
|
169
|
+
const obj = input;
|
|
170
170
|
if (obj.command)
|
|
171
171
|
return String(obj.command).slice(0, 120);
|
|
172
172
|
if (obj.file_path)
|
|
@@ -177,32 +177,32 @@ function summarizeInput(toolName, input) {
|
|
|
177
177
|
return String(obj.query).slice(0, 120);
|
|
178
178
|
if (obj.prompt)
|
|
179
179
|
return String(obj.prompt).slice(0, 120);
|
|
180
|
-
|
|
180
|
+
const keys = Object.keys(obj);
|
|
181
181
|
if (keys.length > 0)
|
|
182
182
|
return keys.slice(0, 3).join(", ");
|
|
183
183
|
}
|
|
184
184
|
return "";
|
|
185
185
|
}
|
|
186
186
|
export function handleHookToolUse(req, res) {
|
|
187
|
-
|
|
187
|
+
const body = req.body;
|
|
188
188
|
if (!body.session_id) {
|
|
189
189
|
res.status(400).json({ status: "error", message: "missing session_id" });
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
const toolName = body.tool_name || "unknown";
|
|
193
|
+
const inputSummary = summarizeInput(toolName, body.tool_input);
|
|
194
|
+
const estInput = estimateTokens(body.tool_input);
|
|
195
|
+
const estOutput = estimateTokens(body.tool_response);
|
|
196
|
+
const now = Date.now();
|
|
197
197
|
if (body.cwd && !sessionProjectMap.has(body.session_id)) {
|
|
198
|
-
|
|
198
|
+
const match = matchCwdToProject(body.cwd);
|
|
199
199
|
if (match) {
|
|
200
200
|
sessionProjectMap.set(body.session_id, match);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
const sessionProject = sessionProjectMap.get(body.session_id) || null;
|
|
204
|
+
const analyzer = getOrCreateAnalyzer(body.session_id);
|
|
205
|
+
const toolId = body.session_id + "-" + now;
|
|
206
206
|
analyzer.onToolStart(toolId, toolName);
|
|
207
207
|
analyzer.onToolResult(toolId);
|
|
208
208
|
broadcast({
|
|
@@ -3,8 +3,8 @@ import { sendTo } from "../ws/broadcast.js";
|
|
|
3
3
|
import { startLoop, stopLoop, getLoopStatus } from "../features/ralph-loop.js";
|
|
4
4
|
registerHandler("loop", function (clientId, message) {
|
|
5
5
|
if (message.type === "loop:start") {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const startMsg = message;
|
|
7
|
+
const loop = startLoop(startMsg.projectSlug);
|
|
8
8
|
if (!loop) {
|
|
9
9
|
sendTo(clientId, { type: "chat:error", message: "No PROMPT.md found in .claude/loops/ for this project" });
|
|
10
10
|
return;
|
|
@@ -13,16 +13,16 @@ registerHandler("loop", function (clientId, message) {
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
if (message.type === "loop:stop") {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const stopMsg = message;
|
|
17
|
+
const stopped = stopLoop(stopMsg.loopId);
|
|
18
18
|
if (!stopped) {
|
|
19
19
|
sendTo(clientId, { type: "chat:error", message: "Loop not found or not running" });
|
|
20
20
|
}
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
if (message.type === "loop:status") {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const statusMsg = message;
|
|
25
|
+
const status = getLoopStatus(statusMsg.loopId);
|
|
26
26
|
if (!status) {
|
|
27
27
|
sendTo(clientId, { type: "chat:error", message: "Loop not found" });
|
|
28
28
|
return;
|
|
@@ -9,31 +9,31 @@ function getMemoryDir(projectSlug) {
|
|
|
9
9
|
if (projectSlug === "__global__") {
|
|
10
10
|
return join(homedir(), ".claude", "memory");
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
const project = config.projects.find(function (p) { return p.slug === projectSlug; });
|
|
14
14
|
if (!project)
|
|
15
15
|
return null;
|
|
16
|
-
|
|
16
|
+
const hash = "-" + project.path.replace(/\//g, "-").replace(/^-/, "");
|
|
17
17
|
return join(homedir(), ".claude", "projects", hash, "memory");
|
|
18
18
|
}
|
|
19
19
|
function parseFrontmatter(content) {
|
|
20
|
-
|
|
20
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
21
21
|
if (!match)
|
|
22
22
|
return { name: "", description: "", type: "" };
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const yaml = match[1];
|
|
24
|
+
let name = "";
|
|
25
|
+
let description = "";
|
|
26
|
+
let type = "";
|
|
27
|
+
const lines = yaml.split(/\r?\n/);
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
const nameMatch = line.match(/^name:\s*(.+)/);
|
|
31
31
|
if (nameMatch)
|
|
32
32
|
name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
33
|
-
|
|
33
|
+
const descMatch = line.match(/^description:\s*(.+)/);
|
|
34
34
|
if (descMatch)
|
|
35
35
|
description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
36
|
-
|
|
36
|
+
const typeMatch = line.match(/^type:\s*(.+)/);
|
|
37
37
|
if (typeMatch)
|
|
38
38
|
type = typeMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
39
39
|
}
|
|
@@ -42,28 +42,28 @@ function parseFrontmatter(content) {
|
|
|
42
42
|
async function regenerateIndex(memoryDir) {
|
|
43
43
|
if (!existsSync(memoryDir))
|
|
44
44
|
return;
|
|
45
|
-
|
|
45
|
+
const files = (await readdir(memoryDir)).filter(function (f) {
|
|
46
46
|
return f.endsWith(".md") && f !== "MEMORY.md";
|
|
47
47
|
});
|
|
48
|
-
|
|
49
|
-
for (
|
|
48
|
+
const grouped = {};
|
|
49
|
+
for (let i = 0; i < files.length; i++) {
|
|
50
50
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const content = await readFile(join(memoryDir, files[i]), "utf-8");
|
|
52
|
+
const meta = parseFrontmatter(content);
|
|
53
|
+
const type = meta.type || "other";
|
|
54
54
|
if (!grouped[type])
|
|
55
55
|
grouped[type] = [];
|
|
56
56
|
grouped[type].push({ filename: files[i], name: meta.name || files[i], description: meta.description || "" });
|
|
57
57
|
}
|
|
58
58
|
catch { }
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (
|
|
60
|
+
const lines = ["# Memory Index", ""];
|
|
61
|
+
const types = Object.keys(grouped).sort();
|
|
62
|
+
for (let t = 0; t < types.length; t++) {
|
|
63
63
|
lines.push("## " + types[t].charAt(0).toUpperCase() + types[t].slice(1));
|
|
64
|
-
|
|
65
|
-
for (
|
|
66
|
-
|
|
64
|
+
const entries = grouped[types[t]];
|
|
65
|
+
for (let e = 0; e < entries.length; e++) {
|
|
66
|
+
const desc = entries[e].description ? " — " + entries[e].description : "";
|
|
67
67
|
lines.push("- [" + entries[e].filename + "](" + entries[e].filename + ")" + desc);
|
|
68
68
|
}
|
|
69
69
|
lines.push("");
|
|
@@ -71,14 +71,14 @@ async function regenerateIndex(memoryDir) {
|
|
|
71
71
|
await writeFile(join(memoryDir, "MEMORY.md"), lines.join("\n"), "utf-8");
|
|
72
72
|
}
|
|
73
73
|
async function listMemoryFiles(memDir) {
|
|
74
|
-
|
|
74
|
+
const files = (await readdir(memDir)).filter(function (f) {
|
|
75
75
|
return f.endsWith(".md") && f !== "MEMORY.md";
|
|
76
76
|
});
|
|
77
|
-
|
|
78
|
-
for (
|
|
77
|
+
const memories = [];
|
|
78
|
+
for (let i = 0; i < files.length; i++) {
|
|
79
79
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const content = await readFile(join(memDir, files[i]), "utf-8");
|
|
81
|
+
const meta = parseFrontmatter(content);
|
|
82
82
|
memories.push({
|
|
83
83
|
filename: files[i],
|
|
84
84
|
name: meta.name || files[i].replace(/\.md$/, ""),
|
|
@@ -93,25 +93,25 @@ async function listMemoryFiles(memDir) {
|
|
|
93
93
|
}
|
|
94
94
|
registerHandler("memory", async function (clientId, message) {
|
|
95
95
|
if (message.type === "memory:list") {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const listMsg = message;
|
|
97
|
+
const memDir = getMemoryDir(listMsg.projectSlug);
|
|
98
98
|
if (!memDir || !existsSync(memDir)) {
|
|
99
99
|
sendTo(clientId, { type: "memory:list_result", projectSlug: listMsg.projectSlug, memories: [] });
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
const memories = await listMemoryFiles(memDir);
|
|
103
103
|
sendTo(clientId, { type: "memory:list_result", projectSlug: listMsg.projectSlug, memories: memories });
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
if (message.type === "memory:view") {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const viewMsg = message;
|
|
108
|
+
const viewDir = getMemoryDir(viewMsg.projectSlug);
|
|
109
109
|
if (!viewDir) {
|
|
110
110
|
sendTo(clientId, { type: "memory:view_result", filename: viewMsg.filename, content: "Project not found." });
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
try {
|
|
114
|
-
|
|
114
|
+
const viewContent = await readFile(join(viewDir, viewMsg.filename), "utf-8");
|
|
115
115
|
sendTo(clientId, { type: "memory:view_result", filename: viewMsg.filename, content: viewContent });
|
|
116
116
|
}
|
|
117
117
|
catch {
|
|
@@ -120,8 +120,8 @@ registerHandler("memory", async function (clientId, message) {
|
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
if (message.type === "memory:save") {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
const saveMsg = message;
|
|
124
|
+
const saveDir = getMemoryDir(saveMsg.projectSlug);
|
|
125
125
|
if (!saveDir) {
|
|
126
126
|
sendTo(clientId, { type: "memory:save_result", success: false, message: "Project not found." });
|
|
127
127
|
return;
|
|
@@ -131,7 +131,7 @@ registerHandler("memory", async function (clientId, message) {
|
|
|
131
131
|
await writeFile(join(saveDir, saveMsg.filename), saveMsg.content, "utf-8");
|
|
132
132
|
await regenerateIndex(saveDir);
|
|
133
133
|
sendTo(clientId, { type: "memory:save_result", success: true });
|
|
134
|
-
|
|
134
|
+
const updatedMemories = await listMemoryFiles(saveDir);
|
|
135
135
|
sendTo(clientId, { type: "memory:list_result", projectSlug: saveMsg.projectSlug, memories: updatedMemories });
|
|
136
136
|
}
|
|
137
137
|
catch (err) {
|
|
@@ -140,14 +140,14 @@ registerHandler("memory", async function (clientId, message) {
|
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
if (message.type === "memory:delete") {
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const delMsg = message;
|
|
144
|
+
const delDir = getMemoryDir(delMsg.projectSlug);
|
|
145
145
|
if (!delDir) {
|
|
146
146
|
sendTo(clientId, { type: "memory:delete_result", success: false, message: "Project not found." });
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
149
|
try {
|
|
150
|
-
|
|
150
|
+
const filePath = join(delDir, delMsg.filename);
|
|
151
151
|
if (!existsSync(filePath)) {
|
|
152
152
|
sendTo(clientId, { type: "memory:delete_result", success: false, message: "Memory not found." });
|
|
153
153
|
return;
|
|
@@ -155,7 +155,7 @@ registerHandler("memory", async function (clientId, message) {
|
|
|
155
155
|
await unlink(filePath);
|
|
156
156
|
await regenerateIndex(delDir);
|
|
157
157
|
sendTo(clientId, { type: "memory:delete_result", success: true });
|
|
158
|
-
|
|
158
|
+
const remainingMemories = await listMemoryFiles(delDir);
|
|
159
159
|
sendTo(clientId, { type: "memory:list_result", projectSlug: delMsg.projectSlug, memories: remainingMemories });
|
|
160
160
|
}
|
|
161
161
|
catch (err) {
|
|
@@ -12,27 +12,27 @@ import { networkInterfaces } from "node:os";
|
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
14
|
function getLocalAddress() {
|
|
15
|
-
|
|
15
|
+
const all = getAllAddresses();
|
|
16
16
|
return all.length > 0 ? all[0].address : "localhost";
|
|
17
17
|
}
|
|
18
18
|
function getAllAddresses() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
for (
|
|
23
|
-
|
|
19
|
+
const interfaces = networkInterfaces();
|
|
20
|
+
const keys = Object.keys(interfaces);
|
|
21
|
+
const results = [];
|
|
22
|
+
for (let i = 0; i < keys.length; i++) {
|
|
23
|
+
const addrs = interfaces[keys[i]];
|
|
24
24
|
if (!addrs)
|
|
25
25
|
continue;
|
|
26
|
-
for (
|
|
26
|
+
for (let j = 0; j < addrs.length; j++) {
|
|
27
27
|
if (!addrs[j].internal && addrs[j].family === "IPv4") {
|
|
28
28
|
results.push({ name: keys[i], address: addrs[j].address });
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
if (isWSL()) {
|
|
33
|
-
|
|
34
|
-
for (
|
|
35
|
-
|
|
33
|
+
const windowsAddrs = getWindowsHostAddresses();
|
|
34
|
+
for (let w = 0; w < windowsAddrs.length; w++) {
|
|
35
|
+
const exists = results.some(function (r) { return r.address === windowsAddrs[w].address; });
|
|
36
36
|
if (!exists) {
|
|
37
37
|
results.push(windowsAddrs[w]);
|
|
38
38
|
}
|
|
@@ -43,7 +43,7 @@ function getAllAddresses() {
|
|
|
43
43
|
function isWSL() {
|
|
44
44
|
try {
|
|
45
45
|
if (existsSync("/proc/version")) {
|
|
46
|
-
|
|
46
|
+
const version = readFileSync("/proc/version", "utf-8");
|
|
47
47
|
return version.toLowerCase().includes("microsoft");
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -51,14 +51,14 @@ function isWSL() {
|
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
53
|
function getWindowsHostAddresses() {
|
|
54
|
-
|
|
54
|
+
const results = [];
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const output = execSync("powershell.exe -NoProfile -Command \"Get-NetIPAddress -AddressFamily IPv4 | ForEach-Object { \\$_.IPAddress + '|' + \\$_.InterfaceAlias }\"", { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
57
|
+
const lines = output.trim().split(/\r?\n/);
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const parts = lines[i].trim().split("|");
|
|
60
|
+
const ip = parts[0];
|
|
61
|
+
const iface = parts[1] || "windows";
|
|
62
62
|
if (!ip || ip === "127.0.0.1")
|
|
63
63
|
continue;
|
|
64
64
|
if (ip.startsWith("169.254."))
|
|
@@ -72,18 +72,18 @@ function getWindowsHostAddresses() {
|
|
|
72
72
|
return results;
|
|
73
73
|
}
|
|
74
74
|
function getLocalProjectsList() {
|
|
75
|
-
|
|
75
|
+
const config = loadConfig();
|
|
76
76
|
return config.projects.map(function (p) {
|
|
77
77
|
return { slug: p.slug, title: p.title };
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
export function buildNodesMessage() {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const peers = loadPeers();
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
const identity = loadOrCreateIdentity();
|
|
84
|
+
const connectedIds = new Set(getConnectedPeerIds());
|
|
85
|
+
const localAddrs = getAllAddresses().map(function (a) { return a.address + ":" + config.port; });
|
|
86
|
+
const local = {
|
|
87
87
|
id: identity.id,
|
|
88
88
|
name: config.name,
|
|
89
89
|
address: localAddrs[0] ?? "localhost:" + config.port,
|
|
@@ -95,9 +95,9 @@ export function buildNodesMessage() {
|
|
|
95
95
|
return { slug: p.slug, path: p.path, title: p.title, nodeId: identity.id };
|
|
96
96
|
}),
|
|
97
97
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const remotes = peers.map(function (peer) {
|
|
99
|
+
const peerProjects = getConnectedPeerProjects(peer.id);
|
|
100
|
+
const health = getPeerHealth(peer.id);
|
|
101
101
|
return {
|
|
102
102
|
id: peer.id,
|
|
103
103
|
name: peer.name,
|
|
@@ -118,9 +118,9 @@ export function buildNodesMessage() {
|
|
|
118
118
|
registerHandler("mesh", function (clientId, message) {
|
|
119
119
|
log.meshHello("mesh message: %s from %s", message.type, clientId.slice(0, 8));
|
|
120
120
|
if (message.type === "mesh:generate_invite") {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
const genMsg = message;
|
|
122
|
+
const config = loadConfig();
|
|
123
|
+
const address = genMsg.address || getLocalAddress();
|
|
124
124
|
generateInviteCode(address, config.port).then(function (result) {
|
|
125
125
|
sendTo(clientId, {
|
|
126
126
|
type: "mesh:invite_code",
|
|
@@ -133,26 +133,26 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
if (message.type === "mesh:addresses") {
|
|
136
|
-
|
|
136
|
+
const addresses = getAllAddresses();
|
|
137
137
|
sendTo(clientId, { type: "mesh:addresses_result", addresses: addresses });
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
if (message.type === "mesh:pair") {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const pairMsg = message;
|
|
142
|
+
const parsed = parseInviteCode(pairMsg.code);
|
|
143
143
|
if (!parsed) {
|
|
144
144
|
sendTo(clientId, { type: "mesh:pair_failed", message: "Invalid invite code format" });
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const wsUrl = "ws://" + parsed.address + ":" + parsed.port + "/ws";
|
|
148
|
+
const pairWs = new WebSocket(wsUrl);
|
|
149
|
+
const pairTimeout = setTimeout(function () {
|
|
150
150
|
pairWs.close();
|
|
151
151
|
sendTo(clientId, { type: "mesh:pair_failed", message: "Connection timed out" });
|
|
152
152
|
}, 15000);
|
|
153
153
|
pairWs.addEventListener("open", function () {
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
const identity = loadOrCreateIdentity();
|
|
155
|
+
const pairConfig = loadConfig();
|
|
156
156
|
pairWs.send(JSON.stringify({
|
|
157
157
|
type: "mesh:hello",
|
|
158
158
|
nodeId: identity.id,
|
|
@@ -166,7 +166,7 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
166
166
|
});
|
|
167
167
|
pairWs.addEventListener("message", function (event) {
|
|
168
168
|
try {
|
|
169
|
-
|
|
169
|
+
const data = JSON.parse(event.data);
|
|
170
170
|
if (data.type === "mesh:hello_rejected") {
|
|
171
171
|
clearTimeout(pairTimeout);
|
|
172
172
|
pairWs.close();
|
|
@@ -175,8 +175,8 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
175
175
|
}
|
|
176
176
|
if (data.type === "mesh:hello" && data.nodeId && data.name) {
|
|
177
177
|
clearTimeout(pairTimeout);
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
const peerAddr = parsed.address + ":" + parsed.port;
|
|
179
|
+
const peer = {
|
|
180
180
|
id: data.nodeId,
|
|
181
181
|
name: data.name,
|
|
182
182
|
addresses: [peerAddr],
|
|
@@ -186,8 +186,8 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
186
186
|
addPeer(peer);
|
|
187
187
|
pairWs.close();
|
|
188
188
|
connectToPeer(peer.id, peerAddr);
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
const remoteProjectsList = data.projects ?? [];
|
|
190
|
+
const nodeInfo = {
|
|
191
191
|
id: peer.id,
|
|
192
192
|
name: peer.name,
|
|
193
193
|
address: peerAddr,
|
|
@@ -214,8 +214,8 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
216
|
if (message.type === "mesh:hello") {
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
const hello = message;
|
|
218
|
+
const knownPeer = hello.nodeId ? getPeer(hello.nodeId) : undefined;
|
|
219
219
|
log.meshHello("mesh:hello from nodeId=%s name=%s known=%s", hello.nodeId?.slice(0, 8), hello.name, !!knownPeer);
|
|
220
220
|
if (knownPeer) {
|
|
221
221
|
if (knownPeer.publicKey && hello.publicKey && knownPeer.publicKey !== hello.publicKey) {
|
|
@@ -223,12 +223,12 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
223
223
|
sendTo(clientId, { type: "mesh:hello_rejected", error: "Public key mismatch — possible impersonation" });
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
|
-
|
|
226
|
+
const inboundWs = getClientWebSocket(clientId);
|
|
227
227
|
log.meshHello(" registering inbound connection for %s (ws=%s, projects=%d)", hello.name, !!inboundWs, hello.projects?.length ?? 0);
|
|
228
228
|
if (inboundWs) {
|
|
229
229
|
registerInboundPeer(hello.nodeId, inboundWs, hello.projects ?? []);
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
const identity = loadOrCreateIdentity();
|
|
232
232
|
sendTo(clientId, {
|
|
233
233
|
type: "mesh:hello",
|
|
234
234
|
nodeId: identity.id,
|
|
@@ -244,8 +244,8 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
244
244
|
return;
|
|
245
245
|
}
|
|
246
246
|
consumePairingToken(hello.token);
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const peerAddresses = hello.addresses ?? [];
|
|
248
|
+
const peer = {
|
|
249
249
|
id: hello.nodeId,
|
|
250
250
|
name: hello.name,
|
|
251
251
|
addresses: peerAddresses,
|
|
@@ -256,7 +256,7 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
256
256
|
if (peerAddresses.length > 0) {
|
|
257
257
|
connectToPeer(peer.id, peerAddresses[0]);
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
const identity2 = loadOrCreateIdentity();
|
|
260
260
|
sendTo(clientId, {
|
|
261
261
|
type: "mesh:hello",
|
|
262
262
|
nodeId: identity2.id,
|
|
@@ -268,7 +268,7 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
268
268
|
return;
|
|
269
269
|
}
|
|
270
270
|
if (message.type === "mesh:proxy_request") {
|
|
271
|
-
|
|
271
|
+
const proxyReq = message;
|
|
272
272
|
log.meshProxy("received proxy_request via handler from %s: %s for %s", clientId.slice(0, 8), proxyReq.payload.type, proxyReq.projectSlug);
|
|
273
273
|
registerVirtualClient("mesh-proxy:" + clientId + ":" + proxyReq.requestId, function (response) {
|
|
274
274
|
log.meshProxy(" → sending proxy_response %s back to client %s", response.type, clientId.slice(0, 8));
|
|
@@ -286,13 +286,13 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
288
|
if (message.type === "mesh:proxy_response") {
|
|
289
|
-
|
|
289
|
+
const proxyRes = message;
|
|
290
290
|
log.meshProxy("received proxy_response via handler: %s", proxyRes.payload.type);
|
|
291
291
|
handleProxyResponse(proxyRes);
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
294
|
if (message.type === "mesh:reconnect") {
|
|
295
|
-
|
|
295
|
+
const reconnectMsg = message;
|
|
296
296
|
reconnectPeer(reconnectMsg.nodeId);
|
|
297
297
|
setTimeout(function () {
|
|
298
298
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
@@ -300,23 +300,23 @@ registerHandler("mesh", function (clientId, message) {
|
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
302
|
if (message.type === "mesh:unpair") {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
303
|
+
const unpairMsg = message;
|
|
304
|
+
const identity = loadOrCreateIdentity();
|
|
305
|
+
const peerWs = getPeerConnection(unpairMsg.nodeId);
|
|
306
306
|
if (peerWs) {
|
|
307
307
|
peerWs.send(JSON.stringify({ type: "mesh:unpaired", nodeId: identity.id }));
|
|
308
308
|
}
|
|
309
309
|
disconnectPeer(unpairMsg.nodeId);
|
|
310
|
-
|
|
310
|
+
const removed = removePeer(unpairMsg.nodeId);
|
|
311
311
|
if (removed) {
|
|
312
312
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
313
313
|
}
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
if (message.type === "mesh:unpaired") {
|
|
317
|
-
|
|
317
|
+
const unpaired = message;
|
|
318
318
|
disconnectPeer(unpaired.nodeId);
|
|
319
|
-
|
|
319
|
+
const wasRemoved = removePeer(unpaired.nodeId);
|
|
320
320
|
if (wasRemoved) {
|
|
321
321
|
broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
|
|
322
322
|
}
|
|
@@ -3,19 +3,19 @@ import { sendTo, broadcast } from "../ws/broadcast.js";
|
|
|
3
3
|
import { listNotes, createNote, updateNote, deleteNote } from "../features/sticky-notes.js";
|
|
4
4
|
registerHandler("notes", function (clientId, message) {
|
|
5
5
|
if (message.type === "notes:list") {
|
|
6
|
-
|
|
6
|
+
const listMsg = message;
|
|
7
7
|
sendTo(clientId, { type: "notes:list_result", notes: listNotes(listMsg.projectSlug) });
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
if (message.type === "notes:create") {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const createMsg = message;
|
|
12
|
+
const note = createNote(createMsg.content, createMsg.projectSlug);
|
|
13
13
|
broadcast({ type: "notes:created", note });
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
if (message.type === "notes:update") {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const updateMsg = message;
|
|
18
|
+
const updated = updateNote(updateMsg.id, updateMsg.content);
|
|
19
19
|
if (!updated) {
|
|
20
20
|
sendTo(clientId, { type: "chat:error", message: "Note not found" });
|
|
21
21
|
return;
|
|
@@ -24,8 +24,8 @@ registerHandler("notes", function (clientId, message) {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (message.type === "notes:delete") {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const deleteMsg = message;
|
|
28
|
+
const deleted = deleteNote(deleteMsg.id);
|
|
29
29
|
if (!deleted) {
|
|
30
30
|
sendTo(clientId, { type: "chat:error", message: "Note not found" });
|
|
31
31
|
return;
|