gm-cc 2.0.199 → 2.0.201

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.
@@ -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.199",
7
+ "version": "2.0.201",
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
- const IS_WIN = process.platform === 'win32';
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
- ? `powershell -NoProfile -NonInteractive -Command "Get-Content -Raw '${tmp}'; Remove-Item '${tmp}'"`
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 = spawnSync('bun', ['x', 'gm-exec', 'pm2list'], { encoding: 'utf-8', timeout: 15000 });
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
- const aliases = { js: 'nodejs', javascript: 'nodejs', ts: 'typescript', node: 'nodejs', py: 'python', sh: 'bash', shell: 'bash', zsh: 'bash', powershell: 'bash', ps1: 'bash', cmd: 'bash', browser: 'agent-browser', ab: 'agent-browser', codesearch: 'codesearch', search: 'search', status: 'status', sleep: 'sleep', close: 'close', runner: 'runner', type: 'type', pm2list: 'pm2list' };
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 IS_WIN = process.platform === 'win32';
114
- const langExts = { nodejs: 'mjs', typescript: 'ts', deno: 'ts', python: 'py', bash: IS_WIN ? 'ps1' : 'sh', go: 'go', rust: 'rs', c: 'c', cpp: 'cpp', java: 'java' };
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 = spawnSync('bun', ['x', 'gm-exec', 'exec', `--lang=${l}`, `--file=${tmp}`, ...(cwd ? [`--cwd=${cwd}`] : [])], { encoding: 'utf-8', timeout: 65000 });
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
- spawnSync('bun', ['x', 'gm-exec', 'sleep', bg[1], '60'], { encoding: 'utf-8', timeout: 70000 });
131
- const sr = spawnSync('bun', ['x', 'gm-exec', 'status', bg[1]], { encoding: 'utf-8', timeout: 15000 });
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
- spawnSync('bun', ['x', 'gm-exec', 'close', bg[1]], { encoding: 'utf-8', timeout: 10000 });
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 = spawnSync('bun', ['x', 'codebasesearch', query], { encoding: 'utf-8', timeout: 30000, ...(cwd && { cwd }) });
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 taskId = safeCode.trim();
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 args = ['x', 'gm-exec', 'sleep', ...parts];
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 taskId = safeCode.trim();
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 sub = safeCode.trim();
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 input = lines.slice(1).join('\n').trim();
173
- const r = spawnSync('bun', ['x', 'gm-exec', 'type', taskId, input], { encoding: 'utf-8', timeout: 15000 });
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 = spawnSync('bun', ['x', 'gm-exec', 'pm2list'], { encoding: 'utf-8', timeout: 15000 });
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()}.${IS_WIN ? 'ps1' : 'sh'}`);
308
+ const shFile = path.join(os.tmpdir(), `gm-exec-${Date.now()}.sh`);
184
309
  fs.writeFileSync(shFile, safeCode, 'utf-8');
185
- result = IS_WIN
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
- result = spawnDirect('agent-browser', ['eval', '--stdin'], safeCode);
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('bun x gm-exec --help', { timeout: 10000 }).toString().trim(); } catch (e) {}
210
- 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: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.`);
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 { execSync } = require('child_process');
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 out = execSync(`${thornsBin} ${projectDir}`, {
33
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000, killSignal: 'SIGTERM'
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 e.killed ? '=== mcp-thorns ===\nSkipped (timeout)' : '';
51
+ return '';
38
52
  }
39
53
  };
40
54
 
41
55
  const runCodeSearch = (prompt) => {
42
56
  if (!prompt || !projectDir) return '';
43
57
  try {
44
- const out = execSync(`bun x codebasesearch ${JSON.stringify(prompt)}`, {
45
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000, killSignal: 'SIGTERM',
46
- cwd: projectDir
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('/dev/stdin', 'utf-8'));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-cc",
3
- "version": "2.0.199",
3
+ "version": "2.0.201",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.199",
3
+ "version": "2.0.201",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",