@probelabs/probe 0.6.0-rc121 → 0.6.0-rc122
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/README.md +36 -2
- package/build/agent/ProbeAgent.js +50 -14
- package/build/agent/index.js +182 -48
- package/build/agent/mcp/client.js +69 -15
- package/build/agent/mcp/config.js +8 -8
- package/build/agent/mcp/xmlBridge.js +52 -7
- package/build/agent/schemaUtils.js +28 -15
- package/build/grep.js +152 -0
- package/build/index.js +2 -0
- package/build/mcp/index.js +105 -87
- package/build/mcp/index.ts +120 -97
- package/cjs/agent/ProbeAgent.cjs +191 -57
- package/cjs/index.cjs +249 -57
- package/index.d.ts +45 -0
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +50 -14
- package/src/agent/index.js +1 -1
- package/src/agent/mcp/client.js +69 -15
- package/src/agent/mcp/config.js +8 -8
- package/src/agent/mcp/xmlBridge.js +52 -7
- package/src/agent/schemaUtils.js +28 -15
- package/src/grep.js +152 -0
- package/src/index.js +2 -0
- package/src/mcp/index.ts +120 -97
|
@@ -51,8 +51,8 @@ export function loadMCPConfigurationFromPath(configPath) {
|
|
|
51
51
|
const content = readFileSync(configPath, 'utf8');
|
|
52
52
|
const config = JSON.parse(content);
|
|
53
53
|
|
|
54
|
-
if (process.env.DEBUG === '1') {
|
|
55
|
-
console.error(`[MCP] Loaded configuration from: ${configPath}`);
|
|
54
|
+
if (process.env.DEBUG === '1' || process.env.DEBUG_MCP === '1') {
|
|
55
|
+
console.error(`[MCP DEBUG] Loaded configuration from: ${configPath}`);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Merge with environment variable overrides
|
|
@@ -94,12 +94,12 @@ export function loadMCPConfiguration() {
|
|
|
94
94
|
try {
|
|
95
95
|
const content = readFileSync(configPath, 'utf8');
|
|
96
96
|
config = JSON.parse(content);
|
|
97
|
-
if (process.env.DEBUG === '1') {
|
|
98
|
-
console.error(`[MCP] Loaded configuration from: ${configPath}`);
|
|
97
|
+
if (process.env.DEBUG === '1' || process.env.DEBUG_MCP === '1') {
|
|
98
|
+
console.error(`[MCP DEBUG] Loaded configuration from: ${configPath}`);
|
|
99
99
|
}
|
|
100
100
|
break;
|
|
101
101
|
} catch (error) {
|
|
102
|
-
console.error(`[MCP] Failed to parse config from ${configPath}:`, error.message);
|
|
102
|
+
console.error(`[MCP ERROR] Failed to parse config from ${configPath}:`, error.message);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -209,12 +209,12 @@ export function parseEnabledServers(config) {
|
|
|
209
209
|
// Validate required fields based on transport
|
|
210
210
|
if (server.transport === 'stdio') {
|
|
211
211
|
if (!server.command) {
|
|
212
|
-
console.error(`[MCP] Server ${name} missing required 'command' for stdio transport`);
|
|
212
|
+
console.error(`[MCP ERROR] Server ${name} missing required 'command' for stdio transport`);
|
|
213
213
|
continue;
|
|
214
214
|
}
|
|
215
215
|
} else if (['websocket', 'sse', 'http'].includes(server.transport)) {
|
|
216
216
|
if (!server.url) {
|
|
217
|
-
console.error(`[MCP] Server ${name} missing required 'url' for ${server.transport} transport`);
|
|
217
|
+
console.error(`[MCP ERROR] Server ${name} missing required 'url' for ${server.transport} transport`);
|
|
218
218
|
continue;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -301,7 +301,7 @@ export function saveConfig(config, path) {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
writeFileSync(path, JSON.stringify(config, null, 2), 'utf8');
|
|
304
|
-
console.
|
|
304
|
+
console.error(`[MCP INFO] Configuration saved to: ${path}`);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
export default {
|
|
@@ -141,23 +141,44 @@ export class MCPXmlBridge {
|
|
|
141
141
|
|
|
142
142
|
if (!config) {
|
|
143
143
|
// No config provided - fall back to auto-discovery for backward compatibility
|
|
144
|
+
if (this.debug) {
|
|
145
|
+
console.error('[MCP DEBUG] No config provided, attempting auto-discovery...');
|
|
146
|
+
}
|
|
144
147
|
mcpConfigs = loadMCPConfiguration();
|
|
148
|
+
|
|
149
|
+
// Check if auto-discovery found anything
|
|
150
|
+
if (!mcpConfigs || !mcpConfigs.mcpServers || Object.keys(mcpConfigs.mcpServers).length === 0) {
|
|
151
|
+
console.error('[MCP WARNING] MCP enabled but no configuration found');
|
|
152
|
+
console.error('[MCP INFO] To use MCP, provide configuration via:');
|
|
153
|
+
console.error('[MCP INFO] - mcpConfig option when creating ProbeAgent');
|
|
154
|
+
console.error('[MCP INFO] - mcpConfigPath option pointing to a config file');
|
|
155
|
+
console.error('[MCP INFO] - Config file in standard locations (~/.mcp/config.json, etc.)');
|
|
156
|
+
console.error('[MCP INFO] - Environment variable MCP_CONFIG_PATH');
|
|
157
|
+
}
|
|
145
158
|
} else if (Array.isArray(config)) {
|
|
146
159
|
// Deprecated: Array of server configs (backward compatibility)
|
|
160
|
+
if (this.debug) {
|
|
161
|
+
console.error('[MCP DEBUG] Using deprecated array config format (consider using mcpConfig object)');
|
|
162
|
+
}
|
|
147
163
|
mcpConfigs = { mcpServers: config };
|
|
148
164
|
} else {
|
|
149
165
|
// New: Full config object provided directly
|
|
166
|
+
if (this.debug) {
|
|
167
|
+
console.error('[MCP DEBUG] Using provided MCP config object');
|
|
168
|
+
}
|
|
150
169
|
mcpConfigs = config;
|
|
151
170
|
}
|
|
152
171
|
|
|
153
172
|
if (!mcpConfigs || !mcpConfigs.mcpServers || Object.keys(mcpConfigs.mcpServers).length === 0) {
|
|
154
|
-
|
|
155
|
-
console.error('[MCP] No MCP servers configured');
|
|
156
|
-
}
|
|
173
|
+
console.error('[MCP INFO] 0 MCP tools available');
|
|
157
174
|
return;
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
try {
|
|
178
|
+
if (this.debug) {
|
|
179
|
+
console.error('[MCP DEBUG] Initializing MCP client manager...');
|
|
180
|
+
}
|
|
181
|
+
|
|
161
182
|
// Initialize the MCP client manager
|
|
162
183
|
this.mcpManager = new MCPClientManager({ debug: this.debug });
|
|
163
184
|
const result = await this.mcpManager.initialize(mcpConfigs);
|
|
@@ -165,17 +186,27 @@ export class MCPXmlBridge {
|
|
|
165
186
|
// Get tools from the manager
|
|
166
187
|
const vercelTools = this.mcpManager.getVercelTools();
|
|
167
188
|
this.mcpTools = vercelTools;
|
|
189
|
+
const toolCount = Object.keys(vercelTools).length;
|
|
168
190
|
|
|
169
191
|
// Generate XML definitions for all tools
|
|
170
192
|
for (const [name, tool] of Object.entries(vercelTools)) {
|
|
171
193
|
this.xmlDefinitions[name] = mcpToolToXmlDefinition(name, tool);
|
|
172
194
|
}
|
|
173
195
|
|
|
174
|
-
if (
|
|
175
|
-
console.error(
|
|
196
|
+
if (toolCount === 0) {
|
|
197
|
+
console.error('[MCP INFO] MCP initialization complete: 0 tools loaded');
|
|
198
|
+
} else {
|
|
199
|
+
console.error(`[MCP INFO] MCP initialization complete: ${toolCount} tool${toolCount !== 1 ? 's' : ''} loaded from ${result.connected} server${result.connected !== 1 ? 's' : ''}`);
|
|
200
|
+
|
|
201
|
+
if (this.debug) {
|
|
202
|
+
console.error('[MCP DEBUG] Tool definitions generated for XML bridge');
|
|
203
|
+
}
|
|
176
204
|
}
|
|
177
205
|
} catch (error) {
|
|
178
|
-
console.error('[MCP] Failed to initialize MCP connections:', error);
|
|
206
|
+
console.error('[MCP ERROR] Failed to initialize MCP connections:', error.message);
|
|
207
|
+
if (this.debug) {
|
|
208
|
+
console.error('[MCP DEBUG] Full error details:', error);
|
|
209
|
+
}
|
|
179
210
|
}
|
|
180
211
|
}
|
|
181
212
|
|
|
@@ -204,28 +235,42 @@ export class MCPXmlBridge {
|
|
|
204
235
|
const parsed = parseXmlMcpToolCall(xmlString, this.getToolNames());
|
|
205
236
|
|
|
206
237
|
if (!parsed) {
|
|
238
|
+
console.error('[MCP ERROR] No valid MCP tool call found in XML');
|
|
207
239
|
throw new Error('No valid MCP tool call found in XML');
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
const { toolName, params } = parsed;
|
|
211
243
|
|
|
212
244
|
if (this.debug) {
|
|
213
|
-
console.error(`[MCP] Executing MCP tool: ${toolName}
|
|
245
|
+
console.error(`[MCP DEBUG] Executing MCP tool: ${toolName}`);
|
|
246
|
+
console.error(`[MCP DEBUG] Parameters:`, JSON.stringify(params, null, 2));
|
|
214
247
|
}
|
|
215
248
|
|
|
216
249
|
const tool = this.mcpTools[toolName];
|
|
217
250
|
if (!tool) {
|
|
251
|
+
console.error(`[MCP ERROR] Unknown MCP tool: ${toolName}`);
|
|
252
|
+
console.error(`[MCP ERROR] Available tools: ${this.getToolNames().join(', ')}`);
|
|
218
253
|
throw new Error(`Unknown MCP tool: ${toolName}`);
|
|
219
254
|
}
|
|
220
255
|
|
|
221
256
|
try {
|
|
222
257
|
const result = await tool.execute(params);
|
|
258
|
+
|
|
259
|
+
if (this.debug) {
|
|
260
|
+
console.error(`[MCP DEBUG] Tool ${toolName} executed successfully`);
|
|
261
|
+
}
|
|
262
|
+
|
|
223
263
|
return {
|
|
224
264
|
success: true,
|
|
225
265
|
toolName,
|
|
226
266
|
result
|
|
227
267
|
};
|
|
228
268
|
} catch (error) {
|
|
269
|
+
console.error(`[MCP ERROR] Tool ${toolName} execution failed:`, error.message);
|
|
270
|
+
if (this.debug) {
|
|
271
|
+
console.error(`[MCP DEBUG] Full error details:`, error);
|
|
272
|
+
}
|
|
273
|
+
|
|
229
274
|
return {
|
|
230
275
|
success: false,
|
|
231
276
|
toolName,
|
|
@@ -40,7 +40,7 @@ export function decodeHtmlEntities(text) {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Clean AI response by extracting JSON content when response contains JSON
|
|
43
|
-
* Only processes responses that contain JSON structures { or [
|
|
43
|
+
* Only processes responses that contain JSON structures { or [
|
|
44
44
|
* @param {string} response - Raw AI response
|
|
45
45
|
* @returns {string} - Cleaned response with JSON boundaries extracted if applicable
|
|
46
46
|
*/
|
|
@@ -50,34 +50,47 @@ export function cleanSchemaResponse(response) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const trimmed = response.trim();
|
|
53
|
-
|
|
54
|
-
// First, look for JSON after code block markers
|
|
53
|
+
|
|
54
|
+
// First, look for JSON after code block markers - similar to mermaid extraction
|
|
55
|
+
// Try with json language specifier
|
|
56
|
+
const jsonBlockMatch = trimmed.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
57
|
+
if (jsonBlockMatch) {
|
|
58
|
+
return jsonBlockMatch[1].trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Try any code block with JSON content
|
|
62
|
+
const anyBlockMatch = trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
|
|
63
|
+
if (anyBlockMatch) {
|
|
64
|
+
return anyBlockMatch[1].trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Legacy patterns for more specific matching
|
|
55
68
|
const codeBlockPatterns = [
|
|
56
69
|
/```json\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/,
|
|
57
70
|
/```\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/,
|
|
58
71
|
/`([{\[][\s\S]*?[}\]])`/
|
|
59
72
|
];
|
|
60
|
-
|
|
73
|
+
|
|
61
74
|
for (const pattern of codeBlockPatterns) {
|
|
62
75
|
const match = trimmed.match(pattern);
|
|
63
76
|
if (match) {
|
|
64
77
|
return match[1].trim();
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
|
-
|
|
80
|
+
|
|
68
81
|
// Look for code block start followed immediately by JSON
|
|
69
82
|
const codeBlockStartPattern = /```(?:json)?\s*\n?\s*([{\[])/;
|
|
70
83
|
const codeBlockMatch = trimmed.match(codeBlockStartPattern);
|
|
71
|
-
|
|
84
|
+
|
|
72
85
|
if (codeBlockMatch) {
|
|
73
86
|
const startIndex = codeBlockMatch.index + codeBlockMatch[0].length - 1; // Position of the bracket
|
|
74
|
-
|
|
87
|
+
|
|
75
88
|
// Find the matching closing bracket
|
|
76
89
|
const openChar = codeBlockMatch[1];
|
|
77
90
|
const closeChar = openChar === '{' ? '}' : ']';
|
|
78
91
|
let bracketCount = 1;
|
|
79
92
|
let endIndex = startIndex + 1;
|
|
80
|
-
|
|
93
|
+
|
|
81
94
|
while (endIndex < trimmed.length && bracketCount > 0) {
|
|
82
95
|
const char = trimmed[endIndex];
|
|
83
96
|
if (char === openChar) {
|
|
@@ -87,36 +100,36 @@ export function cleanSchemaResponse(response) {
|
|
|
87
100
|
}
|
|
88
101
|
endIndex++;
|
|
89
102
|
}
|
|
90
|
-
|
|
103
|
+
|
|
91
104
|
if (bracketCount === 0) {
|
|
92
105
|
return trimmed.substring(startIndex, endIndex);
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
|
-
|
|
108
|
+
|
|
96
109
|
// Fallback: Find JSON boundaries anywhere in the text
|
|
97
110
|
const firstBracket = Math.min(
|
|
98
111
|
trimmed.indexOf('{') >= 0 ? trimmed.indexOf('{') : Infinity,
|
|
99
112
|
trimmed.indexOf('[') >= 0 ? trimmed.indexOf('[') : Infinity
|
|
100
113
|
);
|
|
101
|
-
|
|
114
|
+
|
|
102
115
|
const lastBracket = Math.max(
|
|
103
116
|
trimmed.lastIndexOf('}'),
|
|
104
117
|
trimmed.lastIndexOf(']')
|
|
105
118
|
);
|
|
106
|
-
|
|
119
|
+
|
|
107
120
|
// Only extract if we found valid JSON boundaries
|
|
108
121
|
if (firstBracket < Infinity && lastBracket >= 0 && firstBracket < lastBracket) {
|
|
109
122
|
// Check if the response likely starts with JSON (directly or after minimal content)
|
|
110
123
|
const beforeFirstBracket = trimmed.substring(0, firstBracket).trim();
|
|
111
|
-
|
|
124
|
+
|
|
112
125
|
// If there's minimal content before the first bracket, extract the JSON
|
|
113
|
-
if (beforeFirstBracket === '' ||
|
|
126
|
+
if (beforeFirstBracket === '' ||
|
|
114
127
|
beforeFirstBracket.match(/^```\w*$/) ||
|
|
115
128
|
beforeFirstBracket.split('\n').length <= 2) {
|
|
116
129
|
return trimmed.substring(firstBracket, lastBracket + 1);
|
|
117
130
|
}
|
|
118
131
|
}
|
|
119
|
-
|
|
132
|
+
|
|
120
133
|
return response; // Return original if no extractable JSON found
|
|
121
134
|
}
|
|
122
135
|
|
package/build/grep.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grep functionality for the probe package
|
|
3
|
+
* @module grep
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { getBinaryPath } from './utils.js';
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Flag mapping for grep options
|
|
14
|
+
* Maps option keys to command-line flags
|
|
15
|
+
*/
|
|
16
|
+
const GREP_FLAG_MAP = {
|
|
17
|
+
ignoreCase: '-i',
|
|
18
|
+
lineNumbers: '-n',
|
|
19
|
+
count: '-c',
|
|
20
|
+
filesWithMatches: '-l',
|
|
21
|
+
filesWithoutMatches: '-L',
|
|
22
|
+
invertMatch: '-v',
|
|
23
|
+
beforeContext: '-B',
|
|
24
|
+
afterContext: '-A',
|
|
25
|
+
context: '-C',
|
|
26
|
+
noGitignore: '--no-gitignore',
|
|
27
|
+
color: '--color',
|
|
28
|
+
maxCount: '-m'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Standard grep-style search across files (works with any file type, not just code)
|
|
33
|
+
*
|
|
34
|
+
* This provides a cross-platform grep interface that works on any OS and file type.
|
|
35
|
+
* Use this for searching non-code files (logs, config files, text files, etc.) that
|
|
36
|
+
* are not supported by probe's semantic search.
|
|
37
|
+
*
|
|
38
|
+
* For code files, prefer using the `search()` function which provides semantic,
|
|
39
|
+
* AST-aware search capabilities.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options - Grep options
|
|
42
|
+
* @param {string} options.pattern - Pattern to search for (regex)
|
|
43
|
+
* @param {string|string[]} options.paths - Path(s) to search in
|
|
44
|
+
* @param {boolean} [options.ignoreCase] - Case-insensitive search (-i)
|
|
45
|
+
* @param {boolean} [options.lineNumbers] - Show line numbers (-n)
|
|
46
|
+
* @param {boolean} [options.count] - Only show count of matches (-c)
|
|
47
|
+
* @param {boolean} [options.filesWithMatches] - Only show filenames with matches (-l)
|
|
48
|
+
* @param {boolean} [options.filesWithoutMatches] - Only show filenames without matches (-L)
|
|
49
|
+
* @param {boolean} [options.invertMatch] - Invert match, show non-matching lines (-v)
|
|
50
|
+
* @param {number} [options.beforeContext] - Lines of context before match (-B)
|
|
51
|
+
* @param {number} [options.afterContext] - Lines of context after match (-A)
|
|
52
|
+
* @param {number} [options.context] - Lines of context before and after match (-C)
|
|
53
|
+
* @param {boolean} [options.noGitignore] - Don't respect .gitignore files (--no-gitignore)
|
|
54
|
+
* @param {string} [options.color] - Colorize output: 'always', 'never', 'auto' (--color)
|
|
55
|
+
* @param {number} [options.maxCount] - Stop after N matches per file (-m)
|
|
56
|
+
* @param {Object} [options.binaryOptions] - Options for getting the binary
|
|
57
|
+
* @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
|
|
58
|
+
* @param {string} [options.binaryOptions.version] - Specific version to download
|
|
59
|
+
* @returns {Promise<string>} - Grep results as string
|
|
60
|
+
* @throws {Error} If the grep operation fails
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Search for "error" in log files (case-insensitive)
|
|
64
|
+
* const results = await grep({
|
|
65
|
+
* pattern: 'error',
|
|
66
|
+
* paths: '/var/log',
|
|
67
|
+
* ignoreCase: true,
|
|
68
|
+
* lineNumbers: true
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Count occurrences of "TODO" in project
|
|
73
|
+
* const count = await grep({
|
|
74
|
+
* pattern: 'TODO',
|
|
75
|
+
* paths: '.',
|
|
76
|
+
* count: true
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Find files containing "config" with context
|
|
81
|
+
* const matches = await grep({
|
|
82
|
+
* pattern: 'config',
|
|
83
|
+
* paths: '/etc',
|
|
84
|
+
* context: 2,
|
|
85
|
+
* filesWithMatches: true
|
|
86
|
+
* });
|
|
87
|
+
*/
|
|
88
|
+
export async function grep(options) {
|
|
89
|
+
if (!options || !options.pattern) {
|
|
90
|
+
throw new Error('Pattern is required');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!options.paths) {
|
|
94
|
+
throw new Error('Path(s) are required');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Get the binary path
|
|
98
|
+
const binaryPath = await getBinaryPath(options.binaryOptions || {});
|
|
99
|
+
|
|
100
|
+
// Build CLI arguments array for grep subcommand
|
|
101
|
+
// Using an array prevents command injection vulnerabilities
|
|
102
|
+
const cliArgs = ['grep'];
|
|
103
|
+
|
|
104
|
+
// Add flags from GREP_FLAG_MAP
|
|
105
|
+
for (const [key, flag] of Object.entries(GREP_FLAG_MAP)) {
|
|
106
|
+
const value = options[key];
|
|
107
|
+
if (value === undefined || value === null) continue;
|
|
108
|
+
|
|
109
|
+
if (typeof value === 'boolean' && value) {
|
|
110
|
+
// Boolean flag
|
|
111
|
+
cliArgs.push(flag);
|
|
112
|
+
} else if (typeof value === 'number') {
|
|
113
|
+
// Numeric option
|
|
114
|
+
cliArgs.push(flag, String(value));
|
|
115
|
+
} else if (typeof value === 'string') {
|
|
116
|
+
// String option (like color)
|
|
117
|
+
cliArgs.push(flag, value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Add pattern (no need to escape - execFile handles it securely)
|
|
122
|
+
cliArgs.push(options.pattern);
|
|
123
|
+
|
|
124
|
+
// Add paths (can be single string or array)
|
|
125
|
+
const paths = Array.isArray(options.paths) ? options.paths : [options.paths];
|
|
126
|
+
cliArgs.push(...paths);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Use execFile instead of exec to prevent command injection
|
|
130
|
+
// execFile does not spawn a shell and passes arguments as an array
|
|
131
|
+
const { stdout, stderr } = await execFileAsync(binaryPath, cliArgs, {
|
|
132
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
133
|
+
env: {
|
|
134
|
+
...process.env,
|
|
135
|
+
// Disable colors in stderr for cleaner output
|
|
136
|
+
NO_COLOR: '1'
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Return stdout (grep results)
|
|
141
|
+
return stdout;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// Grep exit code 1 means "no matches found", which is not an error
|
|
144
|
+
if (error.code === 1 && !error.stderr) {
|
|
145
|
+
return error.stdout || '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Other errors are real failures
|
|
149
|
+
const errorMessage = error.stderr || error.message || 'Unknown error';
|
|
150
|
+
throw new Error(`Grep failed: ${errorMessage}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
package/build/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { search } from './search.js';
|
|
11
11
|
import { query } from './query.js';
|
|
12
12
|
import { extract } from './extract.js';
|
|
13
|
+
import { grep } from './grep.js';
|
|
13
14
|
import { delegate } from './delegate.js';
|
|
14
15
|
import { getBinaryPath, setBinaryPath } from './utils.js';
|
|
15
16
|
import * as tools from './tools/index.js';
|
|
@@ -40,6 +41,7 @@ export {
|
|
|
40
41
|
search,
|
|
41
42
|
query,
|
|
42
43
|
extract,
|
|
44
|
+
grep,
|
|
43
45
|
delegate,
|
|
44
46
|
getBinaryPath,
|
|
45
47
|
setBinaryPath,
|