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.
- package/dist/commands/opencode.js +59 -97
- package/dist/commands/plugin.js +259 -0
- package/dist/commands/setup.js +9 -0
- package/dist/commands/watch.js +11 -0
- package/dist/config.js +1 -0
- package/dist/index.js +8 -1
- package/dist/lib/daemon/daemon.js +36 -7
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/scripts/postinstall.js +12 -0
|
@@ -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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
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([
|
|
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 {
|
|
137
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
package/dist/commands/setup.js
CHANGED
|
@@ -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/commands/watch.js
CHANGED
|
@@ -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
|
-
//
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
301
|
+
catch (_g) { }
|
|
273
302
|
try {
|
|
274
303
|
yield ((_c = this.vectorDb) === null || _c === void 0 ? void 0 : _c.close());
|
|
275
304
|
}
|
|
276
|
-
catch (
|
|
305
|
+
catch (_h) { }
|
|
277
306
|
console.log("[daemon] Shutdown complete");
|
|
278
307
|
});
|
|
279
308
|
}
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
+
}
|