grepmax 0.11.2 → 0.12.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.
@@ -19,14 +19,14 @@ const node_os_1 = __importDefault(require("node:os"));
19
19
  const node_path_1 = __importDefault(require("node:path"));
20
20
  const commander_1 = require("commander");
21
21
  const TOOL_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "tool", "gmax.ts");
22
- const PLUGIN_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "plugin", "gmax.ts");
22
+ const PLUGIN_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "plugins", "gmax.ts");
23
+ const LEGACY_PLUGIN_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "plugin", "gmax.ts");
23
24
  const CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
24
25
  function resolveGmaxBin() {
25
26
  try {
26
27
  return (0, node_child_process_1.execSync)("which gmax", { encoding: "utf-8" }).trim();
27
28
  }
28
29
  catch (_a) {
29
- // Fall back to the path of the current process entry point
30
30
  const binDir = node_path_1.default.dirname(process.argv[1]);
31
31
  const candidate = node_path_1.default.join(binDir, "gmax");
32
32
  if (node_fs_1.default.existsSync(candidate))
@@ -37,104 +37,44 @@ function resolveGmaxBin() {
37
37
  function buildShimContent(gmaxBin) {
38
38
  return `
39
39
  import { tool } from "@opencode-ai/plugin";
40
-
41
- const SKILL = \`
42
- ---
43
- name: gmax
44
- description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
45
- allowed-tools: "Bash(gmax:*), Read"
46
- ---
47
-
48
- ## What gmax does
49
-
50
- Finds code by meaning. When you'd ask a colleague "where do we handle auth?", use gmax.
51
-
52
- - grep/ripgrep: exact string match, fast
53
- - gmax: concept match, finds code you couldn't grep for
54
-
55
- ## Primary command
56
-
57
- gmax "where do we validate user permissions"
58
-
59
-
60
- Returns ~10 results with code snippets (15+ lines each). Usually enough to understand what's happening.
61
-
62
- ## Output explained
63
-
64
- ORCHESTRATION src/auth/handler.ts:45
65
- Defines: handleAuth | Calls: validate, checkRole, respond | Score: .94
66
-
67
- export async function handleAuth(req: Request) {
68
- const token = req.headers.get("Authorization");
69
- const claims = await validateToken(token);
70
- if (!claims) return unauthorized();
71
- const allowed = await checkRole(claims.role, req.path);
72
- ...
73
-
74
- - **ORCHESTRATION** = contains logic, coordinates other code
75
- - **DEFINITION** = types, interfaces, classes
76
- - **Score** = relevance (1 = best match)
77
- - **Calls** = what this code calls (helps you trace flow)
78
-
79
- ## When to Read more
80
-
81
- The snippet often has enough context. But if you need more:
82
-
83
- # gmax found src/auth/handler.ts:45-90 as ORCH
84
- Read src/auth/handler.ts:45-120
85
-
86
-
87
- Read the specific line range, not the whole file.
88
-
89
- ## Other commands
90
-
91
- # Trace call graph (who calls X, what X calls)
92
- gmax trace handleAuth
93
-
94
- # Skeleton of a huge file (to find which ranges to read)
95
- gmax skeleton src/giant-2000-line-file.ts
96
-
97
- # Just file paths when you only need locations
98
- gmax "authentication" --compact
99
-
100
-
101
- ## Workflow: architecture questions
102
-
103
- # 1. Find entry points
104
- gmax "where do requests enter the server"
105
- # Review the ORCH results - code is shown
106
-
107
- # 2. If you need deeper context on a specific function
108
- Read src/server/handler.ts:45-120
109
-
110
- # 3. Trace to understand call flow
111
- gmax trace handleRequest
112
-
113
- ## Tips
114
-
115
- - More words = better results. "auth" is vague. "where does the server validate JWT tokens" is specific.
116
- - ORCH results contain the logic - prioritize these
117
- - Don't read entire files. Use the line ranges gmax gives you.
118
- - If results seem off, rephrase your query like you'd ask a teammate
119
-
120
- ## If Index is Building
121
-
122
- If you see "Indexing" or "Syncing": STOP. Tell the user the index is building. Ask if they want to wait or proceed with partial results.
123
-
124
- \`;
125
-
126
- const GMAX_BIN = "${gmaxBin}";
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
+ }
57
+
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
+ })();
127
67
 
128
68
  export default tool({
129
- description: SKILL,
69
+ description: _gmax.skill,
130
70
  args: {
131
71
  argv: tool.schema.array(tool.schema.string())
132
- .describe("Arguments for gmax, e.g. ['search', 'user auth']")
72
+ .describe("Arguments for gmax, e.g. ['search', 'user auth', '--agent']")
133
73
  },
134
74
  async execute({ argv }) {
135
75
  try {
136
76
  // @ts-ignore
137
- const out = await Bun.spawn([GMAX_BIN, ...argv], { stdout: "pipe" }).stdout;
77
+ const out = await Bun.spawn([_gmax.bin, ...argv], { stdout: "pipe" }).stdout;
138
78
  const text = await new Response(out).text();
139
79
  if (text.includes("Indexing") || text.includes("Building") || text.includes("Syncing")) {
140
80
  return \`WARN: The index is currently updating.
@@ -142,7 +82,7 @@ export default tool({
142
82
  Output so far:
143
83
  \${text.trim()}
144
84
 
145
- PLEASE READ THE "Indexing" WARNING IN MY SKILL DESCRIPTION.\`;
85
+ Please wait for indexing to complete before searching.\`;
146
86
  }
147
87
  return text.trim();
148
88
  } catch (err) {
@@ -151,43 +91,90 @@ PLEASE READ THE "Indexing" WARNING IN MY SKILL DESCRIPTION.\`;
151
91
  },
152
92
  })`;
153
93
  }
94
+ function buildPluginContent(gmaxBin) {
95
+ return `import { existsSync, readFileSync, realpathSync } from "node:fs";
96
+ import { execFileSync } from "node:child_process";
97
+ import { join, resolve, dirname } from "node:path";
98
+ import { homedir } from "node:os";
99
+
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
+ }
109
+
110
+ function isProjectRegistered() {
111
+ try {
112
+ const projectsPath = join(homedir(), ".gmax", "projects.json");
113
+ const projects = JSON.parse(readFileSync(projectsPath, "utf-8"));
114
+ const cwd = process.cwd();
115
+ return projects.some((p) => cwd.startsWith(p.root));
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
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
+
132
+ export const GmaxPlugin = async () => {
133
+ startDaemon();
134
+
135
+ return {
136
+ "session.created": async () => {
137
+ startDaemon();
138
+ },
139
+ };
140
+ };
141
+ `;
142
+ }
154
143
  function install() {
155
144
  return __awaiter(this, void 0, void 0, function* () {
145
+ var _a;
156
146
  try {
157
147
  // 1. Delete legacy plugin
158
- if (node_fs_1.default.existsSync(PLUGIN_PATH)) {
148
+ if (node_fs_1.default.existsSync(LEGACY_PLUGIN_PATH)) {
159
149
  try {
160
- node_fs_1.default.unlinkSync(PLUGIN_PATH);
161
- console.log("Deleted legacy plugin at", PLUGIN_PATH);
162
- }
163
- catch (e) {
164
- console.warn("mnt: Failed to delete legacy plugin:", e);
150
+ node_fs_1.default.unlinkSync(LEGACY_PLUGIN_PATH);
151
+ console.log("Deleted legacy plugin at", LEGACY_PLUGIN_PATH);
165
152
  }
153
+ catch (_b) { }
166
154
  }
167
155
  // 2. Resolve absolute path to gmax binary
168
156
  const gmaxBin = resolveGmaxBin();
169
157
  console.log(` Resolved gmax binary: ${gmaxBin}`);
170
- // 3. Create tool shim
158
+ // 3. Create tool shim (reads SKILL dynamically from package root)
171
159
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(TOOL_PATH), { recursive: true });
172
160
  node_fs_1.default.writeFileSync(TOOL_PATH, buildShimContent(gmaxBin));
173
161
  console.log("✅ Created tool shim at", TOOL_PATH);
174
- // 4. Register MCP
175
- if (!node_fs_1.default.existsSync(CONFIG_PATH)) {
176
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(CONFIG_PATH), { recursive: true });
177
- node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify({}, null, 2));
162
+ // 4. Create plugin for daemon startup
163
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(PLUGIN_PATH), { recursive: true });
164
+ node_fs_1.default.writeFileSync(PLUGIN_PATH, buildPluginContent(gmaxBin));
165
+ console.log("✅ Created plugin at", PLUGIN_PATH);
166
+ // 5. Clean up stale MCP registration if present
167
+ if (node_fs_1.default.existsSync(CONFIG_PATH)) {
168
+ try {
169
+ const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
170
+ if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.gmax) {
171
+ delete config.mcp.gmax;
172
+ node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
173
+ console.log("✅ Removed stale MCP registration from", CONFIG_PATH);
174
+ }
175
+ }
176
+ catch (_c) { }
178
177
  }
179
- const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
180
- if (!config.$schema)
181
- config.$schema = "https://opencode.ai/config.json";
182
- if (!config.mcp)
183
- config.mcp = {};
184
- config.mcp.gmax = {
185
- type: "local",
186
- command: [gmaxBin, "mcp"],
187
- enabled: true,
188
- };
189
- node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
190
- console.log("✅ Registered MCP server in", CONFIG_PATH);
191
178
  }
192
179
  catch (err) {
193
180
  console.error("❌ Installation failed:", err);
@@ -198,25 +185,30 @@ function uninstall() {
198
185
  return __awaiter(this, void 0, void 0, function* () {
199
186
  var _a;
200
187
  try {
201
- // 1. Remove shim
188
+ // 1. Remove tool shim
202
189
  if (node_fs_1.default.existsSync(TOOL_PATH)) {
203
190
  node_fs_1.default.unlinkSync(TOOL_PATH);
204
191
  console.log("✅ Removed tool shim.");
205
192
  }
206
- // 2. Unregister MCP
193
+ // 2. Remove plugin
194
+ if (node_fs_1.default.existsSync(PLUGIN_PATH)) {
195
+ node_fs_1.default.unlinkSync(PLUGIN_PATH);
196
+ console.log("✅ Removed plugin.");
197
+ }
198
+ // 3. Clean up legacy paths
199
+ if (node_fs_1.default.existsSync(LEGACY_PLUGIN_PATH)) {
200
+ node_fs_1.default.unlinkSync(LEGACY_PLUGIN_PATH);
201
+ console.log("✅ Cleaned up legacy plugin.");
202
+ }
203
+ // 4. Clean up MCP registration if present
207
204
  if (node_fs_1.default.existsSync(CONFIG_PATH)) {
208
205
  const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
209
206
  if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.gmax) {
210
207
  delete config.mcp.gmax;
211
208
  node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
212
- console.log("✅ Unregistered MCP server.");
209
+ console.log("✅ Removed MCP registration.");
213
210
  }
214
211
  }
215
- // Cleanup plugin just in case
216
- if (node_fs_1.default.existsSync(PLUGIN_PATH)) {
217
- node_fs_1.default.unlinkSync(PLUGIN_PATH);
218
- console.log("✅ Cleaned up plugin file.");
219
- }
220
212
  }
221
213
  catch (err) {
222
214
  console.error("❌ Uninstall failed:", err);
@@ -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
  }));
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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.2",
3
+ "version": "0.12.0",
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
+ }