gm-cc 2.0.198 → 2.0.200
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/.claude-plugin/marketplace.json +1 -1
- package/hooks/pre-tool-use-hook.js +166 -31
- package/hooks/prompt-submit-hook.js +26 -14
- package/package.json +1 -1
- package/plugin.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "AnEntrypoint"
|
|
5
5
|
},
|
|
6
6
|
"description": "State machine agent with hooks, skills, and automated git enforcement",
|
|
7
|
-
"version": "2.0.
|
|
7
|
+
"version": "2.0.200",
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "State machine agent with hooks, skills, and automated git enforcement"
|
|
10
10
|
},
|
|
@@ -6,7 +6,115 @@ const os = require('os');
|
|
|
6
6
|
const { execSync, spawnSync } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const isGemini = process.env.GEMINI_PROJECT_DIR !== undefined;
|
|
9
|
+
const IS_WIN = process.platform === 'win32';
|
|
9
10
|
|
|
11
|
+
// ─── Local tool management ────────────────────────────────────────────────────
|
|
12
|
+
const TOOLS_DIR = path.join(os.homedir(), '.claude', 'gm-tools');
|
|
13
|
+
const CHECK_STAMP = path.join(TOOLS_DIR, '.last-check');
|
|
14
|
+
const PKG_JSON = path.join(TOOLS_DIR, 'package.json');
|
|
15
|
+
const MANAGED_PKGS = ['gm-exec', 'codebasesearch', 'mcp-thorns', 'agent-browser'];
|
|
16
|
+
const CHECK_INTERVAL_MS = 60 * 1000; // 60 seconds
|
|
17
|
+
|
|
18
|
+
function ensureToolsDir() {
|
|
19
|
+
try { fs.mkdirSync(TOOLS_DIR, { recursive: true }); } catch {}
|
|
20
|
+
if (!fs.existsSync(PKG_JSON)) {
|
|
21
|
+
try { fs.writeFileSync(PKG_JSON, JSON.stringify({ name: 'gm-tools', version: '1.0.0', private: true })); } catch {}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function localBin(name) {
|
|
26
|
+
const ext = IS_WIN ? '.exe' : '';
|
|
27
|
+
return path.join(TOOLS_DIR, 'node_modules', '.bin', name + ext);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isInstalled(name) {
|
|
31
|
+
return fs.existsSync(localBin(name));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function installPkg(name) {
|
|
35
|
+
try {
|
|
36
|
+
spawnSync('bun', ['add', name + '@latest'], {
|
|
37
|
+
cwd: TOOLS_DIR, encoding: 'utf8', timeout: 120000, windowsHide: true
|
|
38
|
+
});
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getInstalledVersion(name) {
|
|
43
|
+
try {
|
|
44
|
+
const p = path.join(TOOLS_DIR, 'node_modules', name, 'package.json');
|
|
45
|
+
return JSON.parse(fs.readFileSync(p, 'utf8')).version;
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getLatestVersion(name) {
|
|
50
|
+
try {
|
|
51
|
+
const https = require('https');
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const req = https.get(
|
|
54
|
+
`https://registry.npmjs.org/${name}/latest`,
|
|
55
|
+
{ headers: { Accept: 'application/json' } },
|
|
56
|
+
(res) => {
|
|
57
|
+
let d = '';
|
|
58
|
+
res.on('data', c => d += c);
|
|
59
|
+
res.on('end', () => { try { resolve(JSON.parse(d).version); } catch { resolve(null); } });
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
req.setTimeout(5000, () => { req.destroy(); resolve(null); });
|
|
63
|
+
req.on('error', () => resolve(null));
|
|
64
|
+
});
|
|
65
|
+
} catch { return Promise.resolve(null); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shouldCheck() {
|
|
69
|
+
try {
|
|
70
|
+
const t = parseInt(fs.readFileSync(CHECK_STAMP, 'utf8').trim(), 10);
|
|
71
|
+
return isNaN(t) || (Date.now() - t) > CHECK_INTERVAL_MS;
|
|
72
|
+
} catch { return true; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stampCheck() {
|
|
76
|
+
try { fs.writeFileSync(CHECK_STAMP, String(Date.now())); } catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function ensureTools() {
|
|
80
|
+
ensureToolsDir();
|
|
81
|
+
// Install any missing packages immediately (synchronous first-run)
|
|
82
|
+
const missing = MANAGED_PKGS.filter(p => !isInstalled(p));
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
try {
|
|
85
|
+
spawnSync('bun', ['add', ...missing.map(p => p + '@latest')], {
|
|
86
|
+
cwd: TOOLS_DIR, encoding: 'utf8', timeout: 180000, windowsHide: true
|
|
87
|
+
});
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
// Async version check (non-blocking — fire and forget)
|
|
91
|
+
if (shouldCheck()) {
|
|
92
|
+
stampCheck();
|
|
93
|
+
(async () => {
|
|
94
|
+
for (const name of MANAGED_PKGS) {
|
|
95
|
+
const installed = getInstalledVersion(name);
|
|
96
|
+
if (!installed) { installPkg(name); continue; }
|
|
97
|
+
const latest = await getLatestVersion(name);
|
|
98
|
+
if (latest && latest !== installed) installPkg(name);
|
|
99
|
+
}
|
|
100
|
+
})().catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Run tool installation (fire-and-forget, won't block hook)
|
|
105
|
+
ensureTools().catch(() => {});
|
|
106
|
+
|
|
107
|
+
// Helper: run a local binary (falls back to bunx if not installed)
|
|
108
|
+
function runLocal(name, args, opts = {}) {
|
|
109
|
+
const bin = localBin(name);
|
|
110
|
+
if (fs.existsSync(bin)) {
|
|
111
|
+
return spawnSync(bin, args, { encoding: 'utf8', windowsHide: true, timeout: 65000, ...opts });
|
|
112
|
+
}
|
|
113
|
+
// Fallback to bunx
|
|
114
|
+
return spawnSync('bun', ['x', name, ...args], { encoding: 'utf8', windowsHide: true, timeout: 65000, ...opts });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Hook helpers ─────────────────────────────────────────────────────────────
|
|
10
118
|
const writeTools = ['Write', 'write_file'];
|
|
11
119
|
const searchTools = ['glob', 'search_file_content', 'Search', 'search'];
|
|
12
120
|
const forbiddenTools = ['find', 'Find', 'Glob', 'Grep'];
|
|
@@ -17,12 +125,15 @@ const allow = (additionalContext) => ({
|
|
|
17
125
|
const deny = (reason) => isGemini
|
|
18
126
|
? { decision: 'deny', reason }
|
|
19
127
|
: { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } };
|
|
128
|
+
|
|
129
|
+
// Write output to tmp file and redirect the Bash command to read+delete it via bun (no popup)
|
|
20
130
|
const allowWithNoop = (context) => {
|
|
21
131
|
const tmp = path.join(os.tmpdir(), `gm-out-${Date.now()}.txt`);
|
|
22
132
|
fs.writeFileSync(tmp, context, 'utf-8');
|
|
23
|
-
|
|
133
|
+
// Use bun -e to read and print file — windowsHide applies to child, and bun itself is hidden
|
|
134
|
+
// cmd /c type also works without popup since cmd.exe is the host shell
|
|
24
135
|
const cmd = IS_WIN
|
|
25
|
-
? `
|
|
136
|
+
? `bun -e "process.stdout.write(require('fs').readFileSync(process.argv[1],'utf8'));require('fs').unlinkSync(process.argv[1])" "${tmp}"`
|
|
26
137
|
: `cat '${tmp}'; rm -f '${tmp}'`;
|
|
27
138
|
return {
|
|
28
139
|
hookSpecificOutput: {
|
|
@@ -33,6 +144,16 @@ const allowWithNoop = (context) => {
|
|
|
33
144
|
};
|
|
34
145
|
};
|
|
35
146
|
|
|
147
|
+
// ─── gm-exec runner helper ────────────────────────────────────────────────────
|
|
148
|
+
function runGmExec(args, opts = {}) {
|
|
149
|
+
const bin = localBin('gm-exec');
|
|
150
|
+
if (fs.existsSync(bin)) {
|
|
151
|
+
return spawnSync(bin, args, { encoding: 'utf8', windowsHide: true, timeout: 65000, ...opts });
|
|
152
|
+
}
|
|
153
|
+
return spawnSync('bun', ['x', 'gm-exec', ...args], { encoding: 'utf8', windowsHide: true, timeout: 65000, ...opts });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Main hook ────────────────────────────────────────────────────────────────
|
|
36
157
|
const run = () => {
|
|
37
158
|
try {
|
|
38
159
|
const input = fs.readFileSync(0, 'utf-8');
|
|
@@ -84,16 +205,18 @@ const run = () => {
|
|
|
84
205
|
if (tool_name === 'Bash') {
|
|
85
206
|
const command = (tool_input?.command || '').trim();
|
|
86
207
|
const stripFooter = (s) => s.replace(/\n\[Running tools\][\s\S]*$/, '').trimEnd();
|
|
208
|
+
|
|
87
209
|
if (/^exec:pm2list\s*$/.test(command)) {
|
|
88
|
-
const r =
|
|
210
|
+
const r = runGmExec(['pm2list']);
|
|
89
211
|
return allowWithNoop(`exec:pm2list output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
90
212
|
}
|
|
91
213
|
if (/^exec:pm2logs(\s|$)/.test(command)) {
|
|
92
214
|
const args = command.replace(/^exec:pm2logs\s*/, '').trim();
|
|
93
215
|
const pmArgs = args ? ['logs', '--nostream', '--lines', '50', args] : ['logs', '--nostream', '--lines', '50'];
|
|
94
|
-
const r = spawnSync('pm2', pmArgs, { encoding: 'utf-8', timeout: 15000 });
|
|
216
|
+
const r = spawnSync('pm2', pmArgs, { encoding: 'utf-8', timeout: 15000, windowsHide: true });
|
|
95
217
|
return allowWithNoop(`exec:pm2logs output:\n\n${stripFooter((r.stdout || '') + (r.stderr || '')) || '(no logs)'}`);
|
|
96
218
|
}
|
|
219
|
+
|
|
97
220
|
const execMatch = command.match(/^exec(?::(\S+))?\n([\s\S]+)$/);
|
|
98
221
|
if (execMatch) {
|
|
99
222
|
const rawLang = (execMatch[1] || '').toLowerCase();
|
|
@@ -108,85 +231,90 @@ const run = () => {
|
|
|
108
231
|
if (/^\s*(echo |ls |cd |mkdir |rm |cat |grep |find |export |source |#!)/.test(src)) return 'bash';
|
|
109
232
|
return 'nodejs';
|
|
110
233
|
};
|
|
111
|
-
|
|
234
|
+
// Note: 'cmd' is NOT aliased to 'bash' — it has its own handler below
|
|
235
|
+
const aliases = { js: 'nodejs', javascript: 'nodejs', ts: 'typescript', node: 'nodejs', py: 'python', sh: 'bash', shell: 'bash', zsh: 'bash', powershell: 'powershell', ps1: 'powershell', browser: 'agent-browser', ab: 'agent-browser', codesearch: 'codesearch', search: 'search', status: 'status', sleep: 'sleep', close: 'close', runner: 'runner', type: 'type', pm2list: 'pm2list' };
|
|
112
236
|
const lang = aliases[rawLang] || rawLang || detectLang(code);
|
|
113
|
-
const
|
|
114
|
-
|
|
237
|
+
const langExts = { nodejs: 'mjs', typescript: 'ts', deno: 'ts', python: 'py', bash: 'sh', powershell: 'ps1', go: 'go', rust: 'rs', c: 'c', cpp: 'cpp', java: 'java' };
|
|
238
|
+
|
|
115
239
|
const spawnDirect = (bin, args, stdin) => {
|
|
116
|
-
const opts = { encoding: 'utf-8', timeout: 60000, ...(cwd && { cwd }), ...(stdin !== undefined && { input: stdin }) };
|
|
240
|
+
const opts = { encoding: 'utf-8', timeout: 60000, windowsHide: true, ...(cwd && { cwd }), ...(stdin !== undefined && { input: stdin }) };
|
|
117
241
|
const r = spawnSync(bin, args, opts);
|
|
118
242
|
if (!r.stdout && !r.stderr && r.error) return `[spawn error: ${r.error.message}]`;
|
|
119
243
|
const out = (r.stdout || '').trimEnd(), err = stripFooter(r.stderr || '').trimEnd();
|
|
120
244
|
return out && err ? out + '\n[stderr]\n' + err : stripFooter(out || err);
|
|
121
245
|
};
|
|
246
|
+
|
|
122
247
|
const runWithFile = (l, src) => {
|
|
123
248
|
const tmp = path.join(os.tmpdir(), `gm-exec-${Date.now()}.${langExts[l] || l}`);
|
|
124
249
|
fs.writeFileSync(tmp, src, 'utf-8');
|
|
125
|
-
const r =
|
|
250
|
+
const r = runGmExec(['exec', `--lang=${l}`, `--file=${tmp}`, ...(cwd ? [`--cwd=${cwd}`] : [])], { timeout: 65000 });
|
|
126
251
|
try { fs.unlinkSync(tmp); } catch (e) {}
|
|
127
252
|
let out = stripFooter((r.stdout || '') + (r.stderr || ''));
|
|
128
253
|
const bg = out.match(/Task ID:\s*(task_\S+)/);
|
|
129
254
|
if (bg) {
|
|
130
|
-
|
|
131
|
-
const sr =
|
|
255
|
+
runGmExec(['sleep', bg[1], '60'], { timeout: 70000 });
|
|
256
|
+
const sr = runGmExec(['status', bg[1]], { timeout: 15000 });
|
|
132
257
|
out = stripFooter((sr.stdout || '') + (sr.stderr || ''));
|
|
133
|
-
|
|
258
|
+
runGmExec(['close', bg[1]], { timeout: 10000 });
|
|
134
259
|
}
|
|
135
260
|
return out;
|
|
136
261
|
};
|
|
262
|
+
|
|
137
263
|
const decodeB64 = (s) => {
|
|
138
264
|
const t = s.trim();
|
|
139
265
|
if (t.length < 16 || t.length % 4 !== 0 || !/^[A-Za-z0-9+/\r\n]+=*$/.test(t)) return s;
|
|
140
266
|
try { const d = Buffer.from(t, 'base64').toString('utf-8'); return /[\x00-\x08\x0b\x0e-\x1f]/.test(d) ? s : d; } catch { return s; }
|
|
141
267
|
};
|
|
268
|
+
|
|
142
269
|
const safeCode = decodeB64(code);
|
|
270
|
+
|
|
143
271
|
if (['codesearch', 'search'].includes(lang)) {
|
|
144
272
|
const query = safeCode.trim();
|
|
145
|
-
const r =
|
|
273
|
+
const r = runLocal('codebasesearch', [query], { timeout: 30000, ...(cwd && { cwd }) });
|
|
146
274
|
return allowWithNoop(`exec:${lang} output:\n\n${stripFooter((r.stdout || '') + (r.stderr || '')) || '(no results)'}`);
|
|
147
275
|
}
|
|
148
276
|
if (lang === 'status') {
|
|
149
|
-
const
|
|
150
|
-
const r = spawnSync('bun', ['x', 'gm-exec', 'status', taskId], { encoding: 'utf-8', timeout: 15000 });
|
|
277
|
+
const r = runGmExec(['status', safeCode.trim()], { timeout: 15000 });
|
|
151
278
|
return allowWithNoop(`exec:status output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
152
279
|
}
|
|
153
280
|
if (lang === 'sleep') {
|
|
154
281
|
const parts = safeCode.trim().split(/\s+/);
|
|
155
|
-
const
|
|
156
|
-
const r = spawnSync('bun', args, { encoding: 'utf-8', timeout: 70000 });
|
|
282
|
+
const r = runGmExec(['sleep', ...parts], { timeout: 70000 });
|
|
157
283
|
return allowWithNoop(`exec:sleep output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
158
284
|
}
|
|
159
285
|
if (lang === 'close') {
|
|
160
|
-
const
|
|
161
|
-
const r = spawnSync('bun', ['x', 'gm-exec', 'close', taskId], { encoding: 'utf-8', timeout: 15000 });
|
|
286
|
+
const r = runGmExec(['close', safeCode.trim()], { timeout: 15000 });
|
|
162
287
|
return allowWithNoop(`exec:close output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
163
288
|
}
|
|
164
289
|
if (lang === 'runner') {
|
|
165
|
-
const
|
|
166
|
-
const r = spawnSync('bun', ['x', 'gm-exec', 'runner', sub], { encoding: 'utf-8', timeout: 15000 });
|
|
290
|
+
const r = runGmExec(['runner', safeCode.trim()], { timeout: 15000 });
|
|
167
291
|
return allowWithNoop(`exec:runner output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
168
292
|
}
|
|
169
293
|
if (lang === 'type') {
|
|
170
294
|
const lines = safeCode.split(/\r?\n/);
|
|
171
295
|
const taskId = lines[0].trim();
|
|
172
|
-
const
|
|
173
|
-
const r =
|
|
296
|
+
const inputData = lines.slice(1).join('\n').trim();
|
|
297
|
+
const r = runGmExec(['type', taskId, inputData], { timeout: 15000 });
|
|
174
298
|
return allowWithNoop(`exec:type output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
175
299
|
}
|
|
176
300
|
if (lang === 'pm2list') {
|
|
177
|
-
const r =
|
|
301
|
+
const r = runGmExec(['pm2list'], { timeout: 15000 });
|
|
178
302
|
return allowWithNoop(`exec:pm2list output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
|
|
179
303
|
}
|
|
304
|
+
|
|
180
305
|
try {
|
|
181
306
|
let result;
|
|
182
307
|
if (lang === 'bash') {
|
|
183
|
-
const shFile = path.join(os.tmpdir(), `gm-exec-${Date.now()}
|
|
308
|
+
const shFile = path.join(os.tmpdir(), `gm-exec-${Date.now()}.sh`);
|
|
184
309
|
fs.writeFileSync(shFile, safeCode, 'utf-8');
|
|
185
|
-
result =
|
|
186
|
-
? spawnDirect('powershell', ['-NoProfile', '-NonInteractive', '-File', shFile])
|
|
187
|
-
: spawnDirect('bash', [shFile]);
|
|
310
|
+
result = spawnDirect('bash', [shFile]);
|
|
188
311
|
try { fs.unlinkSync(shFile); } catch (e) {}
|
|
189
312
|
if (!result || result.startsWith('[spawn error:')) result = runWithFile('bash', safeCode);
|
|
313
|
+
} else if (lang === 'cmd') {
|
|
314
|
+
// exec:cmd always runs cmd.exe /c — explicit Windows command prompt
|
|
315
|
+
result = spawnDirect('cmd.exe', ['/c', safeCode]);
|
|
316
|
+
if (!result || result.startsWith('[spawn error:')) result = runWithFile('cmd', safeCode);
|
|
317
|
+
return allowWithNoop(`exec:cmd output:\n\n${result || '(no output)'}`);
|
|
190
318
|
} else if (lang === 'python') {
|
|
191
319
|
result = spawnDirect('python3', ['-c', safeCode]);
|
|
192
320
|
if (!result || result.startsWith('[spawn error:')) result = spawnDirect('python', ['-c', safeCode]);
|
|
@@ -194,7 +322,12 @@ const run = () => {
|
|
|
194
322
|
const wrapped = `const __result = await (async () => {\n${safeCode}\n})();\nif (__result !== undefined) { if (typeof __result === 'object') { console.log(JSON.stringify(__result, null, 2)); } else { console.log(__result); } }`;
|
|
195
323
|
result = runWithFile(lang || 'nodejs', wrapped);
|
|
196
324
|
} else if (lang === 'agent-browser') {
|
|
197
|
-
|
|
325
|
+
const abBin = localBin('agent-browser');
|
|
326
|
+
if (fs.existsSync(abBin)) {
|
|
327
|
+
result = spawnDirect(abBin, ['eval', '--stdin'], safeCode);
|
|
328
|
+
} else {
|
|
329
|
+
result = spawnDirect('agent-browser', ['eval', '--stdin'], safeCode);
|
|
330
|
+
}
|
|
198
331
|
} else {
|
|
199
332
|
result = runWithFile(lang, safeCode);
|
|
200
333
|
}
|
|
@@ -206,8 +339,10 @@ const run = () => {
|
|
|
206
339
|
|
|
207
340
|
if (!/^exec(\s|:)/.test(command) && !/^bun x gm-exec(@[^\s]*)?(\s|$)/.test(command) && !/^git /.test(command) && !/^bun x codebasesearch/.test(command) && !/(\bclaude\b)/.test(command) && !/^npm install .* \/config\/.gmweb/.test(command) && !/^bun install --cwd \/config\/.gmweb/.test(command)) {
|
|
208
341
|
let helpText = '';
|
|
209
|
-
try { helpText = '\n\n' + execSync('
|
|
210
|
-
|
|
342
|
+
try { helpText = '\n\n' + execSync(`"${localBin('gm-exec')}" --help`, { timeout: 10000, windowsHide: true }).toString().trim(); } catch (e) {
|
|
343
|
+
try { helpText = '\n\n' + execSync('bun x gm-exec --help', { timeout: 10000, windowsHide: true }).toString().trim(); } catch {}
|
|
344
|
+
}
|
|
345
|
+
return deny(`Bash is restricted to exec:<lang> and git.\n\nexec:<lang> syntax (lang auto-detected if omitted):\n exec:nodejs / exec:python / exec:bash / exec:typescript\n exec:go / exec:rust / exec:java / exec:c / exec:cpp\n exec:cmd ← runs cmd.exe /c on Windows\n exec:agent-browser ← plain JS piped to browser eval (NO base64)\n exec ← auto-detects language\n\nTask management shortcuts (body = args):\n exec:status\n <task_id>\n\n exec:sleep\n <task_id> [seconds] [--next-output]\n\n exec:type\n <task_id>\n <input to send to stdin>\n\n exec:close\n <task_id>\n\n exec:runner\n start|stop|status\n\nCode search shortcut:\n exec:codesearch\n <natural language query>\n\nNEVER encode agent-browser code as base64 — pass plain JS directly.\n\nbun x gm-exec${helpText}\n\nAll other Bash commands are blocked.`);
|
|
211
346
|
}
|
|
212
347
|
}
|
|
213
348
|
|
|
@@ -7,7 +7,24 @@ if (process.env.AGENTGUI_SUBPROCESS === '1') {
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { execSync, spawnSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const IS_WIN = process.platform === 'win32';
|
|
14
|
+
const TOOLS_DIR = path.join(os.homedir(), '.claude', 'gm-tools');
|
|
15
|
+
|
|
16
|
+
function localBin(name) {
|
|
17
|
+
const ext = IS_WIN ? '.exe' : '';
|
|
18
|
+
return path.join(TOOLS_DIR, 'node_modules', '.bin', name + ext);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runLocal(name, args, opts = {}) {
|
|
22
|
+
const bin = localBin(name);
|
|
23
|
+
if (fs.existsSync(bin)) {
|
|
24
|
+
return spawnSync(bin, args, { encoding: 'utf8', windowsHide: true, timeout: 30000, ...opts });
|
|
25
|
+
}
|
|
26
|
+
return spawnSync('bun', ['x', name, ...args], { encoding: 'utf8', windowsHide: true, timeout: 30000, ...opts });
|
|
27
|
+
}
|
|
11
28
|
|
|
12
29
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.OC_PROJECT_DIR || process.env.KILO_PROJECT_DIR;
|
|
13
30
|
|
|
@@ -26,26 +43,21 @@ const ensureGitignore = () => {
|
|
|
26
43
|
|
|
27
44
|
const runThorns = () => {
|
|
28
45
|
if (!projectDir || !fs.existsSync(projectDir)) return '';
|
|
29
|
-
const localThorns = path.join(process.env.HOME || '/root', 'mcp-thorns', 'index.js');
|
|
30
|
-
const thornsBin = fs.existsSync(localThorns) ? `node ${localThorns}` : 'bun x mcp-thorns@latest';
|
|
31
46
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
return `=== mcp-thorns ===\n${out.trim()}`;
|
|
47
|
+
const r = runLocal('mcp-thorns', [projectDir], { timeout: 15000 });
|
|
48
|
+
const out = ((r.stdout || '') + (r.stderr || '')).trim();
|
|
49
|
+
return out ? `=== mcp-thorns ===\n${out}` : '';
|
|
36
50
|
} catch (e) {
|
|
37
|
-
return
|
|
51
|
+
return '';
|
|
38
52
|
}
|
|
39
53
|
};
|
|
40
54
|
|
|
41
55
|
const runCodeSearch = (prompt) => {
|
|
42
56
|
if (!prompt || !projectDir) return '';
|
|
43
57
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
return `=== codebasesearch ===\n${out.trim()}`;
|
|
58
|
+
const r = runLocal('codebasesearch', [prompt], { timeout: 10000, cwd: projectDir });
|
|
59
|
+
const out = ((r.stdout || '') + (r.stderr || '')).trim();
|
|
60
|
+
return out ? `=== codebasesearch ===\n${out}` : '';
|
|
49
61
|
} catch (e) {
|
|
50
62
|
return '';
|
|
51
63
|
}
|
|
@@ -67,7 +79,7 @@ const emit = (additionalContext) => {
|
|
|
67
79
|
try {
|
|
68
80
|
let prompt = '';
|
|
69
81
|
try {
|
|
70
|
-
const input = JSON.parse(fs.readFileSync(
|
|
82
|
+
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
|
|
71
83
|
prompt = input.prompt || input.message || input.userMessage || '';
|
|
72
84
|
} catch (e) {}
|
|
73
85
|
|
package/package.json
CHANGED