grepmax 0.11.3 → 0.12.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.
@@ -27,7 +27,6 @@ function resolveGmaxBin() {
27
27
  return (0, node_child_process_1.execSync)("which gmax", { encoding: "utf-8" }).trim();
28
28
  }
29
29
  catch (_a) {
30
- // Fall back to the path of the current process entry point
31
30
  const binDir = node_path_1.default.dirname(process.argv[1]);
32
31
  const candidate = node_path_1.default.join(binDir, "gmax");
33
32
  if (node_fs_1.default.existsSync(candidate))
@@ -38,76 +37,36 @@ function resolveGmaxBin() {
38
37
  function buildShimContent(gmaxBin) {
39
38
  return `
40
39
  import { tool } from "@opencode-ai/plugin";
40
+ import { existsSync, realpathSync, readFileSync } from "node:fs";
41
+ import { resolve, dirname, join } from "node:path";
42
+ import { execFileSync } from "node:child_process";
43
+
44
+ // Resolve gmax binary and SKILL dynamically from the installed package.
45
+ // This means npm install -g grepmax@latest automatically updates the SKILL
46
+ // without needing to re-run gmax install-opencode.
47
+ const _gmax = (() => {
48
+ // Binary: try hardcoded path, fall back to which
49
+ let bin = "${gmaxBin}";
50
+ if (!existsSync(bin)) {
51
+ try {
52
+ bin = execFileSync("which gmax", { encoding: "utf-8" }).trim();
53
+ } catch {
54
+ return { bin: "gmax", skill: "Semantic code search. Run: gmax 'query' --agent" };
55
+ }
56
+ }
41
57
 
42
- const SKILL = \`
43
- ---
44
- name: gmax
45
- description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
46
- ---
47
-
48
- ## When to use what
49
-
50
- - **Know the exact string/symbol?** → grep/ripgrep (fastest)
51
- - **Know the file already?** → Read tool directly
52
- - **Searching by concept/behavior?** → gmax "query" --agent (semantic search)
53
- - **Need full function body?** → gmax extract <symbol> (complete source)
54
- - **Quick symbol overview?** → gmax peek <symbol> (signature + callers + callees)
55
- - **Need file structure?** → gmax skeleton <path>
56
- - **Need call flow?** → gmax trace <symbol>
57
-
58
- ## Primary command
59
-
60
- Use --agent for compact, token-efficient output (one line per result):
61
-
62
- gmax "where do we handle authentication" --agent
63
- gmax "database connection pooling" --role ORCHESTRATION --agent -m 5
64
- gmax "error handling" --lang ts --exclude tests/ --agent
65
-
66
- Output: file:line symbol [ROLE] — signature_hint
67
-
68
- All search flags: --agent -m <n> --per-file <n> --root <dir> --file <name> --exclude <prefix> --lang <ext> --role <role> --symbol --imports --name <regex> -C <n> --skeleton --explain --context-for-llm --budget <tokens>
69
-
70
- ## Commands
71
-
72
- ### Core
73
- gmax "query" --agent # semantic search (compact output)
74
- gmax extract <symbol> # full function body with line numbers
75
- gmax peek <symbol> # signature + callers + callees
76
- gmax trace <symbol> -d 2 # call graph (multi-hop)
77
- gmax skeleton <path> # file structure (signatures only)
78
- gmax symbols --agent # list all indexed symbols
79
-
80
- ### Analysis
81
- gmax diff [ref] --agent # search scoped to git changes
82
- gmax test <symbol> --agent # find tests via reverse call graph
83
- gmax impact <symbol> --agent # dependents + affected tests
84
- gmax similar <symbol> --agent # vector-to-vector similarity
85
- gmax context "topic" --budget 4k # token-budgeted topic summary
86
-
87
- ### Project
88
- gmax project --agent # languages, structure, key symbols
89
- gmax related <file> --agent # dependencies + dependents
90
- gmax recent --agent # recently modified files
91
- gmax status --agent # all indexed projects
92
-
93
- ### Management
94
- gmax add # add + index current directory
95
- gmax index # reindex current project
96
- gmax doctor --fix # health check + auto-repair
97
-
98
- ## Tips
99
-
100
- - Be specific. "auth" is vague. "where does the server validate JWT tokens" is specific.
101
- - Use --role ORCHESTRATION to skip type definitions and find actual logic.
102
- - Use --symbol when the query is a function/class name — gets search + trace in one shot.
103
- - Don't search for exact strings — use grep for that. gmax finds concepts.
104
- - If search returns nothing, run: gmax add
105
- \`;
106
-
107
- const GMAX_BIN = "${gmaxBin}";
58
+ // SKILL: read from package root
59
+ try {
60
+ const root = resolve(dirname(realpathSync(bin)), "..");
61
+ const skillPath = join(root, "plugins", "grepmax", "skills", "grepmax", "SKILL.md");
62
+ return { bin, skill: readFileSync(skillPath, "utf-8") };
63
+ } catch {
64
+ return { bin, skill: "Semantic code search. Run: gmax 'query' --agent" };
65
+ }
66
+ })();
108
67
 
109
68
  export default tool({
110
- description: SKILL,
69
+ description: _gmax.skill,
111
70
  args: {
112
71
  argv: tool.schema.array(tool.schema.string())
113
72
  .describe("Arguments for gmax, e.g. ['search', 'user auth', '--agent']")
@@ -115,7 +74,7 @@ export default tool({
115
74
  async execute({ argv }) {
116
75
  try {
117
76
  // @ts-ignore
118
- const out = await Bun.spawn([GMAX_BIN, ...argv], { stdout: "pipe" }).stdout;
77
+ const out = await Bun.spawn([_gmax.bin, ...argv], { stdout: "pipe" }).stdout;
119
78
  const text = await new Response(out).text();
120
79
  if (text.includes("Indexing") || text.includes("Building") || text.includes("Syncing")) {
121
80
  return \`WARN: The index is currently updating.
@@ -133,12 +92,20 @@ Please wait for indexing to complete before searching.\`;
133
92
  })`;
134
93
  }
135
94
  function buildPluginContent(gmaxBin) {
136
- return `import { execFileSync } from "node:child_process";
137
- import { readFileSync } from "node:fs";
138
- import { join } from "node:path";
95
+ return `import { existsSync, readFileSync, realpathSync } from "node:fs";
96
+ import { execFileSync } from "node:child_process";
97
+ import { join, resolve, dirname } from "node:path";
139
98
  import { homedir } from "node:os";
140
99
 
141
- const GMAX_BIN = "${gmaxBin}";
100
+ function resolveGmaxBin() {
101
+ const hardcoded = "${gmaxBin}";
102
+ if (existsSync(hardcoded)) return hardcoded;
103
+ try {
104
+ return execFileSync("which gmax", { encoding: "utf-8" }).trim();
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
142
109
 
143
110
  function isProjectRegistered() {
144
111
  try {
@@ -151,26 +118,23 @@ function isProjectRegistered() {
151
118
  }
152
119
  }
153
120
 
121
+ function startDaemon() {
122
+ const bin = resolveGmaxBin();
123
+ if (!bin || !isProjectRegistered()) return;
124
+ try {
125
+ execFileSync(bin, ["watch", "--daemon", "-b"], {
126
+ timeout: 5000,
127
+ stdio: "ignore",
128
+ });
129
+ } catch {}
130
+ }
131
+
154
132
  export const GmaxPlugin = async () => {
155
- // Start daemon on session creation if project is indexed
156
- if (isProjectRegistered()) {
157
- try {
158
- execFileSync(GMAX_BIN, ["watch", "--daemon", "-b"], {
159
- timeout: 5000,
160
- stdio: "ignore",
161
- });
162
- } catch {}
163
- }
133
+ startDaemon();
164
134
 
165
135
  return {
166
136
  "session.created": async () => {
167
- if (!isProjectRegistered()) return;
168
- try {
169
- execFileSync(GMAX_BIN, ["watch", "--daemon", "-b"], {
170
- timeout: 5000,
171
- stdio: "ignore",
172
- });
173
- } catch {}
137
+ startDaemon();
174
138
  },
175
139
  };
176
140
  };
@@ -181,19 +145,17 @@ function install() {
181
145
  var _a;
182
146
  try {
183
147
  // 1. Delete legacy plugin
184
- for (const legacy of [LEGACY_PLUGIN_PATH]) {
185
- if (node_fs_1.default.existsSync(legacy)) {
186
- try {
187
- node_fs_1.default.unlinkSync(legacy);
188
- console.log("Deleted legacy plugin at", legacy);
189
- }
190
- catch (_b) { }
148
+ if (node_fs_1.default.existsSync(LEGACY_PLUGIN_PATH)) {
149
+ try {
150
+ node_fs_1.default.unlinkSync(LEGACY_PLUGIN_PATH);
151
+ console.log("Deleted legacy plugin at", LEGACY_PLUGIN_PATH);
191
152
  }
153
+ catch (_b) { }
192
154
  }
193
155
  // 2. Resolve absolute path to gmax binary
194
156
  const gmaxBin = resolveGmaxBin();
195
157
  console.log(` Resolved gmax binary: ${gmaxBin}`);
196
- // 3. Create tool shim
158
+ // 3. Create tool shim (reads SKILL dynamically from package root)
197
159
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(TOOL_PATH), { recursive: true });
198
160
  node_fs_1.default.writeFileSync(TOOL_PATH, buildShimContent(gmaxBin));
199
161
  console.log("✅ Created tool shim at", TOOL_PATH);
@@ -0,0 +1,259 @@
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.plugin = void 0;
46
+ const node_child_process_1 = require("node:child_process");
47
+ const fs = __importStar(require("node:fs"));
48
+ const os = __importStar(require("node:os"));
49
+ const path = __importStar(require("node:path"));
50
+ const commander_1 = require("commander");
51
+ const exit_1 = require("../lib/utils/exit");
52
+ function commandExists(cmd) {
53
+ try {
54
+ (0, node_child_process_1.execSync)(`which ${cmd}`, { encoding: "utf-8", stdio: "pipe" });
55
+ return true;
56
+ }
57
+ catch (_a) {
58
+ return false;
59
+ }
60
+ }
61
+ function getClients() {
62
+ return [
63
+ {
64
+ name: "Claude Code",
65
+ id: "claude",
66
+ detect: () => commandExists("claude"),
67
+ isInstalled: () => fs.existsSync(path.join(os.homedir(), ".claude", "plugins", "cache", "grepmax", "grepmax")),
68
+ install: () => __awaiter(this, void 0, void 0, function* () {
69
+ const { installClaudeCode } = yield Promise.resolve().then(() => __importStar(require("./claude-code")));
70
+ yield installClaudeCode.parseAsync(["node", "gmax"]);
71
+ }),
72
+ uninstall: () => __awaiter(this, void 0, void 0, function* () {
73
+ const cacheBase = path.join(os.homedir(), ".claude", "plugins", "cache", "grepmax");
74
+ if (fs.existsSync(cacheBase)) {
75
+ fs.rmSync(cacheBase, { recursive: true, force: true });
76
+ }
77
+ try {
78
+ const { spawn } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
79
+ yield new Promise((resolve) => {
80
+ const child = spawn("claude", ["plugin", "marketplace", "remove", "grepmax"], { stdio: "ignore" });
81
+ child.on("exit", () => resolve());
82
+ child.on("error", () => resolve());
83
+ });
84
+ }
85
+ catch (_a) { }
86
+ console.log("✅ Removed Claude Code plugin.");
87
+ }),
88
+ },
89
+ {
90
+ name: "OpenCode",
91
+ id: "opencode",
92
+ detect: () => commandExists("opencode"),
93
+ isInstalled: () => fs.existsSync(path.join(os.homedir(), ".config", "opencode", "tool", "gmax.ts")),
94
+ install: () => __awaiter(this, void 0, void 0, function* () {
95
+ const { installOpencode } = yield Promise.resolve().then(() => __importStar(require("./opencode")));
96
+ yield installOpencode.parseAsync(["node", "gmax"]);
97
+ }),
98
+ uninstall: () => __awaiter(this, void 0, void 0, function* () {
99
+ const { uninstallOpencode } = yield Promise.resolve().then(() => __importStar(require("./opencode")));
100
+ yield uninstallOpencode.parseAsync(["node", "gmax"]);
101
+ }),
102
+ },
103
+ {
104
+ name: "Codex",
105
+ id: "codex",
106
+ detect: () => commandExists("codex"),
107
+ isInstalled: () => {
108
+ const p = path.join(os.homedir(), ".codex", "AGENTS.md");
109
+ try {
110
+ return fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("name: gmax");
111
+ }
112
+ catch (_a) {
113
+ return false;
114
+ }
115
+ },
116
+ install: () => __awaiter(this, void 0, void 0, function* () {
117
+ const { installCodex } = yield Promise.resolve().then(() => __importStar(require("./codex")));
118
+ yield installCodex.parseAsync(["node", "gmax"]);
119
+ }),
120
+ uninstall: () => __awaiter(this, void 0, void 0, function* () {
121
+ const { uninstallCodex } = yield Promise.resolve().then(() => __importStar(require("./codex")));
122
+ yield uninstallCodex.parseAsync(["node", "gmax"]);
123
+ }),
124
+ },
125
+ {
126
+ name: "Factory Droid",
127
+ id: "droid",
128
+ detect: () => fs.existsSync(path.join(os.homedir(), ".factory")) &&
129
+ commandExists("droid"),
130
+ isInstalled: () => fs.existsSync(path.join(os.homedir(), ".factory", "skills", "gmax", "SKILL.md")),
131
+ install: () => __awaiter(this, void 0, void 0, function* () {
132
+ const { installDroid } = yield Promise.resolve().then(() => __importStar(require("./droid")));
133
+ yield installDroid.parseAsync(["node", "gmax"]);
134
+ }),
135
+ uninstall: () => __awaiter(this, void 0, void 0, function* () {
136
+ const { uninstallDroid } = yield Promise.resolve().then(() => __importStar(require("./droid")));
137
+ yield uninstallDroid.parseAsync(["node", "gmax"]);
138
+ }),
139
+ },
140
+ ];
141
+ }
142
+ // --- Subcommands ---
143
+ const addCmd = new commander_1.Command("add")
144
+ .description("Install or update gmax plugins")
145
+ .argument("[client]", "Client to install (claude, opencode, codex, droid, all)")
146
+ .action((clientArg) => __awaiter(void 0, void 0, void 0, function* () {
147
+ const clients = getClients();
148
+ const onlyId = clientArg && clientArg !== "all" ? clientArg : undefined;
149
+ if (onlyId) {
150
+ const client = clients.find((c) => c.id === onlyId);
151
+ if (!client) {
152
+ console.error(`Unknown client: ${onlyId}`);
153
+ console.error(`Available: ${clients.map((c) => c.id).join(", ")}`);
154
+ yield (0, exit_1.gracefulExit)(1);
155
+ return;
156
+ }
157
+ if (!client.detect()) {
158
+ console.error(`${client.name} not found on this system`);
159
+ yield (0, exit_1.gracefulExit)(1);
160
+ return;
161
+ }
162
+ yield client.install();
163
+ yield (0, exit_1.gracefulExit)();
164
+ return;
165
+ }
166
+ // Install all detected
167
+ console.log("gmax plugin add — detecting clients...\n");
168
+ let installed = 0;
169
+ for (const client of clients) {
170
+ if (!client.detect()) {
171
+ console.log(` skip ${client.name} — not found`);
172
+ continue;
173
+ }
174
+ try {
175
+ yield client.install();
176
+ installed++;
177
+ }
178
+ catch (err) {
179
+ console.error(` FAIL ${client.name} — ${err instanceof Error ? err.message : String(err)}`);
180
+ }
181
+ }
182
+ if (installed === 0) {
183
+ console.log("\nNo supported clients found. Install one of: claude, opencode, codex, droid");
184
+ }
185
+ else {
186
+ console.log(`\n${installed} plugin(s) installed.`);
187
+ }
188
+ yield (0, exit_1.gracefulExit)();
189
+ }));
190
+ const removeCmd = new commander_1.Command("remove")
191
+ .description("Remove gmax plugins")
192
+ .argument("[client]", "Client to remove (claude, opencode, codex, droid, all)")
193
+ .action((clientArg) => __awaiter(void 0, void 0, void 0, function* () {
194
+ const clients = getClients();
195
+ if (clientArg && clientArg !== "all") {
196
+ const client = clients.find((c) => c.id === clientArg);
197
+ if (!client) {
198
+ console.error(`Unknown client: ${clientArg}`);
199
+ console.error(`Available: ${clients.map((c) => c.id).join(", ")}`);
200
+ yield (0, exit_1.gracefulExit)(1);
201
+ return;
202
+ }
203
+ try {
204
+ yield client.uninstall();
205
+ }
206
+ catch (err) {
207
+ console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
208
+ }
209
+ yield (0, exit_1.gracefulExit)();
210
+ return;
211
+ }
212
+ const installedClients = clients.filter((c) => c.isInstalled());
213
+ if (installedClients.length === 0) {
214
+ console.log("No gmax plugins currently installed.");
215
+ yield (0, exit_1.gracefulExit)();
216
+ return;
217
+ }
218
+ // No arg or "all": remove all installed
219
+ for (const client of installedClients) {
220
+ try {
221
+ yield client.uninstall();
222
+ }
223
+ catch (err) {
224
+ console.error(` FAIL ${client.name} — ${err instanceof Error ? err.message : String(err)}`);
225
+ }
226
+ }
227
+ console.log(`\n${installedClients.length} plugin(s) removed.`);
228
+ yield (0, exit_1.gracefulExit)();
229
+ }));
230
+ // --- Status (default action for bare `gmax plugin`) ---
231
+ function statusAction() {
232
+ return __awaiter(this, void 0, void 0, function* () {
233
+ const clients = getClients();
234
+ console.log("gmax plugins\n");
235
+ for (const client of clients) {
236
+ const detected = client.detect();
237
+ const installed = client.isInstalled();
238
+ let status;
239
+ if (installed)
240
+ status = "✅ installed";
241
+ else if (detected)
242
+ status = "— not installed";
243
+ else
244
+ status = "— not found";
245
+ console.log(` ${client.name.padEnd(16)} ${status}`);
246
+ }
247
+ console.log("\nCommands:");
248
+ console.log(" gmax plugin add Install all detected clients");
249
+ console.log(" gmax plugin add <client> Install a specific client");
250
+ console.log(" gmax plugin remove Remove all installed plugins");
251
+ console.log(" gmax plugin remove <client> Remove a specific plugin");
252
+ yield (0, exit_1.gracefulExit)();
253
+ });
254
+ }
255
+ exports.plugin = new commander_1.Command("plugin")
256
+ .description("Manage gmax plugins for AI coding clients")
257
+ .action(statusAction)
258
+ .addCommand(addCmd)
259
+ .addCommand(removeCmd);
@@ -148,6 +148,15 @@ exports.setup = new commander_1.Command("setup")
148
148
  p.log.warn("Embedding mode changed. Run `gmax serve` to apply the new settings.");
149
149
  }
150
150
  }
151
+ // Step 8: Install plugins for detected clients
152
+ const installPlugins = yield p.confirm({
153
+ message: "Install plugins for detected clients?",
154
+ initialValue: true,
155
+ });
156
+ if (!p.isCancel(installPlugins) && installPlugins) {
157
+ const { plugin: pluginCmd } = yield Promise.resolve().then(() => __importStar(require("./plugin")));
158
+ yield pluginCmd.parseAsync(["node", "gmax"]);
159
+ }
151
160
  p.outro(`Ready — ${selectedTier.label}, ${embedMode === "gpu" ? "GPU" : "CPU"} mode`);
152
161
  yield (0, exit_1.gracefulExit)();
153
162
  }));
@@ -44,6 +44,7 @@ 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"));
47
48
  const path = __importStar(require("node:path"));
48
49
  const commander_1 = require("commander");
49
50
  const config_1 = require("../config");
@@ -79,6 +80,16 @@ exports.watch = new commander_1.Command("watch")
79
80
  if (options.background) {
80
81
  // Skip spawn if daemon already running — prevents process accumulation
81
82
  // when SessionStart hook fires on every session/clear/resume
83
+ const pidFile = config_1.PATHS.daemonPidFile;
84
+ try {
85
+ const existingPid = Number.parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
86
+ if (existingPid) {
87
+ process.kill(existingPid, 0); // throws if dead
88
+ process.exit(0); // alive — skip
89
+ }
90
+ }
91
+ catch (_c) { }
92
+ // Also check socket as fallback
82
93
  const { isDaemonRunning } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
83
94
  if (yield isDaemonRunning()) {
84
95
  process.exit(0);
package/dist/config.js CHANGED
@@ -95,6 +95,7 @@ exports.PATHS = {
95
95
  grammars: path.join(GLOBAL_ROOT, "grammars"),
96
96
  logsDir: path.join(GLOBAL_ROOT, "logs"),
97
97
  daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
98
+ daemonPidFile: path.join(GLOBAL_ROOT, "daemon.pid"),
98
99
  // Centralized index storage — one database for all indexed directories
99
100
  lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
100
101
  cacheDir: path.join(GLOBAL_ROOT, "cache"),
package/dist/index.js CHANGED
@@ -55,6 +55,7 @@ const project_1 = require("./commands/project");
55
55
  const recent_1 = require("./commands/recent");
56
56
  const related_1 = require("./commands/related");
57
57
  const opencode_1 = require("./commands/opencode");
58
+ const plugin_1 = require("./commands/plugin");
58
59
  const remove_1 = require("./commands/remove");
59
60
  const search_1 = require("./commands/search");
60
61
  const similar_1 = require("./commands/similar");
@@ -112,12 +113,18 @@ commander_1.program.addCommand(summarize_1.summarize);
112
113
  commander_1.program.addCommand(setup_1.setup);
113
114
  commander_1.program.addCommand(config_1.config);
114
115
  commander_1.program.addCommand(doctor_1.doctor);
115
- // Plugin installers
116
+ // Plugins
117
+ commander_1.program.addCommand(plugin_1.plugin);
118
+ // Legacy plugin installers (hidden — use `gmax plugin` instead)
119
+ claude_code_1.installClaudeCode._hidden = true;
116
120
  commander_1.program.addCommand(claude_code_1.installClaudeCode);
121
+ codex_1.installCodex._hidden = true;
117
122
  commander_1.program.addCommand(codex_1.installCodex);
123
+ droid_1.installDroid._hidden = true;
118
124
  commander_1.program.addCommand(droid_1.installDroid);
119
125
  droid_1.uninstallDroid._hidden = true;
120
126
  commander_1.program.addCommand(droid_1.uninstallDroid);
127
+ opencode_1.installOpencode._hidden = true;
121
128
  commander_1.program.addCommand(opencode_1.installOpencode);
122
129
  opencode_1.uninstallOpencode._hidden = true;
123
130
  commander_1.program.addCommand(opencode_1.uninstallOpencode);
@@ -81,11 +81,27 @@ class Daemon {
81
81
  yield (0, process_1.killProcess)(w.pid);
82
82
  (0, watcher_store_1.unregisterWatcher)(w.pid);
83
83
  }
84
- // 2. Stale socket cleanup
84
+ // 2. PID file — atomic dedup guard
85
+ const pidFile = config_1.PATHS.daemonPidFile;
86
+ try {
87
+ // Check if another daemon is alive
88
+ const existingPid = Number.parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
89
+ if (existingPid && existingPid !== process.pid) {
90
+ try {
91
+ process.kill(existingPid, 0); // throws if dead
92
+ console.error("[daemon] Another daemon is already running (PID:", existingPid + ")");
93
+ process.exit(0);
94
+ }
95
+ catch (_a) { }
96
+ }
97
+ }
98
+ catch (_b) { }
99
+ fs.writeFileSync(pidFile, String(process.pid));
100
+ // 3. Stale socket cleanup
85
101
  try {
86
102
  fs.unlinkSync(config_1.PATHS.daemonSocket);
87
103
  }
88
- catch (_a) { }
104
+ catch (_c) { }
89
105
  // 3. Open shared resources
90
106
  try {
91
107
  fs.mkdirSync(config_1.PATHS.cacheDir, { recursive: true });
@@ -99,10 +115,19 @@ class Daemon {
99
115
  }
100
116
  // 4. Register daemon (only after resources are open)
101
117
  (0, watcher_store_1.registerDaemon)(process.pid);
102
- // 5. Subscribe to all registered projects
118
+ // 5. Subscribe to all registered projects (skip missing directories)
103
119
  const projects = (0, project_registry_1.listProjects)().filter((p) => p.status === "indexed");
104
120
  for (const p of projects) {
105
- yield this.watchProject(p.root);
121
+ if (!fs.existsSync(p.root)) {
122
+ console.log(`[daemon] Skipping ${path.basename(p.root)} — directory not found`);
123
+ continue;
124
+ }
125
+ try {
126
+ yield this.watchProject(p.root);
127
+ }
128
+ catch (err) {
129
+ console.error(`[daemon] Failed to watch ${path.basename(p.root)}:`, err);
130
+ }
106
131
  }
107
132
  // 6. Heartbeat
108
133
  this.heartbeatInterval = setInterval(() => {
@@ -253,12 +278,16 @@ class Daemon {
253
278
  catch (_d) { }
254
279
  }
255
280
  this.subscriptions.clear();
256
- // Close server + socket
281
+ // Close server + socket + PID file
257
282
  (_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
258
283
  try {
259
284
  fs.unlinkSync(config_1.PATHS.daemonSocket);
260
285
  }
261
286
  catch (_e) { }
287
+ try {
288
+ fs.unlinkSync(config_1.PATHS.daemonPidFile);
289
+ }
290
+ catch (_f) { }
262
291
  // Unregister all
263
292
  for (const root of this.processors.keys()) {
264
293
  (0, watcher_store_1.unregisterWatcherByRoot)(root);
@@ -269,11 +298,11 @@ class Daemon {
269
298
  try {
270
299
  yield ((_b = this.metaCache) === null || _b === void 0 ? void 0 : _b.close());
271
300
  }
272
- catch (_f) { }
301
+ catch (_g) { }
273
302
  try {
274
303
  yield ((_c = this.vectorDb) === null || _c === void 0 ? void 0 : _c.close());
275
304
  }
276
- catch (_g) { }
305
+ catch (_h) { }
277
306
  console.log("[daemon] Shutdown complete");
278
307
  });
279
308
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.11.3",
3
+ "version": "0.12.1",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.11.3",
3
+ "version": "0.12.1",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",
@@ -64,3 +64,15 @@ for (const ver of versionDirs) {
64
64
  // Best-effort — don't fail the install
65
65
  }
66
66
  }
67
+
68
+ // Sync OpenCode: re-run installer if tool shim or plugin exists
69
+ const ocToolPath = path.join(os.homedir(), ".config", "opencode", "tool", "gmax.ts");
70
+ const ocPluginPath = path.join(os.homedir(), ".config", "opencode", "plugins", "gmax.ts");
71
+ if (fs.existsSync(ocToolPath) || fs.existsSync(ocPluginPath)) {
72
+ try {
73
+ const { execSync: exec } = require("node:child_process");
74
+ exec("gmax install-opencode", { stdio: "ignore", timeout: 10000 });
75
+ } catch {
76
+ // Best-effort — don't fail the install
77
+ }
78
+ }