memorix 0.5.0 → 0.5.2
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/README.md +8 -6
- package/dist/cli/index.js +333 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +921 -0
- package/dist/dashboard/static/index.html +85 -0
- package/dist/dashboard/static/logo.png +0 -0
- package/dist/dashboard/static/style.css +1048 -0
- package/dist/index.js +372 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -28,6 +28,103 @@ var init_esm_shims = __esm({
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
// src/store/persistence.ts
|
|
32
|
+
import { promises as fs } from "fs";
|
|
33
|
+
import path2 from "path";
|
|
34
|
+
import os from "os";
|
|
35
|
+
async function getProjectDataDir(projectId, baseDir) {
|
|
36
|
+
const dataDir = baseDir ?? DEFAULT_DATA_DIR;
|
|
37
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
38
|
+
return dataDir;
|
|
39
|
+
}
|
|
40
|
+
function getGraphFilePath(projectDir2) {
|
|
41
|
+
return path2.join(projectDir2, "graph.jsonl");
|
|
42
|
+
}
|
|
43
|
+
async function saveGraphJsonl(projectDir2, entities, relations) {
|
|
44
|
+
const lines = [
|
|
45
|
+
...entities.map(
|
|
46
|
+
(e) => JSON.stringify({ type: "entity", name: e.name, entityType: e.entityType, observations: e.observations })
|
|
47
|
+
),
|
|
48
|
+
...relations.map(
|
|
49
|
+
(r) => JSON.stringify({ type: "relation", from: r.from, to: r.to, relationType: r.relationType })
|
|
50
|
+
)
|
|
51
|
+
];
|
|
52
|
+
await fs.writeFile(getGraphFilePath(projectDir2), lines.join("\n"), "utf-8");
|
|
53
|
+
}
|
|
54
|
+
async function loadGraphJsonl(projectDir2) {
|
|
55
|
+
const filePath = getGraphFilePath(projectDir2);
|
|
56
|
+
try {
|
|
57
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
58
|
+
const lines = data.split("\n").filter((line) => line.trim() !== "");
|
|
59
|
+
return lines.reduce(
|
|
60
|
+
(graph, line) => {
|
|
61
|
+
const item = JSON.parse(line);
|
|
62
|
+
if (item.type === "entity") {
|
|
63
|
+
graph.entities.push({
|
|
64
|
+
name: item.name,
|
|
65
|
+
entityType: item.entityType,
|
|
66
|
+
observations: item.observations
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (item.type === "relation") {
|
|
70
|
+
graph.relations.push({
|
|
71
|
+
from: item.from,
|
|
72
|
+
to: item.to,
|
|
73
|
+
relationType: item.relationType
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return graph;
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
entities: [],
|
|
80
|
+
relations: []
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
85
|
+
return { entities: [], relations: [] };
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function saveObservationsJson(projectDir2, observations2) {
|
|
91
|
+
const filePath = path2.join(projectDir2, "observations.json");
|
|
92
|
+
await fs.writeFile(filePath, JSON.stringify(observations2, null, 2), "utf-8");
|
|
93
|
+
}
|
|
94
|
+
async function loadObservationsJson(projectDir2) {
|
|
95
|
+
const filePath = path2.join(projectDir2, "observations.json");
|
|
96
|
+
try {
|
|
97
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
98
|
+
return JSON.parse(data);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function saveIdCounter(projectDir2, nextId2) {
|
|
107
|
+
const filePath = path2.join(projectDir2, "counter.json");
|
|
108
|
+
await fs.writeFile(filePath, JSON.stringify({ nextId: nextId2 }), "utf-8");
|
|
109
|
+
}
|
|
110
|
+
async function loadIdCounter(projectDir2) {
|
|
111
|
+
const filePath = path2.join(projectDir2, "counter.json");
|
|
112
|
+
try {
|
|
113
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
114
|
+
return JSON.parse(data).nextId ?? 1;
|
|
115
|
+
} catch {
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
var DEFAULT_DATA_DIR;
|
|
120
|
+
var init_persistence = __esm({
|
|
121
|
+
"src/store/persistence.ts"() {
|
|
122
|
+
"use strict";
|
|
123
|
+
init_esm_shims();
|
|
124
|
+
DEFAULT_DATA_DIR = path2.join(os.homedir(), ".memorix", "data");
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
31
128
|
// src/types.ts
|
|
32
129
|
var OBSERVATION_ICONS;
|
|
33
130
|
var init_types = __esm({
|
|
@@ -848,111 +945,212 @@ var init_retention = __esm({
|
|
|
848
945
|
}
|
|
849
946
|
});
|
|
850
947
|
|
|
851
|
-
// src/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
import {
|
|
858
|
-
import
|
|
859
|
-
import {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
import { promises as fs } from "fs";
|
|
867
|
-
import path2 from "path";
|
|
868
|
-
import os from "os";
|
|
869
|
-
var DEFAULT_DATA_DIR = path2.join(os.homedir(), ".memorix", "data");
|
|
870
|
-
async function getProjectDataDir(projectId, baseDir) {
|
|
871
|
-
const dataDir = baseDir ?? DEFAULT_DATA_DIR;
|
|
872
|
-
await fs.mkdir(dataDir, { recursive: true });
|
|
873
|
-
return dataDir;
|
|
948
|
+
// src/dashboard/server.ts
|
|
949
|
+
var server_exports = {};
|
|
950
|
+
__export(server_exports, {
|
|
951
|
+
startDashboard: () => startDashboard
|
|
952
|
+
});
|
|
953
|
+
import { createServer } from "http";
|
|
954
|
+
import { promises as fs4 } from "fs";
|
|
955
|
+
import path6 from "path";
|
|
956
|
+
import { exec } from "child_process";
|
|
957
|
+
function sendJson(res, data, status = 200) {
|
|
958
|
+
res.writeHead(status, {
|
|
959
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
960
|
+
"Access-Control-Allow-Origin": "*"
|
|
961
|
+
});
|
|
962
|
+
res.end(JSON.stringify(data));
|
|
874
963
|
}
|
|
875
|
-
function
|
|
876
|
-
|
|
964
|
+
function sendError(res, message, status = 500) {
|
|
965
|
+
sendJson(res, { error: message }, status);
|
|
877
966
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
...entities.map(
|
|
881
|
-
(e) => JSON.stringify({ type: "entity", name: e.name, entityType: e.entityType, observations: e.observations })
|
|
882
|
-
),
|
|
883
|
-
...relations.map(
|
|
884
|
-
(r) => JSON.stringify({ type: "relation", from: r.from, to: r.to, relationType: r.relationType })
|
|
885
|
-
)
|
|
886
|
-
];
|
|
887
|
-
await fs.writeFile(getGraphFilePath(projectDir2), lines.join("\n"), "utf-8");
|
|
967
|
+
function filterByProject(items, projectId) {
|
|
968
|
+
return items.filter((item) => item.projectId === projectId);
|
|
888
969
|
}
|
|
889
|
-
async function
|
|
890
|
-
const
|
|
970
|
+
async function handleApi(req, res, dataDir, projectId, projectName) {
|
|
971
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
972
|
+
const apiPath = url.pathname.replace("/api", "");
|
|
891
973
|
try {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
974
|
+
switch (apiPath) {
|
|
975
|
+
case "/project": {
|
|
976
|
+
sendJson(res, { id: projectId, name: projectName });
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case "/graph": {
|
|
980
|
+
const graph = await loadGraphJsonl(dataDir);
|
|
981
|
+
sendJson(res, graph);
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
case "/observations": {
|
|
985
|
+
const allObs = await loadObservationsJson(dataDir);
|
|
986
|
+
const observations2 = filterByProject(allObs, projectId);
|
|
987
|
+
sendJson(res, observations2);
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
case "/stats": {
|
|
991
|
+
const graph = await loadGraphJsonl(dataDir);
|
|
992
|
+
const allObs = await loadObservationsJson(dataDir);
|
|
993
|
+
const observations2 = filterByProject(allObs, projectId);
|
|
994
|
+
const nextId2 = await loadIdCounter(dataDir);
|
|
995
|
+
const typeCounts = {};
|
|
996
|
+
for (const obs of observations2) {
|
|
997
|
+
const t = obs.type || "unknown";
|
|
998
|
+
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
903
999
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1000
|
+
const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
|
|
1001
|
+
sendJson(res, {
|
|
1002
|
+
entities: graph.entities.length,
|
|
1003
|
+
relations: graph.relations.length,
|
|
1004
|
+
observations: observations2.length,
|
|
1005
|
+
nextId: nextId2,
|
|
1006
|
+
typeCounts,
|
|
1007
|
+
recentObservations: sorted
|
|
1008
|
+
});
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
case "/retention": {
|
|
1012
|
+
const allObs = await loadObservationsJson(dataDir);
|
|
1013
|
+
const observations2 = filterByProject(allObs, projectId);
|
|
1014
|
+
const now = Date.now();
|
|
1015
|
+
const scored = observations2.map((obs) => {
|
|
1016
|
+
const age = now - new Date(obs.createdAt || now).getTime();
|
|
1017
|
+
const ageHours = age / (1e3 * 60 * 60);
|
|
1018
|
+
const importance = obs.importance ?? 5;
|
|
1019
|
+
const accessCount = obs.accessCount ?? 0;
|
|
1020
|
+
const lambda = 0.01;
|
|
1021
|
+
const decayScore = importance * Math.exp(-lambda * ageHours);
|
|
1022
|
+
const accessBonus = Math.min(accessCount * 0.5, 3);
|
|
1023
|
+
const score = Math.min(decayScore + accessBonus, 10);
|
|
1024
|
+
const isImmune2 = importance >= 8 || obs.type === "gotcha" || obs.type === "decision";
|
|
1025
|
+
return {
|
|
1026
|
+
id: obs.id,
|
|
1027
|
+
title: obs.title,
|
|
1028
|
+
type: obs.type,
|
|
1029
|
+
entityName: obs.entityName,
|
|
1030
|
+
score: Math.round(score * 100) / 100,
|
|
1031
|
+
isImmune: isImmune2,
|
|
1032
|
+
ageHours: Math.round(ageHours * 10) / 10,
|
|
1033
|
+
accessCount
|
|
1034
|
+
};
|
|
1035
|
+
});
|
|
1036
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1037
|
+
const activeCount = scored.filter((s) => s.score >= 3).length;
|
|
1038
|
+
const staleCount = scored.filter((s) => s.score < 3 && s.score >= 1).length;
|
|
1039
|
+
const archiveCount = scored.filter((s) => s.score < 1).length;
|
|
1040
|
+
const immuneCount = scored.filter((s) => s.isImmune).length;
|
|
1041
|
+
sendJson(res, {
|
|
1042
|
+
summary: { active: activeCount, stale: staleCount, archive: archiveCount, immune: immuneCount },
|
|
1043
|
+
items: scored
|
|
1044
|
+
});
|
|
1045
|
+
break;
|
|
916
1046
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
920
|
-
return { entities: [], relations: [] };
|
|
1047
|
+
default:
|
|
1048
|
+
sendError(res, "Not found", 404);
|
|
921
1049
|
}
|
|
922
|
-
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1052
|
+
sendError(res, message);
|
|
923
1053
|
}
|
|
924
1054
|
}
|
|
925
|
-
async function
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const filePath =
|
|
1055
|
+
async function serveStatic(req, res, staticDir) {
|
|
1056
|
+
let urlPath = new URL(req.url || "/", `http://${req.headers.host}`).pathname;
|
|
1057
|
+
if (urlPath === "/" || !urlPath.includes(".")) {
|
|
1058
|
+
urlPath = "/index.html";
|
|
1059
|
+
}
|
|
1060
|
+
const filePath = path6.join(staticDir, urlPath);
|
|
1061
|
+
if (!filePath.startsWith(staticDir)) {
|
|
1062
|
+
sendError(res, "Forbidden", 403);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
931
1065
|
try {
|
|
932
|
-
const data = await
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1066
|
+
const data = await fs4.readFile(filePath);
|
|
1067
|
+
const ext = path6.extname(filePath);
|
|
1068
|
+
res.writeHead(200, {
|
|
1069
|
+
"Content-Type": MIME_TYPES[ext] || "application/octet-stream",
|
|
1070
|
+
"Cache-Control": "no-cache"
|
|
1071
|
+
});
|
|
1072
|
+
res.end(data);
|
|
1073
|
+
} catch {
|
|
1074
|
+
try {
|
|
1075
|
+
const indexData = await fs4.readFile(path6.join(staticDir, "index.html"));
|
|
1076
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1077
|
+
res.end(indexData);
|
|
1078
|
+
} catch {
|
|
1079
|
+
sendError(res, "Not found", 404);
|
|
937
1080
|
}
|
|
938
|
-
throw error;
|
|
939
1081
|
}
|
|
940
1082
|
}
|
|
941
|
-
|
|
942
|
-
const
|
|
943
|
-
|
|
1083
|
+
function openBrowser(url) {
|
|
1084
|
+
const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
1085
|
+
exec(cmd, () => {
|
|
1086
|
+
});
|
|
944
1087
|
}
|
|
945
|
-
async function
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1088
|
+
async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true) {
|
|
1089
|
+
const resolvedStaticDir = staticDir;
|
|
1090
|
+
const server = createServer(async (req, res) => {
|
|
1091
|
+
const url = req.url || "/";
|
|
1092
|
+
if (url.startsWith("/api/")) {
|
|
1093
|
+
await handleApi(req, res, dataDir, projectId, projectName);
|
|
1094
|
+
} else {
|
|
1095
|
+
await serveStatic(req, res, resolvedStaticDir);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
return new Promise((resolve2, reject) => {
|
|
1099
|
+
server.on("error", (err) => {
|
|
1100
|
+
if (err.code === "EADDRINUSE") {
|
|
1101
|
+
console.error(`Port ${port} is already in use. Try: memorix dashboard --port ${port + 1}`);
|
|
1102
|
+
reject(err);
|
|
1103
|
+
} else {
|
|
1104
|
+
reject(err);
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
server.listen(port, () => {
|
|
1108
|
+
const url = `http://localhost:${port}`;
|
|
1109
|
+
console.error(`
|
|
1110
|
+
Memorix Dashboard`);
|
|
1111
|
+
console.error(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
1112
|
+
console.error(` Project: ${projectName} (${projectId})`);
|
|
1113
|
+
console.error(` Local: ${url}`);
|
|
1114
|
+
console.error(` Data dir: ${dataDir}`);
|
|
1115
|
+
console.error(`
|
|
1116
|
+
Press Ctrl+C to stop
|
|
1117
|
+
`);
|
|
1118
|
+
if (autoOpen) openBrowser(url);
|
|
1119
|
+
resolve2();
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
953
1122
|
}
|
|
1123
|
+
var MIME_TYPES;
|
|
1124
|
+
var init_server = __esm({
|
|
1125
|
+
"src/dashboard/server.ts"() {
|
|
1126
|
+
"use strict";
|
|
1127
|
+
init_esm_shims();
|
|
1128
|
+
init_persistence();
|
|
1129
|
+
MIME_TYPES = {
|
|
1130
|
+
".html": "text/html; charset=utf-8",
|
|
1131
|
+
".css": "text/css; charset=utf-8",
|
|
1132
|
+
".js": "application/javascript; charset=utf-8",
|
|
1133
|
+
".json": "application/json; charset=utf-8",
|
|
1134
|
+
".svg": "image/svg+xml",
|
|
1135
|
+
".png": "image/png",
|
|
1136
|
+
".ico": "image/x-icon"
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
// src/index.ts
|
|
1142
|
+
init_esm_shims();
|
|
1143
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1144
|
+
|
|
1145
|
+
// src/server.ts
|
|
1146
|
+
init_esm_shims();
|
|
1147
|
+
import { watch } from "fs";
|
|
1148
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1149
|
+
import { z } from "zod";
|
|
954
1150
|
|
|
955
1151
|
// src/memory/graph.ts
|
|
1152
|
+
init_esm_shims();
|
|
1153
|
+
init_persistence();
|
|
956
1154
|
var KnowledgeGraphManager = class {
|
|
957
1155
|
entities = [];
|
|
958
1156
|
relations = [];
|
|
@@ -1075,6 +1273,7 @@ var KnowledgeGraphManager = class {
|
|
|
1075
1273
|
// src/memory/observations.ts
|
|
1076
1274
|
init_esm_shims();
|
|
1077
1275
|
init_orama_store();
|
|
1276
|
+
init_persistence();
|
|
1078
1277
|
|
|
1079
1278
|
// src/compact/token-budget.ts
|
|
1080
1279
|
init_esm_shims();
|
|
@@ -1578,6 +1777,9 @@ function normalizeGitRemote(remote) {
|
|
|
1578
1777
|
}
|
|
1579
1778
|
}
|
|
1580
1779
|
|
|
1780
|
+
// src/server.ts
|
|
1781
|
+
init_persistence();
|
|
1782
|
+
|
|
1581
1783
|
// src/rules/syncer.ts
|
|
1582
1784
|
init_esm_shims();
|
|
1583
1785
|
import { promises as fs2 } from "fs";
|
|
@@ -2888,10 +3090,10 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
2888
3090
|
for (const [target, adapter] of this.adapters) {
|
|
2889
3091
|
const configPath = adapter.getConfigPath(this.projectRoot);
|
|
2890
3092
|
const globalPath = adapter.getConfigPath();
|
|
2891
|
-
for (const
|
|
2892
|
-
if (existsSync4(
|
|
3093
|
+
for (const path7 of [configPath, globalPath]) {
|
|
3094
|
+
if (existsSync4(path7)) {
|
|
2893
3095
|
try {
|
|
2894
|
-
const content = readFileSync2(
|
|
3096
|
+
const content = readFileSync2(path7, "utf-8");
|
|
2895
3097
|
const servers = adapter.parse(content);
|
|
2896
3098
|
if (servers.length > 0) {
|
|
2897
3099
|
mcpConfigs[target] = servers;
|
|
@@ -3792,6 +3994,85 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3792
3994
|
};
|
|
3793
3995
|
}
|
|
3794
3996
|
);
|
|
3997
|
+
let dashboardRunning = false;
|
|
3998
|
+
server.registerTool(
|
|
3999
|
+
"memorix_dashboard",
|
|
4000
|
+
{
|
|
4001
|
+
title: "Launch Dashboard",
|
|
4002
|
+
description: "Launch the Memorix Web Dashboard in the browser. Shows knowledge graph, observations, retention scores, and project stats in a visual interface.",
|
|
4003
|
+
inputSchema: {
|
|
4004
|
+
port: z.number().optional().describe("Port to run the dashboard on (default: 3210)")
|
|
4005
|
+
}
|
|
4006
|
+
},
|
|
4007
|
+
async ({ port: dashboardPort }) => {
|
|
4008
|
+
const portNum = dashboardPort || 3210;
|
|
4009
|
+
const url = `http://localhost:${portNum}`;
|
|
4010
|
+
if (dashboardRunning) {
|
|
4011
|
+
const { exec: exec2 } = await import("child_process");
|
|
4012
|
+
const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
4013
|
+
exec2(cmd, () => {
|
|
4014
|
+
});
|
|
4015
|
+
return {
|
|
4016
|
+
content: [{ type: "text", text: `Dashboard is already running at ${url}. Opened in browser.` }]
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
try {
|
|
4020
|
+
const pathMod = await import("path");
|
|
4021
|
+
const fsMod = await import("fs");
|
|
4022
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
4023
|
+
const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4024
|
+
const candidates = [
|
|
4025
|
+
pathMod.default.join(__dirname, "..", "dashboard", "static"),
|
|
4026
|
+
pathMod.default.join(__dirname, "dashboard", "static"),
|
|
4027
|
+
pathMod.default.join(pathMod.default.dirname(fileURLToPath2(import.meta.url)), "..", "dashboard", "static"),
|
|
4028
|
+
pathMod.default.join(pathMod.default.dirname(fileURLToPath2(import.meta.url)), "dashboard", "static")
|
|
4029
|
+
];
|
|
4030
|
+
for (const [i, c] of candidates.entries()) {
|
|
4031
|
+
const hasIndex = fsMod.existsSync(pathMod.default.join(c, "index.html"));
|
|
4032
|
+
console.error(`[memorix] candidate[${i}]: ${c} (has index.html: ${hasIndex})`);
|
|
4033
|
+
}
|
|
4034
|
+
let staticDir = candidates[0];
|
|
4035
|
+
for (const c of candidates) {
|
|
4036
|
+
if (fsMod.existsSync(pathMod.default.join(c, "index.html"))) {
|
|
4037
|
+
staticDir = c;
|
|
4038
|
+
break;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
|
|
4042
|
+
startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false).then(() => {
|
|
4043
|
+
dashboardRunning = true;
|
|
4044
|
+
}).catch((err) => {
|
|
4045
|
+
console.error("[memorix] Dashboard error:", err);
|
|
4046
|
+
dashboardRunning = false;
|
|
4047
|
+
});
|
|
4048
|
+
await new Promise((resolve2) => setTimeout(resolve2, 800));
|
|
4049
|
+
dashboardRunning = true;
|
|
4050
|
+
const { exec: execCmd } = await import("child_process");
|
|
4051
|
+
const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
4052
|
+
execCmd(openCmd, () => {
|
|
4053
|
+
});
|
|
4054
|
+
return {
|
|
4055
|
+
content: [{
|
|
4056
|
+
type: "text",
|
|
4057
|
+
text: [
|
|
4058
|
+
`Memorix Dashboard started!`,
|
|
4059
|
+
``,
|
|
4060
|
+
`URL: ${url}`,
|
|
4061
|
+
`Project: ${project.name} (${project.id})`,
|
|
4062
|
+
`Static: ${staticDir}`,
|
|
4063
|
+
``,
|
|
4064
|
+
`The dashboard has been opened in your default browser.`,
|
|
4065
|
+
`It shows your knowledge graph, observations, retention scores, and project stats.`
|
|
4066
|
+
].join("\n")
|
|
4067
|
+
}]
|
|
4068
|
+
};
|
|
4069
|
+
} catch (err) {
|
|
4070
|
+
return {
|
|
4071
|
+
content: [{ type: "text", text: `Failed to start dashboard: ${err instanceof Error ? err.message : String(err)}` }]
|
|
4072
|
+
};
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
);
|
|
3795
4076
|
return { server, graphManager, projectId: project.id };
|
|
3796
4077
|
}
|
|
3797
4078
|
|