grepmax 0.23.0 → 0.25.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +25 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +16 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +76 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/impact.js +33 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +494 -162
- package/dist/commands/peek.js +16 -5
- package/dist/commands/repair.js +54 -120
- package/dist/commands/search-output.js +30 -9
- package/dist/commands/search-run.js +75 -47
- package/dist/commands/search-skeletons.js +28 -18
- package/dist/commands/search.js +45 -49
- package/dist/commands/serve.js +415 -373
- package/dist/commands/setup.js +2 -2
- package/dist/commands/similar.js +5 -5
- package/dist/commands/skeleton.js +67 -41
- package/dist/commands/status.js +5 -2
- package/dist/commands/summarize.js +6 -0
- package/dist/commands/surprises.js +150 -0
- package/dist/commands/watch.js +102 -38
- package/dist/config.js +3 -0
- package/dist/eval-surprising-connections.js +191 -0
- package/dist/index.js +2 -0
- package/dist/lib/analysis/surprising-connections.js +600 -0
- package/dist/lib/daemon/daemon.js +1101 -379
- package/dist/lib/daemon/ipc-handler.js +122 -13
- package/dist/lib/daemon/mlx-server-manager.js +268 -125
- package/dist/lib/daemon/process-manager.js +7 -4
- package/dist/lib/daemon/search-handler.js +23 -9
- package/dist/lib/daemon/watcher-manager.js +353 -110
- package/dist/lib/graph/impact-rollup.js +202 -0
- package/dist/lib/graph/impact.js +15 -1
- package/dist/lib/help/agent-cheatsheet.js +1 -0
- package/dist/lib/index/batch-processor.js +231 -67
- package/dist/lib/index/embedding-generation.js +109 -0
- package/dist/lib/index/embedding-status.js +23 -0
- package/dist/lib/index/file-policy.js +273 -0
- package/dist/lib/index/ignore-patterns.js +4 -0
- package/dist/lib/index/index-config.js +18 -4
- package/dist/lib/index/syncer.js +256 -79
- package/dist/lib/index/walker.js +66 -86
- package/dist/lib/index/watcher-batch.js +9 -0
- package/dist/lib/index/watcher.js +129 -3
- package/dist/lib/llm/server.js +6 -0
- package/dist/lib/search/searcher.js +30 -19
- package/dist/lib/skeleton/skeletonizer.js +118 -0
- package/dist/lib/skeleton/summary-formatter.js +8 -1
- package/dist/lib/store/store-lease.js +473 -0
- package/dist/lib/store/vector-db.js +302 -57
- package/dist/lib/utils/blocked-roots.js +63 -0
- package/dist/lib/utils/cross-project.js +7 -3
- package/dist/lib/utils/daemon-client.js +24 -1
- package/dist/lib/utils/daemon-launcher.js +38 -13
- package/dist/lib/utils/doctor-status.js +76 -0
- package/dist/lib/utils/file-utils.js +74 -4
- package/dist/lib/utils/keyed-mutex.js +101 -0
- package/dist/lib/utils/logger.js +57 -1
- package/dist/lib/utils/mlx-hf-cache.js +114 -0
- package/dist/lib/utils/operation-coordinator.js +146 -0
- package/dist/lib/utils/path-containment.js +106 -0
- package/dist/lib/utils/process.js +44 -0
- package/dist/lib/utils/project-registry.js +351 -3
- package/dist/lib/utils/scope-filter.js +3 -9
- package/dist/lib/utils/stale-hint.js +12 -19
- package/dist/lib/utils/watcher-launcher.js +5 -1
- package/dist/lib/utils/watcher-store.js +2 -2
- package/dist/lib/workers/colbert-math.js +15 -12
- package/dist/lib/workers/embeddings/colbert.js +3 -2
- package/dist/lib/workers/embeddings/granite.js +4 -3
- package/dist/lib/workers/embeddings/mlx-client.js +59 -16
- package/dist/lib/workers/orchestrator.js +39 -8
- package/dist/lib/workers/pool.js +150 -83
- package/dist/lib/workers/process-child.js +11 -2
- package/dist/lib/workers/serialized-handler.js +10 -0
- package/dist/lib/workers/worker.js +13 -4
- package/mlx-embed-server/server.py +21 -3
- package/mlx-embed-server/summarizer.py +8 -0
- package/package.json +6 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
package/dist/commands/serve.js
CHANGED
|
@@ -41,77 +41,345 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
45
|
+
var t = {};
|
|
46
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
47
|
+
t[p] = s[p];
|
|
48
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
49
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
50
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
51
|
+
t[p[i]] = s[p[i]];
|
|
52
|
+
}
|
|
53
|
+
return t;
|
|
54
|
+
};
|
|
44
55
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.serve = void 0;
|
|
56
|
+
exports.serve = exports.MAX_HTTP_SEARCH_LIMIT = void 0;
|
|
57
|
+
exports.waitForChildSpawn = waitForChildSpawn;
|
|
58
|
+
exports.serveRootError = serveRootError;
|
|
59
|
+
exports.createServeHttpServer = createServeHttpServer;
|
|
60
|
+
exports.listenOnLoopback = listenOnLoopback;
|
|
46
61
|
const node_child_process_1 = require("node:child_process");
|
|
47
62
|
const fs = __importStar(require("node:fs"));
|
|
48
63
|
const http = __importStar(require("node:http"));
|
|
49
64
|
const path = __importStar(require("node:path"));
|
|
50
65
|
const commander_1 = require("commander");
|
|
51
66
|
const config_1 = require("../config");
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
55
|
-
const syncer_1 = require("../lib/index/syncer");
|
|
56
|
-
const watcher_1 = require("../lib/index/watcher");
|
|
57
|
-
const searcher_1 = require("../lib/search/searcher");
|
|
58
|
-
const setup_helpers_1 = require("../lib/setup/setup-helpers");
|
|
59
|
-
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
60
|
-
const vector_db_1 = require("../lib/store/vector-db");
|
|
67
|
+
const blocked_roots_1 = require("../lib/utils/blocked-roots");
|
|
68
|
+
const daemon_client_1 = require("../lib/utils/daemon-client");
|
|
61
69
|
const exit_1 = require("../lib/utils/exit");
|
|
62
70
|
const log_rotate_1 = require("../lib/utils/log-rotate");
|
|
71
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
63
72
|
const project_root_1 = require("../lib/utils/project-root");
|
|
73
|
+
const scope_filter_1 = require("../lib/utils/scope-filter");
|
|
64
74
|
const server_registry_1 = require("../lib/utils/server-registry");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
76
|
+
const MAX_BODY_BYTES = 1000000;
|
|
77
|
+
exports.MAX_HTTP_SEARCH_LIMIT = 50;
|
|
78
|
+
class HttpRequestError extends Error {
|
|
79
|
+
constructor(status, code) {
|
|
80
|
+
super(code);
|
|
81
|
+
this.status = status;
|
|
82
|
+
this.code = code;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function waitForChildSpawn(child) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const onSpawn = () => {
|
|
88
|
+
child.off("error", onError);
|
|
89
|
+
child.on("error", (error) => {
|
|
90
|
+
console.error(`Background server process error: ${error.message}`);
|
|
91
|
+
});
|
|
92
|
+
if (child.pid === undefined) {
|
|
93
|
+
reject(new Error("Background process started without a PID"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
resolve(child.pid);
|
|
97
|
+
};
|
|
98
|
+
const onError = (error) => {
|
|
99
|
+
child.off("spawn", onSpawn);
|
|
100
|
+
reject(error);
|
|
101
|
+
};
|
|
102
|
+
child.once("spawn", onSpawn);
|
|
103
|
+
child.once("error", onError);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function writeJson(res, status, body) {
|
|
107
|
+
if (res.writableEnded)
|
|
108
|
+
return;
|
|
109
|
+
res.statusCode = status;
|
|
110
|
+
res.setHeader("Content-Type", "application/json");
|
|
111
|
+
res.end(JSON.stringify(body));
|
|
112
|
+
}
|
|
113
|
+
function readJsonBody(req) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
let size = 0;
|
|
117
|
+
let settled = false;
|
|
118
|
+
const fail = (err) => {
|
|
119
|
+
if (settled)
|
|
120
|
+
return;
|
|
121
|
+
settled = true;
|
|
122
|
+
reject(err);
|
|
123
|
+
};
|
|
124
|
+
req.on("data", (chunk) => {
|
|
125
|
+
if (settled)
|
|
126
|
+
return;
|
|
127
|
+
size += chunk.length;
|
|
128
|
+
if (size > MAX_BODY_BYTES) {
|
|
129
|
+
fail(new HttpRequestError(413, "payload_too_large"));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
chunks.push(chunk);
|
|
71
133
|
});
|
|
72
|
-
req.on("error",
|
|
73
|
-
req.on("
|
|
74
|
-
|
|
75
|
-
|
|
134
|
+
req.on("error", fail);
|
|
135
|
+
req.on("aborted", () => fail(new HttpRequestError(499, "aborted")));
|
|
136
|
+
req.on("end", () => {
|
|
137
|
+
if (settled)
|
|
138
|
+
return;
|
|
139
|
+
settled = true;
|
|
140
|
+
try {
|
|
141
|
+
const value = chunks.length
|
|
142
|
+
? JSON.parse(Buffer.concat(chunks).toString("utf8"))
|
|
143
|
+
: {};
|
|
144
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
145
|
+
reject(new HttpRequestError(400, "invalid_json_object"));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve(value);
|
|
149
|
+
}
|
|
150
|
+
catch (_a) {
|
|
151
|
+
reject(new HttpRequestError(400, "invalid_json"));
|
|
152
|
+
}
|
|
76
153
|
});
|
|
77
154
|
});
|
|
78
155
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
156
|
+
function daemonErrorStatus(error) {
|
|
157
|
+
if (error === "busy" || error === "rebuilding")
|
|
158
|
+
return 503;
|
|
159
|
+
if (error === "project not registered" || error === "project not watched") {
|
|
160
|
+
return 404;
|
|
161
|
+
}
|
|
162
|
+
if (error === "invalid limit" || error === "invalid path")
|
|
163
|
+
return 400;
|
|
164
|
+
return 500;
|
|
165
|
+
}
|
|
166
|
+
function serveRootError(root) {
|
|
167
|
+
if (!(0, blocked_roots_1.isBlockedProjectRoot)(root))
|
|
168
|
+
return undefined;
|
|
169
|
+
return (`Refusing to serve ${root}: this path is blocked from indexing.\n` +
|
|
170
|
+
`(Blocked: ${blocked_roots_1.BLOCKED_ROOTS_DESCRIPTION}.)\n` +
|
|
171
|
+
"Pick a specific project subdirectory instead.");
|
|
172
|
+
}
|
|
173
|
+
function optionalString(body, key) {
|
|
174
|
+
const value = body[key];
|
|
175
|
+
if (value === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
if (typeof value !== "string")
|
|
178
|
+
throw new HttpRequestError(400, `invalid_${key}`);
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function optionalStringArray(body, key) {
|
|
182
|
+
const value = body[key];
|
|
183
|
+
if (value === undefined)
|
|
184
|
+
return undefined;
|
|
185
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
186
|
+
throw new HttpRequestError(400, `invalid_${key}`);
|
|
92
187
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
function createServeHttpServer(projectRoot, deps) {
|
|
191
|
+
const active = new Set();
|
|
192
|
+
const server = http.createServer((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
var _a, _b, _c, _d;
|
|
194
|
+
(_a = deps.onActivity) === null || _a === void 0 ? void 0 : _a.call(deps);
|
|
195
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
196
|
+
writeJson(res, 200, { status: "ok" });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const ac = new AbortController();
|
|
200
|
+
active.add(ac);
|
|
201
|
+
const abort = () => ac.abort();
|
|
202
|
+
req.once("aborted", abort);
|
|
203
|
+
res.once("close", () => {
|
|
204
|
+
if (!res.writableFinished)
|
|
205
|
+
abort();
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
if (req.method === "GET" && req.url === "/stats") {
|
|
209
|
+
const response = yield deps.stats(ac.signal);
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
writeJson(res, daemonErrorStatus(response.error), {
|
|
212
|
+
error: (_b = response.error) !== null && _b !== void 0 ? _b : "stats_failed",
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const { ok: _ok } = response, stats = __rest(response, ["ok"]);
|
|
217
|
+
writeJson(res, 200, stats);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (req.method === "POST" && req.url === "/search") {
|
|
221
|
+
const body = yield readJsonBody(req);
|
|
222
|
+
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
223
|
+
if (!query)
|
|
224
|
+
throw new HttpRequestError(400, "invalid_query");
|
|
225
|
+
const limit = body.limit === undefined ? 10 : body.limit;
|
|
226
|
+
if (typeof limit !== "number" ||
|
|
227
|
+
!Number.isInteger(limit) ||
|
|
228
|
+
limit < 1 ||
|
|
229
|
+
limit > exports.MAX_HTTP_SEARCH_LIMIT) {
|
|
230
|
+
throw new HttpRequestError(400, "invalid_limit");
|
|
231
|
+
}
|
|
232
|
+
const requestedPath = optionalString(body, "path");
|
|
233
|
+
const inValues = optionalStringArray(body, "in");
|
|
234
|
+
const excludeValues = optionalStringArray(body, "exclude");
|
|
235
|
+
const scope = (0, scope_filter_1.resolveScope)({
|
|
236
|
+
projectRoot,
|
|
237
|
+
in: (inValues === null || inValues === void 0 ? void 0 : inValues.length)
|
|
238
|
+
? inValues
|
|
239
|
+
: requestedPath
|
|
240
|
+
? [requestedPath]
|
|
241
|
+
: undefined,
|
|
242
|
+
exclude: excludeValues,
|
|
243
|
+
});
|
|
244
|
+
if (requestedPath) {
|
|
245
|
+
(0, path_containment_1.resolveContainedPath)(projectRoot, requestedPath, {
|
|
246
|
+
verifyExistingTarget: true,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const filters = {};
|
|
250
|
+
const file = optionalString(body, "file");
|
|
251
|
+
const language = optionalString(body, "lang");
|
|
252
|
+
const role = optionalString(body, "role");
|
|
253
|
+
if (file)
|
|
254
|
+
filters.file = file;
|
|
255
|
+
if (language)
|
|
256
|
+
filters.language = language;
|
|
257
|
+
if (role)
|
|
258
|
+
filters.role = role;
|
|
259
|
+
if (scope.inPrefixes.length > 0)
|
|
260
|
+
filters.inPrefixes = scope.inPrefixes;
|
|
261
|
+
if (scope.excludePrefixes.length > 0) {
|
|
262
|
+
filters.excludePrefixes = scope.excludePrefixes;
|
|
263
|
+
}
|
|
264
|
+
const response = yield deps.search({
|
|
265
|
+
query,
|
|
266
|
+
limit,
|
|
267
|
+
pathPrefix: scope.pathPrefix,
|
|
268
|
+
filters: Object.keys(filters).length ? filters : undefined,
|
|
269
|
+
}, ac.signal);
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
writeJson(res, daemonErrorStatus(response.error), {
|
|
272
|
+
error: (_c = response.error) !== null && _c !== void 0 ? _c : "search_failed",
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
writeJson(res, 200, { results: (_d = response.data) !== null && _d !== void 0 ? _d : [] });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
writeJson(res, 404, { error: "not_found" });
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
if (ac.signal.aborted)
|
|
283
|
+
return;
|
|
284
|
+
if (err instanceof path_containment_1.PathContainmentError) {
|
|
285
|
+
writeJson(res, 400, { error: "path_outside_project" });
|
|
286
|
+
}
|
|
287
|
+
else if (err instanceof HttpRequestError) {
|
|
288
|
+
writeJson(res, err.status, { error: err.code });
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
writeJson(res, 500, {
|
|
292
|
+
error: err instanceof Error ? err.message : "internal_error",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
active.delete(ac);
|
|
298
|
+
}
|
|
299
|
+
}));
|
|
300
|
+
server.setTimeout(60000);
|
|
301
|
+
return {
|
|
302
|
+
server,
|
|
303
|
+
abortActive: () => {
|
|
304
|
+
for (const ac of active)
|
|
305
|
+
ac.abort();
|
|
306
|
+
active.clear();
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function listenOnLoopback(server_1, startPort_1) {
|
|
311
|
+
return __awaiter(this, arguments, void 0, function* (server, startPort, attempts = 10) {
|
|
312
|
+
let port = startPort;
|
|
313
|
+
for (let attempt = 0; attempt < attempts; attempt++, port++) {
|
|
314
|
+
try {
|
|
315
|
+
yield new Promise((resolve, reject) => {
|
|
316
|
+
const onError = (err) => {
|
|
317
|
+
server.off("listening", onListening);
|
|
318
|
+
reject(err);
|
|
319
|
+
};
|
|
320
|
+
const onListening = () => {
|
|
321
|
+
server.off("error", onError);
|
|
322
|
+
resolve();
|
|
323
|
+
};
|
|
324
|
+
server.once("error", onError);
|
|
325
|
+
server.once("listening", onListening);
|
|
326
|
+
server.listen(port, LOOPBACK_HOST);
|
|
327
|
+
});
|
|
328
|
+
const address = server.address();
|
|
329
|
+
return typeof address === "object" && address ? address.port : port;
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
if (err.code !== "EADDRINUSE")
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
throw new Error(`Could not find an open port between ${startPort} and ${startPort + attempts - 1}`);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function closeServer(server) {
|
|
340
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
341
|
+
if (!server.listening)
|
|
342
|
+
return;
|
|
343
|
+
yield new Promise((resolve) => {
|
|
344
|
+
const timeout = setTimeout(() => {
|
|
345
|
+
var _a;
|
|
346
|
+
(_a = server.closeAllConnections) === null || _a === void 0 ? void 0 : _a.call(server);
|
|
347
|
+
resolve();
|
|
348
|
+
}, 5000);
|
|
349
|
+
server.close(() => {
|
|
350
|
+
clearTimeout(timeout);
|
|
351
|
+
resolve();
|
|
352
|
+
});
|
|
353
|
+
});
|
|
98
354
|
});
|
|
99
|
-
child.unref();
|
|
100
|
-
return child;
|
|
101
355
|
}
|
|
102
356
|
exports.serve = new commander_1.Command("serve")
|
|
103
|
-
.description("HTTP search
|
|
357
|
+
.description("Loopback HTTP search adapter backed by the gmax daemon")
|
|
104
358
|
.option("-p, --port <port>", "Port to listen on", process.env.GMAX_PORT || "4444")
|
|
105
359
|
.option("-b, --background", "Run in background", false)
|
|
106
|
-
.option("--cpu", "
|
|
360
|
+
.option("--cpu", "Deprecated: configure daemon CPU mode with gmax config", false)
|
|
107
361
|
.option("--no-idle-timeout", "Disable the 30-minute idle shutdown", false)
|
|
108
362
|
.action((_args, cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
109
363
|
var _a, _b;
|
|
110
364
|
const options = cmd.optsWithGlobals();
|
|
111
|
-
|
|
112
|
-
const startPort = port;
|
|
365
|
+
const port = Number.parseInt(options.port, 10);
|
|
113
366
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
114
|
-
|
|
367
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
368
|
+
console.error(`Invalid port: ${options.port}`);
|
|
369
|
+
process.exitCode = 1;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const blockedRootError = serveRootError(projectRoot);
|
|
373
|
+
if (blockedRootError) {
|
|
374
|
+
console.error(blockedRootError);
|
|
375
|
+
process.exitCode = 1;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (options.cpu) {
|
|
379
|
+
console.error("--cpu is now configured on the daemon. Run `gmax config --embed-mode cpu`, then start serve again.");
|
|
380
|
+
process.exitCode = 1;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
115
383
|
const existing = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
116
384
|
if (existing && (0, server_registry_1.isProcessRunning)(existing.pid)) {
|
|
117
385
|
console.log(`Server already running for ${projectRoot} (PID: ${existing.pid}, Port: ${existing.port})`);
|
|
@@ -127,325 +395,101 @@ exports.serve = new commander_1.Command("serve")
|
|
|
127
395
|
const logFile = path.join(config_1.PATHS.logsDir, `server-${safeName}.log`);
|
|
128
396
|
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
129
397
|
const err = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
141
|
-
const projectName = path.basename(projectRoot);
|
|
142
|
-
// Propagate project root to worker processes
|
|
143
|
-
process.env.GMAX_PROJECT_ROOT = projectRoot;
|
|
144
|
-
// Determine embed mode: --cpu flag overrides, then config, then default
|
|
145
|
-
// Default to GPU on Apple Silicon, CPU everywhere else
|
|
146
|
-
const isAppleSilicon = process.arch === "arm64" && process.platform === "darwin";
|
|
147
|
-
const indexConfig = (0, index_config_1.readIndexConfig)(paths.configPath);
|
|
148
|
-
const useGpu = options.cpu
|
|
149
|
-
? false
|
|
150
|
-
: ((_b = indexConfig === null || indexConfig === void 0 ? void 0 : indexConfig.embedMode) !== null && _b !== void 0 ? _b : (isAppleSilicon ? "gpu" : "cpu")) === "gpu";
|
|
151
|
-
const mlxModel = indexConfig === null || indexConfig === void 0 ? void 0 : indexConfig.mlxModel;
|
|
152
|
-
// MLX GPU embed server — started when GPU mode is active.
|
|
153
|
-
let mlxChild = null;
|
|
154
|
-
if (!useGpu) {
|
|
155
|
-
console.log(`[serve:${projectName}] CPU-only mode`);
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
const mlxUp = yield isMlxServerUp();
|
|
159
|
-
if (mlxUp) {
|
|
160
|
-
console.log(`[serve:${projectName}] MLX GPU server already running`);
|
|
398
|
+
try {
|
|
399
|
+
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
|
|
400
|
+
detached: true,
|
|
401
|
+
stdio: ["ignore", out, err],
|
|
402
|
+
cwd: process.cwd(),
|
|
403
|
+
env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
|
|
404
|
+
});
|
|
405
|
+
const childPid = yield waitForChildSpawn(child);
|
|
406
|
+
child.unref();
|
|
407
|
+
console.log(`Started background server (PID: ${childPid})`);
|
|
161
408
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log(`[serve] Starting MLX GPU embed server (PID: ${mlxChild.pid})${mlxModel ? ` [${mlxModel}]` : ""}`);
|
|
166
|
-
let ready = false;
|
|
167
|
-
for (let i = 0; i < 30; i++) {
|
|
168
|
-
yield new Promise((r) => setTimeout(r, 1000));
|
|
169
|
-
if (yield isMlxServerUp()) {
|
|
170
|
-
console.log(`[serve:${projectName}] MLX GPU server ready`);
|
|
171
|
-
ready = true;
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!ready) {
|
|
176
|
-
console.error(`[serve:${projectName}] MLX GPU server failed to start. Run with --cpu to use CPU embeddings.`);
|
|
177
|
-
process.exitCode = 1;
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
console.error(`[serve:${projectName}] MLX server not found. Run with --cpu to use CPU embeddings.`);
|
|
183
|
-
process.exitCode = 1;
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
console.error(`Failed to start background server: ${err instanceof Error ? err.message : String(err)}`);
|
|
411
|
+
process.exitCode = 1;
|
|
186
412
|
}
|
|
413
|
+
finally {
|
|
414
|
+
fs.closeSync(out);
|
|
415
|
+
fs.closeSync(err);
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
187
418
|
}
|
|
419
|
+
const startupAc = new AbortController();
|
|
420
|
+
let runtime = null;
|
|
421
|
+
let idleTimer = null;
|
|
422
|
+
let registered = false;
|
|
423
|
+
let shutdownPromise = null;
|
|
424
|
+
const shutdown = (code = 0) => {
|
|
425
|
+
if (shutdownPromise)
|
|
426
|
+
return shutdownPromise;
|
|
427
|
+
shutdownPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
|
|
428
|
+
startupAc.abort();
|
|
429
|
+
if (idleTimer)
|
|
430
|
+
clearInterval(idleTimer);
|
|
431
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.abortActive();
|
|
432
|
+
if (runtime)
|
|
433
|
+
yield closeServer(runtime.server);
|
|
434
|
+
if (registered)
|
|
435
|
+
(0, server_registry_1.unregisterServer)(process.pid);
|
|
436
|
+
yield (0, exit_1.gracefulExit)(code);
|
|
437
|
+
}))();
|
|
438
|
+
return shutdownPromise;
|
|
439
|
+
};
|
|
440
|
+
process.once("SIGINT", () => void shutdown());
|
|
441
|
+
process.once("SIGTERM", () => void shutdown());
|
|
188
442
|
try {
|
|
189
|
-
yield (0,
|
|
190
|
-
|
|
191
|
-
// Initial sync is self-contained (creates+closes its own VectorDB+MetaCache).
|
|
192
|
-
if (!process.env.GMAX_BACKGROUND) {
|
|
193
|
-
const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Indexing before starting server...");
|
|
194
|
-
try {
|
|
195
|
-
yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
196
|
-
spinner.succeed("Initial index ready. Starting server...");
|
|
197
|
-
}
|
|
198
|
-
catch (e) {
|
|
199
|
-
spinner.fail("Indexing failed");
|
|
200
|
-
throw e;
|
|
201
|
-
}
|
|
443
|
+
if (!(yield (0, daemon_client_1.ensureDaemonRunning)())) {
|
|
444
|
+
throw new Error("Could not start the gmax daemon");
|
|
202
445
|
}
|
|
203
|
-
|
|
204
|
-
|
|
446
|
+
const ensured = yield (0, daemon_client_1.sendStreamingCommand)({ cmd: "ensure-project", root: projectRoot }, () => { }, { signal: startupAc.signal });
|
|
447
|
+
if (!ensured.ok) {
|
|
448
|
+
throw new Error(String((_b = ensured.error) !== null && _b !== void 0 ? _b : "project setup failed"));
|
|
205
449
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Start live file watcher
|
|
211
|
-
let fileWatcher = yield (0, watcher_1.startWatcher)({
|
|
212
|
-
projectRoot,
|
|
213
|
-
vectorDb,
|
|
214
|
-
metaCache,
|
|
215
|
-
dataDir: paths.dataDir,
|
|
216
|
-
onReindex: (files, durationMs) => {
|
|
217
|
-
console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(durationMs / 1000).toFixed(1)}s)`);
|
|
450
|
+
let lastActivity = Date.now();
|
|
451
|
+
runtime = createServeHttpServer(projectRoot, {
|
|
452
|
+
onActivity: () => {
|
|
453
|
+
lastActivity = Date.now();
|
|
218
454
|
},
|
|
455
|
+
search: (input, signal) => (0, daemon_client_1.sendDaemonCommand)({
|
|
456
|
+
cmd: "search",
|
|
457
|
+
projectRoot,
|
|
458
|
+
query: input.query,
|
|
459
|
+
limit: input.limit,
|
|
460
|
+
pathPrefix: input.pathPrefix,
|
|
461
|
+
filters: input.filters,
|
|
462
|
+
rerank: true,
|
|
463
|
+
}, { timeoutMs: 65000, signal }),
|
|
464
|
+
stats: (signal) => (0, daemon_client_1.sendDaemonCommand)({ cmd: "project-stats", root: projectRoot }, { timeoutMs: 10000, signal }),
|
|
219
465
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
466
|
+
const actualPort = yield listenOnLoopback(runtime.server, port);
|
|
467
|
+
(0, server_registry_1.registerServer)({
|
|
468
|
+
pid: process.pid,
|
|
469
|
+
port: actualPort,
|
|
470
|
+
projectRoot,
|
|
471
|
+
startTime: Date.now(),
|
|
472
|
+
});
|
|
473
|
+
registered = true;
|
|
224
474
|
if (options.idleTimeout) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.log(`[serve:${projectName}] Idle timeout reached, shutting down.`);
|
|
229
|
-
clearInterval(idleCheck);
|
|
230
|
-
process.kill(process.pid, "SIGTERM");
|
|
475
|
+
idleTimer = setInterval(() => {
|
|
476
|
+
if (Date.now() - lastActivity > 30 * 60 * 1000) {
|
|
477
|
+
void shutdown();
|
|
231
478
|
}
|
|
232
479
|
}, 60000);
|
|
233
|
-
|
|
480
|
+
idleTimer.unref();
|
|
234
481
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (req.method === "GET" && req.url === "/health") {
|
|
239
|
-
lastActivity = Date.now();
|
|
240
|
-
res.statusCode = 200;
|
|
241
|
-
res.setHeader("Content-Type", "application/json");
|
|
242
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (req.method === "GET" && req.url === "/stats") {
|
|
246
|
-
try {
|
|
247
|
-
const dbStats = yield vectorDb.getStats();
|
|
248
|
-
const cfg = (0, index_config_1.readIndexConfig)(paths.configPath);
|
|
249
|
-
const stats = {
|
|
250
|
-
files: dbStats.chunks > 0
|
|
251
|
-
? yield vectorDb.getDistinctFileCount()
|
|
252
|
-
: 0,
|
|
253
|
-
chunks: dbStats.chunks,
|
|
254
|
-
totalBytes: dbStats.totalBytes,
|
|
255
|
-
vectorDim: (_a = cfg === null || cfg === void 0 ? void 0 : cfg.vectorDim) !== null && _a !== void 0 ? _a : null,
|
|
256
|
-
embedMode: (_b = cfg === null || cfg === void 0 ? void 0 : cfg.embedMode) !== null && _b !== void 0 ? _b : (isAppleSilicon ? "gpu" : "cpu"),
|
|
257
|
-
model: (_c = cfg === null || cfg === void 0 ? void 0 : cfg.embedModel) !== null && _c !== void 0 ? _c : null,
|
|
258
|
-
mlxModel: (_d = cfg === null || cfg === void 0 ? void 0 : cfg.mlxModel) !== null && _d !== void 0 ? _d : null,
|
|
259
|
-
indexedAt: (_e = cfg === null || cfg === void 0 ? void 0 : cfg.indexedAt) !== null && _e !== void 0 ? _e : null,
|
|
260
|
-
watching: fileWatcher !== null,
|
|
261
|
-
};
|
|
262
|
-
res.statusCode = 200;
|
|
263
|
-
res.setHeader("Content-Type", "application/json");
|
|
264
|
-
res.end(JSON.stringify(stats));
|
|
265
|
-
}
|
|
266
|
-
catch (err) {
|
|
267
|
-
res.statusCode = 500;
|
|
268
|
-
res.setHeader("Content-Type", "application/json");
|
|
269
|
-
res.end(JSON.stringify({
|
|
270
|
-
error: (err === null || err === void 0 ? void 0 : err.message) || "stats_failed",
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (req.method === "POST" && req.url === "/search") {
|
|
276
|
-
lastActivity = Date.now();
|
|
277
|
-
const chunks = [];
|
|
278
|
-
let totalSize = 0;
|
|
279
|
-
let aborted = false;
|
|
280
|
-
req.on("data", (chunk) => {
|
|
281
|
-
if (aborted)
|
|
282
|
-
return;
|
|
283
|
-
totalSize += chunk.length;
|
|
284
|
-
if (totalSize > 1000000) {
|
|
285
|
-
aborted = true;
|
|
286
|
-
res.statusCode = 413;
|
|
287
|
-
res.setHeader("Content-Type", "application/json");
|
|
288
|
-
res.end(JSON.stringify({ error: "payload_too_large" }));
|
|
289
|
-
req.destroy();
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
chunks.push(chunk);
|
|
293
|
-
});
|
|
294
|
-
req.on("end", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
295
|
-
if (aborted)
|
|
296
|
-
return;
|
|
297
|
-
try {
|
|
298
|
-
const body = chunks.length
|
|
299
|
-
? JSON.parse(Buffer.concat(chunks).toString("utf-8"))
|
|
300
|
-
: {};
|
|
301
|
-
const query = typeof body.query === "string" ? body.query : "";
|
|
302
|
-
const limit = typeof body.limit === "number" ? body.limit : 10;
|
|
303
|
-
// Use absolute path prefix for search filtering
|
|
304
|
-
let searchPath = `${projectRoot}/`;
|
|
305
|
-
if (typeof body.path === "string") {
|
|
306
|
-
const resolvedPath = path.resolve(projectRoot, body.path);
|
|
307
|
-
searchPath = resolvedPath.endsWith("/")
|
|
308
|
-
? resolvedPath
|
|
309
|
-
: `${resolvedPath}/`;
|
|
310
|
-
}
|
|
311
|
-
// Add AbortController for cancellation
|
|
312
|
-
const ac = new AbortController();
|
|
313
|
-
req.on("close", () => {
|
|
314
|
-
if (req.complete)
|
|
315
|
-
return;
|
|
316
|
-
ac.abort();
|
|
317
|
-
});
|
|
318
|
-
res.on("close", () => {
|
|
319
|
-
if (res.writableFinished)
|
|
320
|
-
return;
|
|
321
|
-
ac.abort();
|
|
322
|
-
});
|
|
323
|
-
const result = yield searcher.search(query, limit, { rerank: true }, undefined, searchPath, undefined, // intent
|
|
324
|
-
ac.signal);
|
|
325
|
-
if (ac.signal.aborted) {
|
|
326
|
-
// Request was cancelled, don't write response if possible
|
|
327
|
-
// (Though usually 'close' means the socket is gone anyway)
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
res.statusCode = 200;
|
|
331
|
-
res.setHeader("Content-Type", "application/json");
|
|
332
|
-
res.end(JSON.stringify({ results: result.data }));
|
|
333
|
-
}
|
|
334
|
-
catch (err) {
|
|
335
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
336
|
-
// Request cancelled
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
res.statusCode = 500;
|
|
340
|
-
res.setHeader("Content-Type", "application/json");
|
|
341
|
-
res.end(JSON.stringify({
|
|
342
|
-
error: (err === null || err === void 0 ? void 0 : err.message) || "search_failed",
|
|
343
|
-
}));
|
|
344
|
-
}
|
|
345
|
-
}));
|
|
346
|
-
req.on("error", (err) => {
|
|
347
|
-
console.error(`[serve:${projectName}] request error:`, err);
|
|
348
|
-
aborted = true;
|
|
349
|
-
});
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
res.statusCode = 404;
|
|
353
|
-
res.end();
|
|
354
|
-
}
|
|
355
|
-
catch (err) {
|
|
356
|
-
console.error(`[serve:${projectName}] request handler error:`, err);
|
|
357
|
-
if (!res.headersSent) {
|
|
358
|
-
res.statusCode = 500;
|
|
359
|
-
res.setHeader("Content-Type", "application/json");
|
|
360
|
-
res.end(JSON.stringify({ error: "internal_error" }));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}));
|
|
364
|
-
server.on("error", (e) => {
|
|
365
|
-
if (e.code === "EADDRINUSE") {
|
|
366
|
-
const nextPort = port + 1;
|
|
367
|
-
if (nextPort < startPort + 10) {
|
|
368
|
-
console.log(`Port ${port} in use, retrying with ${nextPort}...`);
|
|
369
|
-
port = nextPort;
|
|
370
|
-
server.close(() => {
|
|
371
|
-
server.listen(port);
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
console.error(`Could not find an open port between ${startPort} and ${startPort + 9}`);
|
|
376
|
-
}
|
|
377
|
-
console.error(`[serve:${projectName}] server error:`, e);
|
|
378
|
-
// Ensure we exit if server fails to start
|
|
379
|
-
process.exit(1);
|
|
482
|
+
runtime.server.on("error", (err) => {
|
|
483
|
+
console.error(`[serve:${path.basename(projectRoot)}] server error:`, err);
|
|
484
|
+
void shutdown(1);
|
|
380
485
|
});
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
385
|
-
if (!process.env.GMAX_BACKGROUND) {
|
|
386
|
-
console.log(`gmax server listening on http://localhost:${actualPort} (${projectRoot})`);
|
|
387
|
-
}
|
|
388
|
-
(0, server_registry_1.registerServer)({
|
|
389
|
-
pid: process.pid,
|
|
390
|
-
port: actualPort,
|
|
391
|
-
projectRoot,
|
|
392
|
-
startTime: Date.now(),
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
396
|
-
(0, server_registry_1.unregisterServer)(process.pid);
|
|
397
|
-
// Stop file watcher first
|
|
398
|
-
if (fileWatcher) {
|
|
399
|
-
try {
|
|
400
|
-
yield fileWatcher.close();
|
|
401
|
-
}
|
|
402
|
-
catch (_a) { }
|
|
403
|
-
fileWatcher = null;
|
|
404
|
-
}
|
|
405
|
-
// Stop MLX server if we started it
|
|
406
|
-
if (mlxChild === null || mlxChild === void 0 ? void 0 : mlxChild.pid) {
|
|
407
|
-
try {
|
|
408
|
-
process.kill(mlxChild.pid, "SIGTERM");
|
|
409
|
-
}
|
|
410
|
-
catch (_b) { }
|
|
411
|
-
}
|
|
412
|
-
// Properly await server close
|
|
413
|
-
yield new Promise((resolve, reject) => {
|
|
414
|
-
server.close((err) => {
|
|
415
|
-
if (err) {
|
|
416
|
-
console.error("Error closing server:", err);
|
|
417
|
-
reject(err);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
resolve();
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
// Timeout fallback in case close hangs
|
|
424
|
-
setTimeout(resolve, 5000);
|
|
425
|
-
});
|
|
426
|
-
// Clean close of owned resources
|
|
427
|
-
try {
|
|
428
|
-
yield metaCache.close();
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
console.error("Error closing meta cache:", e);
|
|
432
|
-
}
|
|
433
|
-
try {
|
|
434
|
-
yield vectorDb.close();
|
|
435
|
-
}
|
|
436
|
-
catch (e) {
|
|
437
|
-
console.error("Error closing vector DB:", e);
|
|
438
|
-
}
|
|
439
|
-
yield (0, exit_1.gracefulExit)();
|
|
440
|
-
});
|
|
441
|
-
process.on("SIGINT", shutdown);
|
|
442
|
-
process.on("SIGTERM", shutdown);
|
|
486
|
+
if (!process.env.GMAX_BACKGROUND) {
|
|
487
|
+
console.log(`gmax server listening on http://${LOOPBACK_HOST}:${actualPort} (${projectRoot})`);
|
|
488
|
+
}
|
|
443
489
|
}
|
|
444
|
-
catch (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
process.exitCode = 1;
|
|
448
|
-
yield (0, exit_1.gracefulExit)(1);
|
|
490
|
+
catch (err) {
|
|
491
|
+
console.error(`Serve failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
492
|
+
yield shutdown(1);
|
|
449
493
|
}
|
|
450
494
|
}));
|
|
451
495
|
exports.serve
|
|
@@ -458,9 +502,9 @@ exports.serve
|
|
|
458
502
|
return;
|
|
459
503
|
}
|
|
460
504
|
console.log("Running servers:");
|
|
461
|
-
servers
|
|
462
|
-
console.log(`- PID: ${
|
|
463
|
-
}
|
|
505
|
+
for (const server of servers) {
|
|
506
|
+
console.log(`- PID: ${server.pid} | Port: ${server.port} | Root: ${server.projectRoot}`);
|
|
507
|
+
}
|
|
464
508
|
});
|
|
465
509
|
exports.serve
|
|
466
510
|
.command("stop")
|
|
@@ -471,31 +515,29 @@ exports.serve
|
|
|
471
515
|
if (options.all) {
|
|
472
516
|
const servers = (0, server_registry_1.listServers)();
|
|
473
517
|
let count = 0;
|
|
474
|
-
servers
|
|
518
|
+
for (const server of servers) {
|
|
475
519
|
try {
|
|
476
|
-
process.kill(
|
|
520
|
+
process.kill(server.pid, "SIGTERM");
|
|
477
521
|
count++;
|
|
478
522
|
}
|
|
479
|
-
catch (
|
|
480
|
-
console.error(`Failed to stop PID ${
|
|
523
|
+
catch (err) {
|
|
524
|
+
console.error(`Failed to stop PID ${server.pid}:`, err);
|
|
481
525
|
}
|
|
482
|
-
}
|
|
526
|
+
}
|
|
483
527
|
console.log(`Stopped ${count} servers.`);
|
|
528
|
+
return;
|
|
484
529
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
console.log(`No server found for ${projectRoot}`);
|
|
499
|
-
}
|
|
530
|
+
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
531
|
+
const server = (0, server_registry_1.getServerForProject)(projectRoot);
|
|
532
|
+
if (!server) {
|
|
533
|
+
console.log(`No server found for ${projectRoot}`);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
process.kill(server.pid, "SIGTERM");
|
|
538
|
+
console.log(`Stopped server for ${projectRoot} (PID: ${server.pid})`);
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
console.error(`Failed to stop PID ${server.pid}:`, err);
|
|
500
542
|
}
|
|
501
543
|
});
|