grepmax 0.8.1 → 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,10 +154,13 @@ Examples:
154
154
  throw e;
155
155
  }
156
156
  // Start watcher
157
- const launched = (0, watcher_launcher_1.launchWatcher)(projectRoot);
158
- if (launched) {
157
+ const launched = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
158
+ if (launched.ok) {
159
159
  console.log(`Watcher started (PID: ${launched.pid})`);
160
160
  }
161
+ else if (launched.reason === "spawn-failed") {
162
+ console.warn(`[add] ${launched.message}`);
163
+ }
161
164
  }
162
165
  catch (error) {
163
166
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -130,7 +130,10 @@ function installPlugin() {
130
130
  const startScript = `
131
131
  const { spawn } = require("child_process");
132
132
  const fs = require("fs");
133
- const out = fs.openSync("/tmp/gmax.log", "a");
133
+ const path = require("path");
134
+ const logDir = path.join(require("os").homedir(), ".gmax", "logs");
135
+ fs.mkdirSync(logDir, { recursive: true });
136
+ const out = fs.openSync(path.join(logDir, "gmax.log"), "a");
134
137
  const child = spawn("gmax", ["serve"], { detached: true, stdio: ["ignore", out, out] });
135
138
  child.unref();
136
139
  `;
@@ -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,12 @@ 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);
172
- if (launched) {
173
- console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${launched.pid})`);
175
+ const launched = yield (0, watcher_launcher_1.launchWatcher)(projectRoot);
176
+ if (launched.ok) {
177
+ console.log(`Restarted watcher for ${path.basename(projectRoot)} (PID: ${launched.pid})`);
178
+ }
179
+ else if (launched.reason === "spawn-failed") {
180
+ console.warn(`[index] ${launched.message}`);
174
181
  }
175
182
  }
176
183
  }
@@ -77,6 +77,7 @@ const format_helpers_1 = require("../lib/utils/format-helpers");
77
77
  const import_extractor_1 = require("../lib/utils/import-extractor");
78
78
  const project_registry_1 = require("../lib/utils/project-registry");
79
79
  const project_root_1 = require("../lib/utils/project-root");
80
+ const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
80
81
  const watcher_store_1 = require("../lib/utils/watcher-store");
81
82
  // ---------------------------------------------------------------------------
82
83
  // Tool definitions
@@ -352,17 +353,17 @@ exports.mcp = new commander_1.Command("mcp")
352
353
  }
353
354
  // --- Background watcher ---
354
355
  function ensureWatcher() {
355
- // Only start watcher for registered projects
356
- if (!(0, project_registry_1.getProject)(projectRoot))
357
- return;
358
- if ((0, watcher_store_1.getWatcherCoveringPath)(projectRoot))
359
- return;
360
- const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", projectRoot], {
361
- detached: true,
362
- stdio: "ignore",
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
+ }
363
366
  });
364
- child.unref();
365
- console.log(`[MCP] Started background watcher for ${projectRoot}`);
366
367
  }
367
368
  // --- Tool handlers ---
368
369
  function handleSemanticSearch(args_1) {
@@ -49,6 +49,7 @@ const commander_1 = require("commander");
49
49
  const meta_cache_1 = require("../lib/store/meta-cache");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const process_1 = require("../lib/utils/process");
52
53
  const project_marker_1 = require("../lib/utils/project-marker");
53
54
  const project_registry_1 = require("../lib/utils/project-registry");
54
55
  const project_root_1 = require("../lib/utils/project-root");
@@ -98,20 +99,19 @@ Examples:
98
99
  return;
99
100
  }
100
101
  }
101
- // Stop any watcher
102
- const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
103
- if (watcher) {
104
- console.log(`Stopping watcher (PID: ${watcher.pid})...`);
105
- try {
106
- process.kill(watcher.pid, "SIGTERM");
107
- }
108
- catch (_b) { }
109
- for (let i = 0; i < 50; i++) {
110
- if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
111
- break;
112
- yield new Promise((r) => setTimeout(r, 100));
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);
113
114
  }
114
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
115
115
  }
116
116
  // Delete vectors from LanceDB
117
117
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
@@ -139,13 +139,13 @@ Examples:
139
139
  try {
140
140
  metaCache.close();
141
141
  }
142
- catch (_c) { }
142
+ catch (_b) { }
143
143
  }
144
144
  if (vectorDb) {
145
145
  try {
146
146
  yield vectorDb.close();
147
147
  }
148
- catch (_d) { }
148
+ catch (_c) { }
149
149
  }
150
150
  yield (0, exit_1.gracefulExit)();
151
151
  }
@@ -529,7 +529,10 @@ 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
- launchWatcher(projectRoot);
532
+ const launched = yield launchWatcher(projectRoot);
533
+ if (!launched.ok && launched.reason === "spawn-failed") {
534
+ console.warn(`[search] ${launched.message}`);
535
+ }
533
536
  }
534
537
  const searcher = new searcher_1.Searcher(vectorDb);
535
538
  // Use --root or fall back to project root
@@ -59,6 +59,7 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
59
59
  const meta_cache_1 = require("../lib/store/meta-cache");
60
60
  const vector_db_1 = require("../lib/store/vector-db");
61
61
  const exit_1 = require("../lib/utils/exit");
62
+ const log_rotate_1 = require("../lib/utils/log-rotate");
62
63
  const project_root_1 = require("../lib/utils/project-root");
63
64
  const server_registry_1 = require("../lib/utils/server-registry");
64
65
  function isMlxServerUp() {
@@ -84,8 +85,7 @@ function startMlxServer(mlxModel) {
84
85
  const serverDir = candidates.find((d) => fs.existsSync(path.join(d, "server.py")));
85
86
  if (!serverDir)
86
87
  return null;
87
- const logPath = "/tmp/mlx-embed-server.log";
88
- const out = fs.openSync(logPath, "a");
88
+ const out = (0, log_rotate_1.openRotatedLog)(path.join(config_1.PATHS.logsDir, "mlx-embed-server.log"));
89
89
  const env = Object.assign({}, process.env);
90
90
  if (mlxModel) {
91
91
  env.MLX_EMBED_MODEL = mlxModel;
@@ -121,14 +121,12 @@ exports.serve = new commander_1.Command("serve")
121
121
  const args = process.argv
122
122
  .slice(2)
123
123
  .filter((arg) => arg !== "-b" && arg !== "--background");
124
- const logDir = path.join(config_1.PATHS.globalRoot, "logs");
125
- fs.mkdirSync(logDir, { recursive: true });
126
124
  const safeName = path
127
125
  .basename(projectRoot)
128
126
  .replace(/[^a-zA-Z0-9._-]/g, "_");
129
- const logFile = path.join(logDir, `server-${safeName}.log`);
130
- const out = fs.openSync(logFile, "a");
131
- const err = fs.openSync(logFile, "a");
127
+ const logFile = path.join(config_1.PATHS.logsDir, `server-${safeName}.log`);
128
+ const out = (0, log_rotate_1.openRotatedLog)(logFile);
129
+ const err = (0, log_rotate_1.openRotatedLog)(logFile);
132
130
  const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
133
131
  detached: true,
134
132
  stdio: ["ignore", out, err],
@@ -44,7 +44,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.watch = void 0;
46
46
  const node_child_process_1 = require("node:child_process");
47
- const fs = __importStar(require("node:fs"));
48
47
  const path = __importStar(require("node:path"));
49
48
  const commander_1 = require("commander");
50
49
  const config_1 = require("../config");
@@ -55,19 +54,63 @@ const watcher_1 = require("../lib/index/watcher");
55
54
  const meta_cache_1 = require("../lib/store/meta-cache");
56
55
  const vector_db_1 = require("../lib/store/vector-db");
57
56
  const exit_1 = require("../lib/utils/exit");
57
+ const log_rotate_1 = require("../lib/utils/log-rotate");
58
+ const process_1 = require("../lib/utils/process");
58
59
  const project_registry_1 = require("../lib/utils/project-registry");
59
60
  const project_root_1 = require("../lib/utils/project-root");
60
61
  const watcher_store_1 = require("../lib/utils/watcher-store");
61
62
  const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
62
63
  const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
63
- const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB — rotate log when exceeded
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();
@@ -83,20 +126,9 @@ exports.watch = new commander_1.Command("watch")
83
126
  const args = process.argv
84
127
  .slice(2)
85
128
  .filter((arg) => arg !== "-b" && arg !== "--background");
86
- const logDir = path.join(config_1.PATHS.globalRoot, "logs");
87
- fs.mkdirSync(logDir, { recursive: true });
88
129
  const safeName = projectName.replace(/[^a-zA-Z0-9._-]/g, "_");
89
- const logFile = path.join(logDir, `watch-${safeName}.log`);
90
- // Rotate log if it exceeds MAX_LOG_BYTES
91
- try {
92
- const logStat = fs.statSync(logFile);
93
- if (logStat.size > MAX_LOG_BYTES) {
94
- const prev = `${logFile}.prev`;
95
- fs.renameSync(logFile, prev);
96
- }
97
- }
98
- catch (_c) { }
99
- const out = fs.openSync(logFile, "a");
130
+ const logFile = path.join(config_1.PATHS.logsDir, `watch-${safeName}.log`);
131
+ const out = (0, log_rotate_1.openRotatedLog)(logFile);
100
132
  const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
101
133
  detached: true,
102
134
  stdio: ["ignore", out, out],
@@ -107,7 +139,7 @@ exports.watch = new commander_1.Command("watch")
107
139
  console.log(`Watcher started for ${projectName} (PID: ${child.pid}, log: ${logFile})`);
108
140
  process.exit(0);
109
141
  }
110
- // --- Foreground mode ---
142
+ // --- Per-project foreground mode ---
111
143
  // Migrate legacy watchers.json to LMDB on first use
112
144
  (0, watcher_store_1.migrateFromJson)();
113
145
  // Watcher requires project to be registered
@@ -206,16 +238,30 @@ exports.watch
206
238
  .command("status")
207
239
  .description("Show running watchers")
208
240
  .action(() => __awaiter(void 0, void 0, void 0, function* () {
209
- const watchers = (0, watcher_store_1.listWatchers)();
210
- if (watchers.length === 0) {
211
- console.log("No running watchers.");
212
- yield (0, exit_1.gracefulExit)();
213
- 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
+ }
214
262
  }
215
- console.log("Running watchers:");
216
- for (const w of watchers) {
217
- const age = Math.floor((Date.now() - w.startTime) / 60000);
218
- console.log(`- PID: ${w.pid} | Root: ${w.projectRoot} | Running: ${age}m`);
263
+ if (!resp.ok && watchers.length === 0) {
264
+ console.log("No running watchers.");
219
265
  }
220
266
  yield (0, exit_1.gracefulExit)();
221
267
  }));
@@ -225,34 +271,49 @@ exports.watch
225
271
  .option("--all", "Stop all running watchers")
226
272
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
227
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
+ }
228
282
  if (options.all) {
229
283
  const watchers = (0, watcher_store_1.listWatchers)();
230
284
  for (const w of watchers) {
231
- try {
232
- process.kill(w.pid, "SIGTERM");
233
- (0, watcher_store_1.unregisterWatcher)(w.pid);
285
+ const killed = yield (0, process_1.killProcess)(w.pid);
286
+ (0, watcher_store_1.unregisterWatcher)(w.pid);
287
+ if (!killed) {
288
+ console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
234
289
  }
235
- catch (_b) { }
236
290
  }
237
- 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
+ }
238
297
  yield (0, exit_1.gracefulExit)();
239
298
  return;
240
299
  }
300
+ // Single project stop
241
301
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
242
302
  const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
243
303
  if (!watcher) {
244
- console.log("No watcher running for this project.");
304
+ if (!stoppedDaemon) {
305
+ console.log("No watcher running for this project.");
306
+ }
245
307
  yield (0, exit_1.gracefulExit)();
246
308
  return;
247
309
  }
248
- try {
249
- process.kill(watcher.pid, "SIGTERM");
250
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
310
+ const killed = yield (0, process_1.killProcess)(watcher.pid);
311
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
312
+ if (killed) {
251
313
  console.log(`Stopped watcher (PID: ${watcher.pid})`);
252
314
  }
253
- catch (_c) {
254
- console.log("Watcher process not found.");
255
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
315
+ else {
316
+ console.warn(`Warning: watcher PID ${watcher.pid} did not exit`);
256
317
  }
257
318
  yield (0, exit_1.gracefulExit)();
258
319
  }));
package/dist/config.js CHANGED
@@ -93,6 +93,8 @@ exports.PATHS = {
93
93
  globalRoot: GLOBAL_ROOT,
94
94
  models: path.join(GLOBAL_ROOT, "models"),
95
95
  grammars: path.join(GLOBAL_ROOT, "grammars"),
96
+ logsDir: path.join(GLOBAL_ROOT, "logs"),
97
+ daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
96
98
  // Centralized index storage — one database for all indexed directories
97
99
  lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
98
100
  cacheDir: path.join(GLOBAL_ROOT, "cache"),