gm-codex 2.0.197 → 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.
@@ -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,10 +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
  }
213
+ if (/^exec:pm2logs(\s|$)/.test(command)) {
214
+ const args = command.replace(/^exec:pm2logs\s*/, '').trim();
215
+ const pmArgs = args ? ['logs', '--nostream', '--lines', '50', args] : ['logs', '--nostream', '--lines', '50'];
216
+ const r = spawnSync('pm2', pmArgs, { encoding: 'utf-8', timeout: 15000, windowsHide: true });
217
+ return allowWithNoop(`exec:pm2logs output:\n\n${stripFooter((r.stdout || '') + (r.stderr || '')) || '(no logs)'}`);
218
+ }
219
+
91
220
  const execMatch = command.match(/^exec(?::(\S+))?\n([\s\S]+)$/);
92
221
  if (execMatch) {
93
222
  const rawLang = (execMatch[1] || '').toLowerCase();
@@ -102,85 +231,90 @@ const run = () => {
102
231
  if (/^\s*(echo |ls |cd |mkdir |rm |cat |grep |find |export |source |#!)/.test(src)) return 'bash';
103
232
  return 'nodejs';
104
233
  };
105
- 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' };
106
236
  const lang = aliases[rawLang] || rawLang || detectLang(code);
107
- const IS_WIN = process.platform === 'win32';
108
- 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
+
109
239
  const spawnDirect = (bin, args, stdin) => {
110
- 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 }) };
111
241
  const r = spawnSync(bin, args, opts);
112
242
  if (!r.stdout && !r.stderr && r.error) return `[spawn error: ${r.error.message}]`;
113
243
  const out = (r.stdout || '').trimEnd(), err = stripFooter(r.stderr || '').trimEnd();
114
244
  return out && err ? out + '\n[stderr]\n' + err : stripFooter(out || err);
115
245
  };
246
+
116
247
  const runWithFile = (l, src) => {
117
248
  const tmp = path.join(os.tmpdir(), `gm-exec-${Date.now()}.${langExts[l] || l}`);
118
249
  fs.writeFileSync(tmp, src, 'utf-8');
119
- 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 });
120
251
  try { fs.unlinkSync(tmp); } catch (e) {}
121
252
  let out = stripFooter((r.stdout || '') + (r.stderr || ''));
122
253
  const bg = out.match(/Task ID:\s*(task_\S+)/);
123
254
  if (bg) {
124
- spawnSync('bun', ['x', 'gm-exec', 'sleep', bg[1], '60'], { encoding: 'utf-8', timeout: 70000 });
125
- 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 });
126
257
  out = stripFooter((sr.stdout || '') + (sr.stderr || ''));
127
- spawnSync('bun', ['x', 'gm-exec', 'close', bg[1]], { encoding: 'utf-8', timeout: 10000 });
258
+ runGmExec(['close', bg[1]], { timeout: 10000 });
128
259
  }
129
260
  return out;
130
261
  };
262
+
131
263
  const decodeB64 = (s) => {
132
264
  const t = s.trim();
133
265
  if (t.length < 16 || t.length % 4 !== 0 || !/^[A-Za-z0-9+/\r\n]+=*$/.test(t)) return s;
134
266
  try { const d = Buffer.from(t, 'base64').toString('utf-8'); return /[\x00-\x08\x0b\x0e-\x1f]/.test(d) ? s : d; } catch { return s; }
135
267
  };
268
+
136
269
  const safeCode = decodeB64(code);
270
+
137
271
  if (['codesearch', 'search'].includes(lang)) {
138
272
  const query = safeCode.trim();
139
- const r = spawnSync('bun', ['x', 'codebasesearch', query], { encoding: 'utf-8', timeout: 30000, ...(cwd && { cwd }) });
273
+ const r = runLocal('codebasesearch', [query], { timeout: 30000, ...(cwd && { cwd }) });
140
274
  return allowWithNoop(`exec:${lang} output:\n\n${stripFooter((r.stdout || '') + (r.stderr || '')) || '(no results)'}`);
141
275
  }
142
276
  if (lang === 'status') {
143
- const taskId = safeCode.trim();
144
- const r = spawnSync('bun', ['x', 'gm-exec', 'status', taskId], { encoding: 'utf-8', timeout: 15000 });
277
+ const r = runGmExec(['status', safeCode.trim()], { timeout: 15000 });
145
278
  return allowWithNoop(`exec:status output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
146
279
  }
147
280
  if (lang === 'sleep') {
148
281
  const parts = safeCode.trim().split(/\s+/);
149
- const args = ['x', 'gm-exec', 'sleep', ...parts];
150
- const r = spawnSync('bun', args, { encoding: 'utf-8', timeout: 70000 });
282
+ const r = runGmExec(['sleep', ...parts], { timeout: 70000 });
151
283
  return allowWithNoop(`exec:sleep output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
152
284
  }
153
285
  if (lang === 'close') {
154
- const taskId = safeCode.trim();
155
- const r = spawnSync('bun', ['x', 'gm-exec', 'close', taskId], { encoding: 'utf-8', timeout: 15000 });
286
+ const r = runGmExec(['close', safeCode.trim()], { timeout: 15000 });
156
287
  return allowWithNoop(`exec:close output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
157
288
  }
158
289
  if (lang === 'runner') {
159
- const sub = safeCode.trim();
160
- const r = spawnSync('bun', ['x', 'gm-exec', 'runner', sub], { encoding: 'utf-8', timeout: 15000 });
290
+ const r = runGmExec(['runner', safeCode.trim()], { timeout: 15000 });
161
291
  return allowWithNoop(`exec:runner output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
162
292
  }
163
293
  if (lang === 'type') {
164
294
  const lines = safeCode.split(/\r?\n/);
165
295
  const taskId = lines[0].trim();
166
- const input = lines.slice(1).join('\n').trim();
167
- 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 });
168
298
  return allowWithNoop(`exec:type output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
169
299
  }
170
300
  if (lang === 'pm2list') {
171
- const r = spawnSync('bun', ['x', 'gm-exec', 'pm2list'], { encoding: 'utf-8', timeout: 15000 });
301
+ const r = runGmExec(['pm2list'], { timeout: 15000 });
172
302
  return allowWithNoop(`exec:pm2list output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
173
303
  }
304
+
174
305
  try {
175
306
  let result;
176
307
  if (lang === 'bash') {
177
- 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`);
178
309
  fs.writeFileSync(shFile, safeCode, 'utf-8');
179
- result = IS_WIN
180
- ? spawnDirect('powershell', ['-NoProfile', '-NonInteractive', '-File', shFile])
181
- : spawnDirect('bash', [shFile]);
310
+ result = spawnDirect('bash', [shFile]);
182
311
  try { fs.unlinkSync(shFile); } catch (e) {}
183
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)'}`);
184
318
  } else if (lang === 'python') {
185
319
  result = spawnDirect('python3', ['-c', safeCode]);
186
320
  if (!result || result.startsWith('[spawn error:')) result = spawnDirect('python', ['-c', safeCode]);
@@ -188,7 +322,12 @@ const run = () => {
188
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); } }`;
189
323
  result = runWithFile(lang || 'nodejs', wrapped);
190
324
  } else if (lang === 'agent-browser') {
191
- 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
+ }
192
331
  } else {
193
332
  result = runWithFile(lang, safeCode);
194
333
  }
@@ -200,8 +339,10 @@ const run = () => {
200
339
 
201
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)) {
202
341
  let helpText = '';
203
- try { helpText = '\n\n' + execSync('bun x gm-exec --help', { timeout: 10000 }).toString().trim(); } catch (e) {}
204
- 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.`);
205
346
  }
206
347
  }
207
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-codex",
3
- "version": "2.0.197",
3
+ "version": "2.0.200",
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.197",
3
+ "version": "2.0.200",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",