grepmax 0.8.2 → 0.9.1
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/commands/add.js +1 -1
- package/dist/commands/index.js +24 -20
- package/dist/commands/mcp.js +11 -4
- package/dist/commands/remove.js +13 -6
- package/dist/commands/search.js +1 -1
- package/dist/commands/watch.js +85 -12
- package/dist/config.js +1 -0
- package/dist/lib/daemon/daemon.js +314 -0
- package/dist/lib/daemon/ipc-handler.js +53 -0
- package/dist/lib/index/batch-processor.js +286 -0
- package/dist/lib/index/watcher.js +8 -228
- package/dist/lib/utils/daemon-client.js +108 -0
- package/dist/lib/utils/daemon-launcher.js +59 -0
- package/dist/lib/utils/watcher-launcher.js +78 -36
- package/dist/lib/utils/watcher-store.js +42 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +7 -2
package/dist/commands/add.js
CHANGED
|
@@ -154,7 +154,7 @@ Examples:
|
|
|
154
154
|
throw e;
|
|
155
155
|
}
|
|
156
156
|
// Start watcher
|
|
157
|
-
const launched = (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
157
|
+
const launched = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
158
158
|
if (launched.ok) {
|
|
159
159
|
console.log(`Watcher started (PID: ${launched.pid})`);
|
|
160
160
|
}
|
package/dist/commands/index.js
CHANGED
|
@@ -105,25 +105,29 @@ Examples:
|
|
|
105
105
|
// Ensure grammars are present before indexing (silent if already exist)
|
|
106
106
|
yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
|
|
107
107
|
// Stop any watcher that covers this project — it holds the shared lock
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
console.log(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
let restartWatcher = false;
|
|
109
|
+
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
110
|
+
if (yield isDaemonRunning()) {
|
|
111
|
+
console.log("Pausing daemon watcher for reindex...");
|
|
112
|
+
yield sendDaemonCommand({ cmd: "unwatch", root: projectRoot });
|
|
113
|
+
restartWatcher = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
|
|
117
|
+
if (watcher) {
|
|
118
|
+
console.log(`Stopping watcher (PID: ${watcher.pid}) for ${path.basename(watcher.projectRoot)}...`);
|
|
119
|
+
try {
|
|
120
|
+
process.kill(watcher.pid, "SIGTERM");
|
|
121
|
+
}
|
|
122
|
+
catch (_b) { }
|
|
123
|
+
for (let i = 0; i < 50; i++) {
|
|
124
|
+
if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
|
|
125
|
+
break;
|
|
126
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
127
|
+
}
|
|
128
|
+
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
129
|
+
restartWatcher = true;
|
|
121
130
|
}
|
|
122
|
-
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
123
|
-
restartWatcher = {
|
|
124
|
-
pid: watcher.pid,
|
|
125
|
-
projectRoot: watcher.projectRoot,
|
|
126
|
-
};
|
|
127
131
|
}
|
|
128
132
|
const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Indexing...", { verbose: options.verbose });
|
|
129
133
|
try {
|
|
@@ -168,9 +172,9 @@ Examples:
|
|
|
168
172
|
finally {
|
|
169
173
|
// Restart the watcher if we stopped one
|
|
170
174
|
if (restartWatcher) {
|
|
171
|
-
const launched = (0, watcher_launcher_1.launchWatcher)(
|
|
175
|
+
const launched = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
172
176
|
if (launched.ok) {
|
|
173
|
-
console.log(`Restarted watcher for ${path.basename(
|
|
177
|
+
console.log(`Restarted watcher for ${path.basename(projectRoot)} (PID: ${launched.pid})`);
|
|
174
178
|
}
|
|
175
179
|
else if (launched.reason === "spawn-failed") {
|
|
176
180
|
console.warn(`[index] ${launched.message}`);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -353,10 +353,17 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
353
353
|
}
|
|
354
354
|
// --- Background watcher ---
|
|
355
355
|
function ensureWatcher() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
356
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
357
|
+
try {
|
|
358
|
+
const result = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
359
|
+
if (result.ok && !result.reused) {
|
|
360
|
+
console.log(`[MCP] Started background watcher for ${projectRoot} (PID: ${result.pid})`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
console.error("[MCP] Watcher startup failed:", err);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
360
367
|
}
|
|
361
368
|
// --- Tool handlers ---
|
|
362
369
|
function handleSemanticSearch(args_1) {
|
package/dist/commands/remove.js
CHANGED
|
@@ -99,12 +99,19 @@ Examples:
|
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
// Stop any watcher
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
console.log(
|
|
106
|
-
yield (
|
|
107
|
-
|
|
102
|
+
// Stop any watcher — try daemon IPC first, fall back to direct kill
|
|
103
|
+
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
104
|
+
if (yield isDaemonRunning()) {
|
|
105
|
+
console.log("Unwatching via daemon...");
|
|
106
|
+
yield sendDaemonCommand({ cmd: "unwatch", root: projectRoot });
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
|
|
110
|
+
if (watcher) {
|
|
111
|
+
console.log(`Stopping watcher (PID: ${watcher.pid})...`);
|
|
112
|
+
yield (0, process_1.killProcess)(watcher.pid);
|
|
113
|
+
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
116
|
// Delete vectors from LanceDB
|
|
110
117
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
package/dist/commands/search.js
CHANGED
|
@@ -529,7 +529,7 @@ Examples:
|
|
|
529
529
|
// Ensure a watcher is running for live reindexing
|
|
530
530
|
if (!process.env.VITEST && !((_d = process.env.NODE_ENV) === null || _d === void 0 ? void 0 : _d.includes("test"))) {
|
|
531
531
|
const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
|
|
532
|
-
const launched = launchWatcher(projectRoot);
|
|
532
|
+
const launched = yield launchWatcher(projectRoot);
|
|
533
533
|
if (!launched.ok && launched.reason === "spawn-failed") {
|
|
534
534
|
console.warn(`[search] ${launched.message}`);
|
|
535
535
|
}
|
package/dist/commands/watch.js
CHANGED
|
@@ -64,10 +64,53 @@ const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
|
|
|
64
64
|
exports.watch = new commander_1.Command("watch")
|
|
65
65
|
.description("Start background file watcher for live reindexing")
|
|
66
66
|
.option("-b, --background", "Run watcher in background and exit")
|
|
67
|
+
.option("-d, --daemon", "Run as centralized daemon watching all projects")
|
|
67
68
|
.option("-p, --path <dir>", "Directory to watch (defaults to project root)")
|
|
68
69
|
.option("--no-idle-timeout", "Disable the 30-minute idle shutdown")
|
|
69
70
|
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
70
71
|
var _a, _b;
|
|
72
|
+
// --- Daemon mode ---
|
|
73
|
+
if (options.daemon) {
|
|
74
|
+
if (options.path) {
|
|
75
|
+
console.error("Error: --daemon watches all projects globally; --path is not allowed with --daemon");
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (options.background) {
|
|
80
|
+
const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
|
|
81
|
+
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
82
|
+
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--daemon"], {
|
|
83
|
+
detached: true,
|
|
84
|
+
stdio: ["ignore", out, out],
|
|
85
|
+
cwd: process.cwd(),
|
|
86
|
+
env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
|
|
87
|
+
});
|
|
88
|
+
child.unref();
|
|
89
|
+
console.log(`Daemon started (PID: ${child.pid}, log: ${logFile})`);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
// Daemon foreground
|
|
93
|
+
(0, watcher_store_1.migrateFromJson)();
|
|
94
|
+
const { Daemon } = yield Promise.resolve().then(() => __importStar(require("../lib/daemon/daemon")));
|
|
95
|
+
const daemon = new Daemon();
|
|
96
|
+
try {
|
|
97
|
+
yield daemon.start();
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
101
|
+
if (code === "EADDRINUSE") {
|
|
102
|
+
// Another daemon already running — not an error
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
console.error("[daemon] Failed to start:", err);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
process.on("SIGINT", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
110
|
+
process.on("SIGTERM", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// --- Per-project mode ---
|
|
71
114
|
const projectRoot = options.path
|
|
72
115
|
? path.resolve(options.path)
|
|
73
116
|
: (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
@@ -96,7 +139,7 @@ exports.watch = new commander_1.Command("watch")
|
|
|
96
139
|
console.log(`Watcher started for ${projectName} (PID: ${child.pid}, log: ${logFile})`);
|
|
97
140
|
process.exit(0);
|
|
98
141
|
}
|
|
99
|
-
// ---
|
|
142
|
+
// --- Per-project foreground mode ---
|
|
100
143
|
// Migrate legacy watchers.json to LMDB on first use
|
|
101
144
|
(0, watcher_store_1.migrateFromJson)();
|
|
102
145
|
// Watcher requires project to be registered
|
|
@@ -195,16 +238,30 @@ exports.watch
|
|
|
195
238
|
.command("status")
|
|
196
239
|
.description("Show running watchers")
|
|
197
240
|
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
241
|
+
const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
242
|
+
const resp = yield sendDaemonCommand({ cmd: "status" });
|
|
243
|
+
if (resp.ok) {
|
|
244
|
+
const projects = resp.projects;
|
|
245
|
+
const uptime = Math.floor(resp.uptime / 60);
|
|
246
|
+
console.log(`Daemon (PID: ${resp.pid}, uptime: ${uptime}m):`);
|
|
247
|
+
for (const p of projects) {
|
|
248
|
+
console.log(` - ${p.root} [${p.status}]`);
|
|
249
|
+
}
|
|
250
|
+
if (projects.length === 0) {
|
|
251
|
+
console.log(" (no projects)");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Also show any per-project watchers not managed by daemon
|
|
255
|
+
const watchers = (0, watcher_store_1.listWatchers)().filter((w) => !resp.ok || w.pid !== resp.pid);
|
|
256
|
+
if (watchers.length > 0) {
|
|
257
|
+
console.log("Per-project watchers:");
|
|
258
|
+
for (const w of watchers) {
|
|
259
|
+
const age = Math.floor((Date.now() - w.startTime) / 60000);
|
|
260
|
+
console.log(` - PID: ${w.pid} | Root: ${w.projectRoot} | Running: ${age}m`);
|
|
261
|
+
}
|
|
203
262
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const age = Math.floor((Date.now() - w.startTime) / 60000);
|
|
207
|
-
console.log(`- PID: ${w.pid} | Root: ${w.projectRoot} | Running: ${age}m`);
|
|
263
|
+
if (!resp.ok && watchers.length === 0) {
|
|
264
|
+
console.log("No running watchers.");
|
|
208
265
|
}
|
|
209
266
|
yield (0, exit_1.gracefulExit)();
|
|
210
267
|
}));
|
|
@@ -214,6 +271,14 @@ exports.watch
|
|
|
214
271
|
.option("--all", "Stop all running watchers")
|
|
215
272
|
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
273
|
var _a;
|
|
274
|
+
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
275
|
+
let stoppedDaemon = false;
|
|
276
|
+
// Try shutting down daemon first
|
|
277
|
+
if (yield isDaemonRunning()) {
|
|
278
|
+
yield sendDaemonCommand({ cmd: "shutdown" });
|
|
279
|
+
console.log("Daemon stopped.");
|
|
280
|
+
stoppedDaemon = true;
|
|
281
|
+
}
|
|
217
282
|
if (options.all) {
|
|
218
283
|
const watchers = (0, watcher_store_1.listWatchers)();
|
|
219
284
|
for (const w of watchers) {
|
|
@@ -223,14 +288,22 @@ exports.watch
|
|
|
223
288
|
console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
|
|
224
289
|
}
|
|
225
290
|
}
|
|
226
|
-
|
|
291
|
+
if (watchers.length > 0) {
|
|
292
|
+
console.log(`Stopped ${watchers.length} per-project watcher(s).`);
|
|
293
|
+
}
|
|
294
|
+
else if (!stoppedDaemon) {
|
|
295
|
+
console.log("No running watchers.");
|
|
296
|
+
}
|
|
227
297
|
yield (0, exit_1.gracefulExit)();
|
|
228
298
|
return;
|
|
229
299
|
}
|
|
300
|
+
// Single project stop
|
|
230
301
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
231
302
|
const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
|
|
232
303
|
if (!watcher) {
|
|
233
|
-
|
|
304
|
+
if (!stoppedDaemon) {
|
|
305
|
+
console.log("No watcher running for this project.");
|
|
306
|
+
}
|
|
234
307
|
yield (0, exit_1.gracefulExit)();
|
|
235
308
|
return;
|
|
236
309
|
}
|
package/dist/config.js
CHANGED
|
@@ -94,6 +94,7 @@ exports.PATHS = {
|
|
|
94
94
|
models: path.join(GLOBAL_ROOT, "models"),
|
|
95
95
|
grammars: path.join(GLOBAL_ROOT, "grammars"),
|
|
96
96
|
logsDir: path.join(GLOBAL_ROOT, "logs"),
|
|
97
|
+
daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
|
|
97
98
|
// Centralized index storage — one database for all indexed directories
|
|
98
99
|
lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
|
|
99
100
|
cacheDir: path.join(GLOBAL_ROOT, "cache"),
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.Daemon = void 0;
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const net = __importStar(require("node:net"));
|
|
48
|
+
const path = __importStar(require("node:path"));
|
|
49
|
+
const chokidar_1 = require("chokidar");
|
|
50
|
+
const config_1 = require("../../config");
|
|
51
|
+
const batch_processor_1 = require("../index/batch-processor");
|
|
52
|
+
const watcher_1 = require("../index/watcher");
|
|
53
|
+
const meta_cache_1 = require("../store/meta-cache");
|
|
54
|
+
const vector_db_1 = require("../store/vector-db");
|
|
55
|
+
const process_1 = require("../utils/process");
|
|
56
|
+
const project_registry_1 = require("../utils/project-registry");
|
|
57
|
+
const watcher_store_1 = require("../utils/watcher-store");
|
|
58
|
+
const ipc_handler_1 = require("./ipc-handler");
|
|
59
|
+
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
60
|
+
const HEARTBEAT_INTERVAL_MS = 60 * 1000;
|
|
61
|
+
class Daemon {
|
|
62
|
+
constructor() {
|
|
63
|
+
this.watcher = null;
|
|
64
|
+
this.processors = new Map();
|
|
65
|
+
this.vectorDb = null;
|
|
66
|
+
this.metaCache = null;
|
|
67
|
+
this.server = null;
|
|
68
|
+
this.lastActivity = Date.now();
|
|
69
|
+
this.startTime = Date.now();
|
|
70
|
+
this.heartbeatInterval = null;
|
|
71
|
+
this.idleInterval = null;
|
|
72
|
+
this.shuttingDown = false;
|
|
73
|
+
// Sorted longest-first for prefix matching
|
|
74
|
+
this.sortedRoots = [];
|
|
75
|
+
// Guard against concurrent watchProject/unwatchProject
|
|
76
|
+
this.pendingOps = new Set();
|
|
77
|
+
}
|
|
78
|
+
start() {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
// 1. Kill existing per-project watchers
|
|
81
|
+
const existing = (0, watcher_store_1.listWatchers)();
|
|
82
|
+
for (const w of existing) {
|
|
83
|
+
console.log(`[daemon] Taking over from per-project watcher (PID: ${w.pid}, ${path.basename(w.projectRoot)})`);
|
|
84
|
+
yield (0, process_1.killProcess)(w.pid);
|
|
85
|
+
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
86
|
+
}
|
|
87
|
+
// 2. Stale socket cleanup
|
|
88
|
+
try {
|
|
89
|
+
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
90
|
+
}
|
|
91
|
+
catch (_a) { }
|
|
92
|
+
// 3. Open shared resources
|
|
93
|
+
try {
|
|
94
|
+
fs.mkdirSync(config_1.PATHS.cacheDir, { recursive: true });
|
|
95
|
+
fs.mkdirSync(config_1.PATHS.lancedbDir, { recursive: true });
|
|
96
|
+
this.vectorDb = new vector_db_1.VectorDB(config_1.PATHS.lancedbDir);
|
|
97
|
+
this.metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error("[daemon] Failed to open shared resources:", err);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
// 4. Register daemon (only after resources are open)
|
|
104
|
+
(0, watcher_store_1.registerDaemon)(process.pid);
|
|
105
|
+
// 5. Load registered projects and create processors
|
|
106
|
+
const projects = (0, project_registry_1.listProjects)().filter((p) => p.status === "indexed");
|
|
107
|
+
const initialRoots = [];
|
|
108
|
+
for (const p of projects) {
|
|
109
|
+
this.addProcessor(p.root);
|
|
110
|
+
initialRoots.push(p.root);
|
|
111
|
+
}
|
|
112
|
+
// 6. Create chokidar with all initial roots
|
|
113
|
+
// Daemon always uses polling — watching multiple large project trees
|
|
114
|
+
// with native fs.watch can exhaust file descriptors even on macOS.
|
|
115
|
+
// Polling at 5s intervals is lightweight and reliable for all platforms.
|
|
116
|
+
this.watcher = (0, chokidar_1.watch)(initialRoots, {
|
|
117
|
+
ignored: watcher_1.WATCHER_IGNORE_PATTERNS,
|
|
118
|
+
ignoreInitial: true,
|
|
119
|
+
persistent: true,
|
|
120
|
+
usePolling: true,
|
|
121
|
+
interval: 5000,
|
|
122
|
+
binaryInterval: 10000,
|
|
123
|
+
});
|
|
124
|
+
this.watcher.on("add", (p) => this.routeEvent("change", p));
|
|
125
|
+
this.watcher.on("change", (p) => this.routeEvent("change", p));
|
|
126
|
+
this.watcher.on("unlink", (p) => this.routeEvent("unlink", p));
|
|
127
|
+
this.watcher.on("error", (err) => {
|
|
128
|
+
console.error("[daemon] Watcher error:", err);
|
|
129
|
+
});
|
|
130
|
+
// 7. Heartbeat
|
|
131
|
+
this.heartbeatInterval = setInterval(() => {
|
|
132
|
+
(0, watcher_store_1.heartbeat)(process.pid);
|
|
133
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
134
|
+
// 8. Idle timeout
|
|
135
|
+
this.idleInterval = setInterval(() => {
|
|
136
|
+
if (Date.now() - this.lastActivity > IDLE_TIMEOUT_MS) {
|
|
137
|
+
console.log("[daemon] Idle for 30 minutes, shutting down");
|
|
138
|
+
this.shutdown();
|
|
139
|
+
}
|
|
140
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
141
|
+
// 9. Socket server
|
|
142
|
+
this.server = net.createServer((conn) => {
|
|
143
|
+
let buf = "";
|
|
144
|
+
conn.on("data", (chunk) => {
|
|
145
|
+
buf += chunk.toString();
|
|
146
|
+
const nl = buf.indexOf("\n");
|
|
147
|
+
if (nl === -1)
|
|
148
|
+
return;
|
|
149
|
+
const line = buf.slice(0, nl);
|
|
150
|
+
buf = buf.slice(nl + 1);
|
|
151
|
+
let cmd;
|
|
152
|
+
try {
|
|
153
|
+
cmd = JSON.parse(line);
|
|
154
|
+
}
|
|
155
|
+
catch (_a) {
|
|
156
|
+
conn.write(`${JSON.stringify({ ok: false, error: "invalid JSON" })}\n`);
|
|
157
|
+
conn.end();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
(0, ipc_handler_1.handleCommand)(this, cmd).then((resp) => {
|
|
161
|
+
conn.write(`${JSON.stringify(resp)}\n`);
|
|
162
|
+
conn.end();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
conn.on("error", () => { }); // ignore client disconnect
|
|
166
|
+
});
|
|
167
|
+
yield new Promise((resolve, reject) => {
|
|
168
|
+
this.server.on("error", (err) => {
|
|
169
|
+
const code = err.code;
|
|
170
|
+
if (code === "EADDRINUSE") {
|
|
171
|
+
console.error("[daemon] Another daemon is already running");
|
|
172
|
+
reject(err);
|
|
173
|
+
}
|
|
174
|
+
else if (code === "EOPNOTSUPP") {
|
|
175
|
+
console.error("[daemon] Filesystem does not support Unix sockets");
|
|
176
|
+
process.exitCode = 2;
|
|
177
|
+
reject(err);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
reject(err);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
this.server.listen(config_1.PATHS.daemonSocket, () => resolve());
|
|
184
|
+
});
|
|
185
|
+
console.log(`[daemon] Started (PID: ${process.pid}, ${this.processors.size} projects)`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
watchProject(root) {
|
|
189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
if (this.processors.has(root) || this.pendingOps.has(root))
|
|
191
|
+
return;
|
|
192
|
+
if (!this.watcher)
|
|
193
|
+
return;
|
|
194
|
+
this.addProcessor(root);
|
|
195
|
+
this.watcher.add(root);
|
|
196
|
+
console.log(`[daemon] Watching ${root}`);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
addProcessor(root) {
|
|
200
|
+
if (this.processors.has(root))
|
|
201
|
+
return;
|
|
202
|
+
if (!this.vectorDb || !this.metaCache)
|
|
203
|
+
return;
|
|
204
|
+
this.pendingOps.add(root);
|
|
205
|
+
const processor = new batch_processor_1.ProjectBatchProcessor({
|
|
206
|
+
projectRoot: root,
|
|
207
|
+
vectorDb: this.vectorDb,
|
|
208
|
+
metaCache: this.metaCache,
|
|
209
|
+
dataDir: config_1.PATHS.globalRoot,
|
|
210
|
+
onReindex: (files, ms) => {
|
|
211
|
+
console.log(`[daemon:${path.basename(root)}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
|
|
212
|
+
},
|
|
213
|
+
onActivity: () => {
|
|
214
|
+
this.lastActivity = Date.now();
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
this.processors.set(root, processor);
|
|
218
|
+
this.rebuildSortedRoots();
|
|
219
|
+
(0, watcher_store_1.registerWatcher)({
|
|
220
|
+
pid: process.pid,
|
|
221
|
+
projectRoot: root,
|
|
222
|
+
startTime: Date.now(),
|
|
223
|
+
status: "watching",
|
|
224
|
+
lastHeartbeat: Date.now(),
|
|
225
|
+
});
|
|
226
|
+
this.pendingOps.delete(root);
|
|
227
|
+
}
|
|
228
|
+
unwatchProject(root) {
|
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
var _a;
|
|
231
|
+
const processor = this.processors.get(root);
|
|
232
|
+
if (!processor)
|
|
233
|
+
return;
|
|
234
|
+
yield processor.close();
|
|
235
|
+
(_a = this.watcher) === null || _a === void 0 ? void 0 : _a.unwatch(root);
|
|
236
|
+
this.processors.delete(root);
|
|
237
|
+
this.rebuildSortedRoots();
|
|
238
|
+
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
239
|
+
console.log(`[daemon] Unwatched ${root}`);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
listProjects() {
|
|
243
|
+
return [...this.processors.keys()].map((root) => ({
|
|
244
|
+
root,
|
|
245
|
+
status: "watching",
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
uptime() {
|
|
249
|
+
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
250
|
+
}
|
|
251
|
+
shutdown() {
|
|
252
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
253
|
+
var _a, _b, _c, _d;
|
|
254
|
+
if (this.shuttingDown)
|
|
255
|
+
return;
|
|
256
|
+
this.shuttingDown = true;
|
|
257
|
+
console.log("[daemon] Shutting down...");
|
|
258
|
+
if (this.heartbeatInterval)
|
|
259
|
+
clearInterval(this.heartbeatInterval);
|
|
260
|
+
if (this.idleInterval)
|
|
261
|
+
clearInterval(this.idleInterval);
|
|
262
|
+
// Close all processors
|
|
263
|
+
for (const processor of this.processors.values()) {
|
|
264
|
+
yield processor.close();
|
|
265
|
+
}
|
|
266
|
+
// Close chokidar
|
|
267
|
+
try {
|
|
268
|
+
yield ((_a = this.watcher) === null || _a === void 0 ? void 0 : _a.close());
|
|
269
|
+
}
|
|
270
|
+
catch (_e) { }
|
|
271
|
+
// Close server + socket
|
|
272
|
+
(_b = this.server) === null || _b === void 0 ? void 0 : _b.close();
|
|
273
|
+
try {
|
|
274
|
+
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
275
|
+
}
|
|
276
|
+
catch (_f) { }
|
|
277
|
+
// Unregister all
|
|
278
|
+
for (const root of this.processors.keys()) {
|
|
279
|
+
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
280
|
+
}
|
|
281
|
+
(0, watcher_store_1.unregisterDaemon)();
|
|
282
|
+
this.processors.clear();
|
|
283
|
+
// Close shared resources
|
|
284
|
+
try {
|
|
285
|
+
yield ((_c = this.metaCache) === null || _c === void 0 ? void 0 : _c.close());
|
|
286
|
+
}
|
|
287
|
+
catch (_g) { }
|
|
288
|
+
try {
|
|
289
|
+
yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
|
|
290
|
+
}
|
|
291
|
+
catch (_h) { }
|
|
292
|
+
console.log("[daemon] Shutdown complete");
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
routeEvent(event, absPath) {
|
|
296
|
+
const processor = this.findProcessor(absPath);
|
|
297
|
+
if (processor) {
|
|
298
|
+
processor.handleFileEvent(event, absPath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
findProcessor(absPath) {
|
|
302
|
+
// sortedRoots is longest-first, so first match is the most specific
|
|
303
|
+
for (const root of this.sortedRoots) {
|
|
304
|
+
if (absPath.startsWith(root) && (absPath.length === root.length || absPath[root.length] === "/")) {
|
|
305
|
+
return this.processors.get(root);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
rebuildSortedRoots() {
|
|
311
|
+
this.sortedRoots = [...this.processors.keys()].sort((a, b) => b.length - a.length);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
exports.Daemon = Daemon;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.handleCommand = handleCommand;
|
|
13
|
+
function handleCommand(daemon, cmd) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
try {
|
|
16
|
+
switch (cmd.cmd) {
|
|
17
|
+
case "ping":
|
|
18
|
+
return { ok: true, pid: process.pid, uptime: daemon.uptime() };
|
|
19
|
+
case "watch": {
|
|
20
|
+
const root = String(cmd.root || "");
|
|
21
|
+
if (!root)
|
|
22
|
+
return { ok: false, error: "missing root" };
|
|
23
|
+
yield daemon.watchProject(root);
|
|
24
|
+
return { ok: true, pid: process.pid };
|
|
25
|
+
}
|
|
26
|
+
case "unwatch": {
|
|
27
|
+
const root = String(cmd.root || "");
|
|
28
|
+
if (!root)
|
|
29
|
+
return { ok: false, error: "missing root" };
|
|
30
|
+
yield daemon.unwatchProject(root);
|
|
31
|
+
return { ok: true };
|
|
32
|
+
}
|
|
33
|
+
case "status":
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
pid: process.pid,
|
|
37
|
+
uptime: daemon.uptime(),
|
|
38
|
+
projects: daemon.listProjects(),
|
|
39
|
+
};
|
|
40
|
+
case "shutdown":
|
|
41
|
+
// Respond before shutting down so the client gets the response
|
|
42
|
+
setImmediate(() => daemon.shutdown());
|
|
43
|
+
return { ok: true };
|
|
44
|
+
default:
|
|
45
|
+
return { ok: false, error: `unknown command: ${cmd.cmd}` };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50
|
+
return { ok: false, error: msg };
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|