grepmax 0.8.2 → 0.9.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.
@@ -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
  }
@@ -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
- const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
109
- let restartWatcher = null;
110
- if (watcher) {
111
- console.log(`Stopping watcher (PID: ${watcher.pid}) for ${path.basename(watcher.projectRoot)}...`);
112
- try {
113
- process.kill(watcher.pid, "SIGTERM");
114
- }
115
- catch (_b) { }
116
- // Wait for process to exit (up to 5s)
117
- for (let i = 0; i < 50; i++) {
118
- if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
119
- break;
120
- yield new Promise((r) => setTimeout(r, 100));
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)(restartWatcher.projectRoot);
175
+ const launched = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
172
176
  if (launched.ok) {
173
- console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${launched.pid})`);
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}`);
@@ -353,10 +353,17 @@ exports.mcp = new commander_1.Command("mcp")
353
353
  }
354
354
  // --- Background watcher ---
355
355
  function ensureWatcher() {
356
- const result = (0, watcher_launcher_1.launchWatcher)(projectRoot);
357
- if (result.ok && !result.reused) {
358
- console.log(`[MCP] Started background watcher for ${projectRoot} (PID: ${result.pid})`);
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) {
@@ -99,12 +99,19 @@ Examples:
99
99
  return;
100
100
  }
101
101
  }
102
- // Stop any watcher
103
- const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
104
- if (watcher) {
105
- console.log(`Stopping watcher (PID: ${watcher.pid})...`);
106
- yield (0, process_1.killProcess)(watcher.pid);
107
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
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);
@@ -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
  }
@@ -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
- // --- Foreground mode ---
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 watchers = (0, watcher_store_1.listWatchers)();
199
- if (watchers.length === 0) {
200
- console.log("No running watchers.");
201
- yield (0, exit_1.gracefulExit)();
202
- return;
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
- console.log("Running watchers:");
205
- for (const w of watchers) {
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
- console.log(`Stopped ${watchers.length} watcher(s).`);
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
- console.log("No watcher running for this project.");
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,299 @@
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. Create chokidar (empty — projects added via watchProject)
104
+ const forcePoll = process.env.GMAX_WATCH_POLL === "1";
105
+ const usePoll = forcePoll || process.platform !== "darwin";
106
+ this.watcher = (0, chokidar_1.watch)([], Object.assign({ ignored: watcher_1.WATCHER_IGNORE_PATTERNS, ignoreInitial: true, persistent: true }, (usePoll
107
+ ? { usePolling: true, interval: 5000, binaryInterval: 10000 }
108
+ : {})));
109
+ this.watcher.on("add", (p) => this.routeEvent("change", p));
110
+ this.watcher.on("change", (p) => this.routeEvent("change", p));
111
+ this.watcher.on("unlink", (p) => this.routeEvent("unlink", p));
112
+ this.watcher.on("error", (err) => {
113
+ console.error("[daemon] Watcher error:", err);
114
+ });
115
+ // 5. Register daemon (only after resources are open)
116
+ (0, watcher_store_1.registerDaemon)(process.pid);
117
+ // 6. Load registered projects
118
+ const projects = (0, project_registry_1.listProjects)().filter((p) => p.status === "indexed");
119
+ for (const p of projects) {
120
+ yield this.watchProject(p.root);
121
+ }
122
+ // 7. Heartbeat
123
+ this.heartbeatInterval = setInterval(() => {
124
+ (0, watcher_store_1.heartbeat)(process.pid);
125
+ }, HEARTBEAT_INTERVAL_MS);
126
+ // 8. Idle timeout
127
+ this.idleInterval = setInterval(() => {
128
+ if (Date.now() - this.lastActivity > IDLE_TIMEOUT_MS) {
129
+ console.log("[daemon] Idle for 30 minutes, shutting down");
130
+ this.shutdown();
131
+ }
132
+ }, HEARTBEAT_INTERVAL_MS);
133
+ // 9. Socket server
134
+ this.server = net.createServer((conn) => {
135
+ let buf = "";
136
+ conn.on("data", (chunk) => {
137
+ buf += chunk.toString();
138
+ const nl = buf.indexOf("\n");
139
+ if (nl === -1)
140
+ return;
141
+ const line = buf.slice(0, nl);
142
+ buf = buf.slice(nl + 1);
143
+ let cmd;
144
+ try {
145
+ cmd = JSON.parse(line);
146
+ }
147
+ catch (_a) {
148
+ conn.write(`${JSON.stringify({ ok: false, error: "invalid JSON" })}\n`);
149
+ conn.end();
150
+ return;
151
+ }
152
+ (0, ipc_handler_1.handleCommand)(this, cmd).then((resp) => {
153
+ conn.write(`${JSON.stringify(resp)}\n`);
154
+ conn.end();
155
+ });
156
+ });
157
+ conn.on("error", () => { }); // ignore client disconnect
158
+ });
159
+ yield new Promise((resolve, reject) => {
160
+ this.server.on("error", (err) => {
161
+ const code = err.code;
162
+ if (code === "EADDRINUSE") {
163
+ console.error("[daemon] Another daemon is already running");
164
+ reject(err);
165
+ }
166
+ else if (code === "EOPNOTSUPP") {
167
+ console.error("[daemon] Filesystem does not support Unix sockets");
168
+ process.exitCode = 2;
169
+ reject(err);
170
+ }
171
+ else {
172
+ reject(err);
173
+ }
174
+ });
175
+ this.server.listen(config_1.PATHS.daemonSocket, () => resolve());
176
+ });
177
+ console.log(`[daemon] Started (PID: ${process.pid}, ${this.processors.size} projects)`);
178
+ });
179
+ }
180
+ watchProject(root) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ if (this.processors.has(root) || this.pendingOps.has(root))
183
+ return;
184
+ if (!this.vectorDb || !this.metaCache || !this.watcher)
185
+ return;
186
+ this.pendingOps.add(root);
187
+ const processor = new batch_processor_1.ProjectBatchProcessor({
188
+ projectRoot: root,
189
+ vectorDb: this.vectorDb,
190
+ metaCache: this.metaCache,
191
+ dataDir: config_1.PATHS.globalRoot,
192
+ onReindex: (files, ms) => {
193
+ console.log(`[daemon:${path.basename(root)}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
194
+ },
195
+ onActivity: () => {
196
+ this.lastActivity = Date.now();
197
+ },
198
+ });
199
+ this.watcher.add(root);
200
+ this.processors.set(root, processor);
201
+ this.rebuildSortedRoots();
202
+ (0, watcher_store_1.registerWatcher)({
203
+ pid: process.pid,
204
+ projectRoot: root,
205
+ startTime: Date.now(),
206
+ status: "watching",
207
+ lastHeartbeat: Date.now(),
208
+ });
209
+ this.pendingOps.delete(root);
210
+ console.log(`[daemon] Watching ${root}`);
211
+ });
212
+ }
213
+ unwatchProject(root) {
214
+ return __awaiter(this, void 0, void 0, function* () {
215
+ var _a;
216
+ const processor = this.processors.get(root);
217
+ if (!processor)
218
+ return;
219
+ yield processor.close();
220
+ (_a = this.watcher) === null || _a === void 0 ? void 0 : _a.unwatch(root);
221
+ this.processors.delete(root);
222
+ this.rebuildSortedRoots();
223
+ (0, watcher_store_1.unregisterWatcherByRoot)(root);
224
+ console.log(`[daemon] Unwatched ${root}`);
225
+ });
226
+ }
227
+ listProjects() {
228
+ return [...this.processors.keys()].map((root) => ({
229
+ root,
230
+ status: "watching",
231
+ }));
232
+ }
233
+ uptime() {
234
+ return Math.floor((Date.now() - this.startTime) / 1000);
235
+ }
236
+ shutdown() {
237
+ return __awaiter(this, void 0, void 0, function* () {
238
+ var _a, _b, _c, _d;
239
+ if (this.shuttingDown)
240
+ return;
241
+ this.shuttingDown = true;
242
+ console.log("[daemon] Shutting down...");
243
+ if (this.heartbeatInterval)
244
+ clearInterval(this.heartbeatInterval);
245
+ if (this.idleInterval)
246
+ clearInterval(this.idleInterval);
247
+ // Close all processors
248
+ for (const processor of this.processors.values()) {
249
+ yield processor.close();
250
+ }
251
+ // Close chokidar
252
+ try {
253
+ yield ((_a = this.watcher) === null || _a === void 0 ? void 0 : _a.close());
254
+ }
255
+ catch (_e) { }
256
+ // Close server + socket
257
+ (_b = this.server) === null || _b === void 0 ? void 0 : _b.close();
258
+ try {
259
+ fs.unlinkSync(config_1.PATHS.daemonSocket);
260
+ }
261
+ catch (_f) { }
262
+ // Unregister all
263
+ for (const root of this.processors.keys()) {
264
+ (0, watcher_store_1.unregisterWatcherByRoot)(root);
265
+ }
266
+ (0, watcher_store_1.unregisterDaemon)();
267
+ this.processors.clear();
268
+ // Close shared resources
269
+ try {
270
+ yield ((_c = this.metaCache) === null || _c === void 0 ? void 0 : _c.close());
271
+ }
272
+ catch (_g) { }
273
+ try {
274
+ yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
275
+ }
276
+ catch (_h) { }
277
+ console.log("[daemon] Shutdown complete");
278
+ });
279
+ }
280
+ routeEvent(event, absPath) {
281
+ const processor = this.findProcessor(absPath);
282
+ if (processor) {
283
+ processor.handleFileEvent(event, absPath);
284
+ }
285
+ }
286
+ findProcessor(absPath) {
287
+ // sortedRoots is longest-first, so first match is the most specific
288
+ for (const root of this.sortedRoots) {
289
+ if (absPath.startsWith(root) && (absPath.length === root.length || absPath[root.length] === "/")) {
290
+ return this.processors.get(root);
291
+ }
292
+ }
293
+ return undefined;
294
+ }
295
+ rebuildSortedRoots() {
296
+ this.sortedRoots = [...this.processors.keys()].sort((a, b) => b.length - a.length);
297
+ }
298
+ }
299
+ 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
+ }