@kage-core/kage-graph-mcp 1.0.0 → 1.1.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/README.md +294 -0
- package/dist/cli.js +726 -0
- package/dist/daemon.js +230 -0
- package/dist/index.js +659 -21
- package/dist/kernel.js +4177 -0
- package/dist/registry/index.js +373 -0
- package/package.json +17 -8
- package/viewer/app.js +1000 -0
- package/viewer/index.html +136 -0
- package/viewer/styles.css +530 -0
- package/index.ts +0 -254
- package/tsconfig.json +0 -14
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readDaemonStatus = readDaemonStatus;
|
|
4
|
+
exports.daemonDoctor = daemonDoctor;
|
|
5
|
+
exports.stopDaemon = stopDaemon;
|
|
6
|
+
exports.startDaemon = startDaemon;
|
|
7
|
+
exports.startViewer = startViewer;
|
|
8
|
+
const node_http_1 = require("node:http");
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const kernel_js_1 = require("./kernel.js");
|
|
12
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
13
|
+
const DEFAULT_REST_PORT = 3111;
|
|
14
|
+
const DEFAULT_VIEWER_PORT = 3113;
|
|
15
|
+
function daemonDir(projectDir) {
|
|
16
|
+
return (0, node_path_1.join)(projectDir, ".agent_memory", "daemon");
|
|
17
|
+
}
|
|
18
|
+
function isInside(parent, child) {
|
|
19
|
+
const a = (0, node_path_1.resolve)(parent);
|
|
20
|
+
const b = (0, node_path_1.resolve)(child);
|
|
21
|
+
return b === a || b.startsWith(`${a}/`);
|
|
22
|
+
}
|
|
23
|
+
function contentType(filePath) {
|
|
24
|
+
const ext = (0, node_path_1.extname)(filePath);
|
|
25
|
+
if (ext === ".html")
|
|
26
|
+
return "text/html; charset=utf-8";
|
|
27
|
+
if (ext === ".js")
|
|
28
|
+
return "application/javascript; charset=utf-8";
|
|
29
|
+
if (ext === ".css")
|
|
30
|
+
return "text/css; charset=utf-8";
|
|
31
|
+
if (ext === ".json")
|
|
32
|
+
return "application/json; charset=utf-8";
|
|
33
|
+
if (ext === ".md")
|
|
34
|
+
return "text/markdown; charset=utf-8";
|
|
35
|
+
return "application/octet-stream";
|
|
36
|
+
}
|
|
37
|
+
function statusPath(projectDir) {
|
|
38
|
+
return (0, node_path_1.join)(daemonDir(projectDir), "status.json");
|
|
39
|
+
}
|
|
40
|
+
function json(res, status, value) {
|
|
41
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
42
|
+
res.end(JSON.stringify(value, null, 2));
|
|
43
|
+
}
|
|
44
|
+
function notFound(res) {
|
|
45
|
+
json(res, 404, { ok: false, error: "not_found" });
|
|
46
|
+
}
|
|
47
|
+
function readBody(req) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const chunks = [];
|
|
50
|
+
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
51
|
+
req.on("end", () => {
|
|
52
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
53
|
+
if (!text) {
|
|
54
|
+
resolve({});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
resolve(JSON.parse(text));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
reject(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
req.on("error", reject);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function readDaemonStatus(projectDir) {
|
|
68
|
+
const path = statusPath(projectDir);
|
|
69
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
70
|
+
return null;
|
|
71
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
72
|
+
}
|
|
73
|
+
function daemonDoctor(projectDir) {
|
|
74
|
+
const status = readDaemonStatus(projectDir) ?? undefined;
|
|
75
|
+
let running = false;
|
|
76
|
+
if (status) {
|
|
77
|
+
try {
|
|
78
|
+
process.kill(status.pid, 0);
|
|
79
|
+
running = true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
running = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const restPort = status?.rest_port ?? DEFAULT_REST_PORT;
|
|
86
|
+
return {
|
|
87
|
+
configured: Boolean(status),
|
|
88
|
+
running,
|
|
89
|
+
status,
|
|
90
|
+
endpoints: [
|
|
91
|
+
`GET http://${DEFAULT_HOST}:${restPort}/health`,
|
|
92
|
+
`POST http://${DEFAULT_HOST}:${restPort}/kage/recall`,
|
|
93
|
+
`POST http://${DEFAULT_HOST}:${restPort}/kage/observe`,
|
|
94
|
+
`POST http://${DEFAULT_HOST}:${restPort}/kage/distill`,
|
|
95
|
+
`GET http://${DEFAULT_HOST}:${restPort}/kage/metrics`,
|
|
96
|
+
`GET http://${DEFAULT_HOST}:${restPort}/kage/quality`,
|
|
97
|
+
`GET http://${DEFAULT_HOST}:${restPort}/kage/benchmark`,
|
|
98
|
+
],
|
|
99
|
+
warnings: running ? [] : ["Daemon is not running. Start it with `kage daemon start --project <repo>`."],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function stopDaemon(projectDir) {
|
|
103
|
+
const status = readDaemonStatus(projectDir);
|
|
104
|
+
if (!status)
|
|
105
|
+
return { ok: false, message: "No daemon status file found." };
|
|
106
|
+
try {
|
|
107
|
+
process.kill(status.pid, "SIGTERM");
|
|
108
|
+
return { ok: true, message: `Sent SIGTERM to Kage daemon pid ${status.pid}.`, status };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error), status };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function startDaemon(projectDir, options = {}) {
|
|
115
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
116
|
+
const restPort = options.restPort ?? DEFAULT_REST_PORT;
|
|
117
|
+
const viewerPort = options.viewerPort ?? DEFAULT_VIEWER_PORT;
|
|
118
|
+
(0, node_fs_1.mkdirSync)(daemonDir(projectDir), { recursive: true });
|
|
119
|
+
const status = {
|
|
120
|
+
ok: true,
|
|
121
|
+
project_dir: projectDir,
|
|
122
|
+
pid: process.pid,
|
|
123
|
+
host,
|
|
124
|
+
rest_port: restPort,
|
|
125
|
+
viewer_port: viewerPort,
|
|
126
|
+
started_at: new Date().toISOString(),
|
|
127
|
+
status_path: statusPath(projectDir),
|
|
128
|
+
};
|
|
129
|
+
(0, node_fs_1.writeFileSync)(status.status_path, JSON.stringify(status, null, 2), "utf8");
|
|
130
|
+
const server = (0, node_http_1.createServer)(async (req, res) => {
|
|
131
|
+
const url = new URL(req.url ?? "/", `http://${host}:${restPort}`);
|
|
132
|
+
try {
|
|
133
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
134
|
+
json(res, 200, { ok: true, name: "kage-daemon", project_dir: projectDir, pid: process.pid });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (req.method === "GET" && url.pathname === "/kage/status") {
|
|
138
|
+
json(res, 200, status);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (req.method === "GET" && url.pathname === "/kage/metrics") {
|
|
142
|
+
json(res, 200, (0, kernel_js_1.kageMetrics)(projectDir));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (req.method === "GET" && url.pathname === "/kage/quality") {
|
|
146
|
+
json(res, 200, (0, kernel_js_1.qualityReport)(projectDir));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (req.method === "GET" && url.pathname === "/kage/benchmark") {
|
|
150
|
+
json(res, 200, (0, kernel_js_1.benchmarkProject)(projectDir));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (req.method === "POST" && url.pathname === "/kage/recall") {
|
|
154
|
+
const body = await readBody(req);
|
|
155
|
+
json(res, 200, (0, kernel_js_1.recall)(projectDir, String(body.query ?? ""), Number(body.limit ?? 5), Boolean(body.explain)));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (req.method === "POST" && url.pathname === "/kage/observe") {
|
|
159
|
+
const body = await readBody(req);
|
|
160
|
+
json(res, 200, (0, kernel_js_1.observe)(projectDir, body));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (req.method === "POST" && url.pathname === "/kage/distill") {
|
|
164
|
+
const body = await readBody(req);
|
|
165
|
+
json(res, 200, (0, kernel_js_1.distillSession)(projectDir, String(body.session_id ?? body.session ?? "default")));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
notFound(res);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
json(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
await new Promise((resolve) => server.listen(restPort, host, resolve));
|
|
175
|
+
console.log(`Kage daemon listening on http://${host}:${restPort}`);
|
|
176
|
+
console.log(`Project: ${projectDir}`);
|
|
177
|
+
console.log(`Status: ${status.status_path}`);
|
|
178
|
+
process.on("SIGTERM", () => {
|
|
179
|
+
server.close(() => process.exit(0));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async function startViewer(projectDir, options = {}) {
|
|
183
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
184
|
+
const port = options.port ?? DEFAULT_VIEWER_PORT;
|
|
185
|
+
const viewerDir = (0, node_path_1.resolve)(__dirname, "..", "viewer");
|
|
186
|
+
const projectRoot = (0, node_path_1.resolve)(projectDir);
|
|
187
|
+
const graphPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "graph", "graph.json");
|
|
188
|
+
const codePath = (0, node_path_1.join)(projectRoot, ".agent_memory", "code_graph", "graph.json");
|
|
189
|
+
const metricsPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "metrics.json");
|
|
190
|
+
const reviewPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "review", "memory-review.md");
|
|
191
|
+
const pendingDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "pending");
|
|
192
|
+
const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}`;
|
|
193
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
194
|
+
const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
195
|
+
let filePath = null;
|
|
196
|
+
if (requestUrl.pathname === "/" || requestUrl.pathname === "/viewer") {
|
|
197
|
+
filePath = (0, node_path_1.join)(viewerDir, "index.html");
|
|
198
|
+
}
|
|
199
|
+
else if (requestUrl.pathname.startsWith("/viewer/")) {
|
|
200
|
+
filePath = (0, node_path_1.join)(viewerDir, (0, node_path_1.normalize)(requestUrl.pathname.replace(/^\/viewer\//, "")));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const decoded = decodeURIComponent(requestUrl.pathname);
|
|
204
|
+
filePath = (0, node_path_1.resolve)(decoded);
|
|
205
|
+
if (!isInside(projectRoot, filePath))
|
|
206
|
+
filePath = null;
|
|
207
|
+
}
|
|
208
|
+
if (!filePath || (!isInside(viewerDir, filePath) && !isInside(projectRoot, filePath)) || !(0, node_fs_1.existsSync)(filePath)) {
|
|
209
|
+
json(res, 404, { ok: false, error: "not_found" });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const stat = (0, node_fs_1.statSync)(filePath);
|
|
213
|
+
if (stat.isDirectory()) {
|
|
214
|
+
const files = (0, node_fs_1.readdirSync)(filePath)
|
|
215
|
+
.filter((name) => name.endsWith(".json") || name.endsWith(".md"))
|
|
216
|
+
.sort()
|
|
217
|
+
.map((name) => ({ name, path: (0, node_path_1.join)(filePath, name) }));
|
|
218
|
+
json(res, 200, { ok: true, files });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
res.writeHead(200, { "content-type": contentType(filePath) });
|
|
222
|
+
res.end((0, node_fs_1.readFileSync)(filePath));
|
|
223
|
+
});
|
|
224
|
+
await new Promise((resolveListen) => server.listen(port, host, resolveListen));
|
|
225
|
+
console.log(`Kage viewer listening on ${url}`);
|
|
226
|
+
process.on("SIGTERM", () => {
|
|
227
|
+
server.close(() => process.exit(0));
|
|
228
|
+
});
|
|
229
|
+
return { ok: true, project_dir: projectRoot, host, port, url };
|
|
230
|
+
}
|