grepmax 0.11.2 → 0.11.3

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,7 +19,8 @@ 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 {
@@ -42,85 +43,65 @@ const SKILL = \`
42
43
  ---
43
44
  name: gmax
44
45
  description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
45
- allowed-tools: "Bash(gmax:*), Read"
46
46
  ---
47
47
 
48
- ## What gmax does
48
+ ## When to use what
49
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
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>
54
57
 
55
58
  ## Primary command
56
59
 
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
60
+ Use --agent for compact, token-efficient output (one line per result):
85
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
86
65
 
87
- Read the specific line range, not the whole file.
66
+ Output: file:line symbol [ROLE] signature_hint
88
67
 
89
- ## Other commands
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>
90
69
 
91
- # Trace call graph (who calls X, what X calls)
92
- gmax trace handleAuth
70
+ ## Commands
93
71
 
94
- # Skeleton of a huge file (to find which ranges to read)
95
- gmax skeleton src/giant-2000-line-file.ts
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
96
79
 
97
- # Just file paths when you only need locations
98
- gmax "authentication" --compact
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
99
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
100
92
 
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
93
+ ### Management
94
+ gmax add # add + index current directory
95
+ gmax index # reindex current project
96
+ gmax doctor --fix # health check + auto-repair
112
97
 
113
98
  ## Tips
114
99
 
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
-
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
124
105
  \`;
125
106
 
126
107
  const GMAX_BIN = "${gmaxBin}";
@@ -129,7 +110,7 @@ export default tool({
129
110
  description: SKILL,
130
111
  args: {
131
112
  argv: tool.schema.array(tool.schema.string())
132
- .describe("Arguments for gmax, e.g. ['search', 'user auth']")
113
+ .describe("Arguments for gmax, e.g. ['search', 'user auth', '--agent']")
133
114
  },
134
115
  async execute({ argv }) {
135
116
  try {
@@ -142,7 +123,7 @@ export default tool({
142
123
  Output so far:
143
124
  \${text.trim()}
144
125
 
145
- PLEASE READ THE "Indexing" WARNING IN MY SKILL DESCRIPTION.\`;
126
+ Please wait for indexing to complete before searching.\`;
146
127
  }
147
128
  return text.trim();
148
129
  } catch (err) {
@@ -151,17 +132,62 @@ PLEASE READ THE "Indexing" WARNING IN MY SKILL DESCRIPTION.\`;
151
132
  },
152
133
  })`;
153
134
  }
135
+ function buildPluginContent(gmaxBin) {
136
+ return `import { execFileSync } from "node:child_process";
137
+ import { readFileSync } from "node:fs";
138
+ import { join } from "node:path";
139
+ import { homedir } from "node:os";
140
+
141
+ const GMAX_BIN = "${gmaxBin}";
142
+
143
+ function isProjectRegistered() {
144
+ try {
145
+ const projectsPath = join(homedir(), ".gmax", "projects.json");
146
+ const projects = JSON.parse(readFileSync(projectsPath, "utf-8"));
147
+ const cwd = process.cwd();
148
+ return projects.some((p) => cwd.startsWith(p.root));
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ 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
+ }
164
+
165
+ return {
166
+ "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 {}
174
+ },
175
+ };
176
+ };
177
+ `;
178
+ }
154
179
  function install() {
155
180
  return __awaiter(this, void 0, void 0, function* () {
181
+ var _a;
156
182
  try {
157
183
  // 1. Delete legacy plugin
158
- if (node_fs_1.default.existsSync(PLUGIN_PATH)) {
159
- 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);
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) { }
165
191
  }
166
192
  }
167
193
  // 2. Resolve absolute path to gmax binary
@@ -171,23 +197,22 @@ function install() {
171
197
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(TOOL_PATH), { recursive: true });
172
198
  node_fs_1.default.writeFileSync(TOOL_PATH, buildShimContent(gmaxBin));
173
199
  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));
200
+ // 4. Create plugin for daemon startup
201
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(PLUGIN_PATH), { recursive: true });
202
+ node_fs_1.default.writeFileSync(PLUGIN_PATH, buildPluginContent(gmaxBin));
203
+ console.log("✅ Created plugin at", PLUGIN_PATH);
204
+ // 5. Clean up stale MCP registration if present
205
+ if (node_fs_1.default.existsSync(CONFIG_PATH)) {
206
+ try {
207
+ const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
208
+ if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.gmax) {
209
+ delete config.mcp.gmax;
210
+ node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
211
+ console.log("✅ Removed stale MCP registration from", CONFIG_PATH);
212
+ }
213
+ }
214
+ catch (_c) { }
178
215
  }
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
216
  }
192
217
  catch (err) {
193
218
  console.error("❌ Installation failed:", err);
@@ -198,25 +223,30 @@ function uninstall() {
198
223
  return __awaiter(this, void 0, void 0, function* () {
199
224
  var _a;
200
225
  try {
201
- // 1. Remove shim
226
+ // 1. Remove tool shim
202
227
  if (node_fs_1.default.existsSync(TOOL_PATH)) {
203
228
  node_fs_1.default.unlinkSync(TOOL_PATH);
204
229
  console.log("✅ Removed tool shim.");
205
230
  }
206
- // 2. Unregister MCP
231
+ // 2. Remove plugin
232
+ if (node_fs_1.default.existsSync(PLUGIN_PATH)) {
233
+ node_fs_1.default.unlinkSync(PLUGIN_PATH);
234
+ console.log("✅ Removed plugin.");
235
+ }
236
+ // 3. Clean up legacy paths
237
+ if (node_fs_1.default.existsSync(LEGACY_PLUGIN_PATH)) {
238
+ node_fs_1.default.unlinkSync(LEGACY_PLUGIN_PATH);
239
+ console.log("✅ Cleaned up legacy plugin.");
240
+ }
241
+ // 4. Clean up MCP registration if present
207
242
  if (node_fs_1.default.existsSync(CONFIG_PATH)) {
208
243
  const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
209
244
  if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.gmax) {
210
245
  delete config.mcp.gmax;
211
246
  node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
212
- console.log("✅ Unregistered MCP server.");
247
+ console.log("✅ Removed MCP registration.");
213
248
  }
214
249
  }
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
250
  }
221
251
  catch (err) {
222
252
  console.error("❌ Uninstall failed:", err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
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.11.3",
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",