@probelabs/probe 0.6.0-rc146 → 0.6.0-rc148

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc146",
3
+ "version": "0.6.0-rc148",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -21,10 +21,6 @@
21
21
  "import": "./src/agent/simpleTelemetry.js",
22
22
  "require": "./cjs/agent/simpleTelemetry.cjs"
23
23
  },
24
- "./telemetry/full": {
25
- "import": "./src/agent/telemetry.js",
26
- "require": "./cjs/agent/telemetry.cjs"
27
- },
28
24
  "./agent/mcp": {
29
25
  "import": "./src/agent/mcp/index.js"
30
26
  }
@@ -41,6 +37,8 @@
41
37
  "bin/probe-binary",
42
38
  "bin/probe.exe",
43
39
  "bin/.gitkeep",
40
+ "bin/binaries/*.tar.gz",
41
+ "bin/binaries/*.zip",
44
42
  "scripts/postinstall.js",
45
43
  "build/**/*",
46
44
  "MERMAID_SUPPORT_SUMMARY.md",
@@ -77,13 +75,6 @@
77
75
  "@ai-sdk/google": "^2.0.14",
78
76
  "@ai-sdk/openai": "^2.0.10",
79
77
  "@modelcontextprotocol/sdk": "^1.0.0",
80
- "@opentelemetry/api": "^1.9.0",
81
- "@opentelemetry/core": "^1.30.1",
82
- "@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
83
- "@opentelemetry/resources": "^2.0.1",
84
- "@opentelemetry/sdk-node": "^0.203.0",
85
- "@opentelemetry/sdk-trace-base": "^1.30.0",
86
- "@opentelemetry/semantic-conventions": "^1.36.0",
87
78
  "@probelabs/maid": "^0.0.16",
88
79
  "ai": "^5.0.0",
89
80
  "axios": "^1.8.3",
@@ -92,6 +83,7 @@
92
83
  "glob": "^10.3.10",
93
84
  "gpt-tokenizer": "^3.0.1",
94
85
  "tar": "^6.2.0",
86
+ "adm-zip": "^0.5.16",
95
87
  "zod": "^3.24.2"
96
88
  },
97
89
  "devDependencies": {
@@ -10,11 +10,12 @@
10
10
  import fs from 'fs-extra';
11
11
  import path from 'path';
12
12
  import { fileURLToPath } from 'url';
13
- import { exec } from 'child_process';
13
+ import { execFile } from 'child_process';
14
14
  import { promisify } from 'util';
15
+ import { extractBundledBinary } from '../src/extractor.js';
15
16
  import { downloadProbeBinary } from '../src/downloader.js';
16
17
 
17
- const execAsync = promisify(exec);
18
+ const execFileAsync = promisify(execFile);
18
19
 
19
20
  // Get the directory of the current module
20
21
  const __filename = fileURLToPath(import.meta.url);
@@ -98,48 +99,73 @@ You can download the binary from: https://github.com/probelabs/probe/releases
98
99
  console.log('Created .gitignore file in bin directory');
99
100
  }
100
101
 
101
- // Download the probe binary
102
- if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
103
- console.log('Downloading probe binary...');
104
- }
105
- try {
106
- // Try to get the package version
107
- let packageVersion = '0.0.0';
108
- const possiblePaths = [
109
- path.resolve(__dirname, '..', 'package.json'), // When installed from npm: scripts/../package.json
110
- path.resolve(__dirname, '..', '..', 'package.json') // In development: scripts/../../package.json
111
- ];
112
-
113
- for (const packageJsonPath of possiblePaths) {
114
- try {
115
- if (fs.existsSync(packageJsonPath)) {
102
+ // Get the package version first
103
+ let packageVersion = '0.0.0';
104
+ const possiblePaths = [
105
+ path.resolve(__dirname, '..', 'package.json'), // When installed from npm: scripts/../package.json
106
+ path.resolve(__dirname, '..', '..', 'package.json') // In development: scripts/../../package.json
107
+ ];
108
+
109
+ for (const packageJsonPath of possiblePaths) {
110
+ try {
111
+ if (fs.existsSync(packageJsonPath)) {
112
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
113
+ console.log(`Found package.json at: ${packageJsonPath}`);
114
+ }
115
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
116
+ if (packageJson.version) {
117
+ packageVersion = packageJson.version;
116
118
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
117
- console.log(`Found package.json at: ${packageJsonPath}`);
118
- }
119
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
120
- if (packageJson.version) {
121
- packageVersion = packageJson.version;
122
- if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
123
- console.log(`Using version from package.json: ${packageVersion}`);
124
- }
125
- break;
119
+ console.log(`Using version from package.json: ${packageVersion}`);
126
120
  }
121
+ break;
127
122
  }
128
- } catch (err) {
129
- console.error(`Error reading package.json at ${packageJsonPath}:`, err);
130
123
  }
124
+ } catch (err) {
125
+ console.error(`Error reading package.json at ${packageJsonPath}:`, err);
131
126
  }
127
+ }
132
128
 
133
- // Download the binary (it will be placed at the correct location automatically)
134
- const binaryPath = await downloadProbeBinary(packageVersion);
129
+ // Try to extract bundled binary first
130
+ let binaryPath;
131
+ try {
132
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
133
+ console.log('Extracting bundled probe binary...');
134
+ }
135
+ binaryPath = await extractBundledBinary(packageVersion);
136
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
137
+ console.log(`Successfully extracted bundled binary to: ${binaryPath}`);
138
+ }
139
+ } catch (extractError) {
140
+ // If bundled binary extraction fails, fall back to downloading
135
141
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
136
- console.log(`Successfully downloaded probe binary to: ${binaryPath}`);
142
+ console.log(`Bundled binary extraction failed: ${extractError.message}`);
143
+ console.log('Falling back to downloading binary from GitHub...');
144
+ } else {
145
+ console.log('Bundled binary not found, downloading from GitHub...');
137
146
  }
138
147
 
139
- // Get the path to the target binary (preserve the Node.js wrapper script)
140
- // (variables already declared at the beginning of main function)
148
+ try {
149
+ binaryPath = await downloadProbeBinary(packageVersion);
150
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
151
+ console.log(`Successfully downloaded probe binary to: ${binaryPath}`);
152
+ }
153
+ } catch (downloadError) {
154
+ throw new Error(
155
+ `Failed to install probe binary.\n` +
156
+ `Bundled extraction error: ${extractError.message}\n` +
157
+ `Download error: ${downloadError.message}\n` +
158
+ `\n` +
159
+ `Please check:\n` +
160
+ `1. Your platform is supported\n` +
161
+ `2. You have internet connectivity\n` +
162
+ `3. GitHub releases are accessible`
163
+ );
164
+ }
165
+ }
141
166
 
142
- // Copy the downloaded binary to the correct location
167
+ // Copy the extracted/downloaded binary to the correct location if needed
168
+ // (targetBinaryName and targetBinaryPath already declared at the beginning of main function)
143
169
  if (binaryPath !== targetBinaryPath) {
144
170
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
145
171
  console.log(`Copying binary to ${targetBinaryPath} from ${binaryPath}`);
@@ -151,7 +177,18 @@ You can download the binary from: https://github.com/probelabs/probe/releases
151
177
  // On macOS, try to remove quarantine attributes that might prevent execution
152
178
  if (process.platform === 'darwin') {
153
179
  try {
154
- await execAsync(`xattr -d com.apple.quarantine "${targetBinaryPath}" 2>/dev/null || true`);
180
+ // Security: Use execFile with array args instead of exec to prevent command injection
181
+ // Validate that the path is within the bin directory
182
+ const normalizedPath = path.normalize(targetBinaryPath);
183
+ const normalizedBinDir = path.normalize(binDir);
184
+ if (!normalizedPath.startsWith(normalizedBinDir)) {
185
+ throw new Error('Invalid binary path - outside of bin directory');
186
+ }
187
+
188
+ await execFileAsync('xattr', ['-d', 'com.apple.quarantine', normalizedPath]).catch(() => {
189
+ // Ignore errors - xattr may not exist or file may not have quarantine attribute
190
+ });
191
+
155
192
  if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
156
193
  console.log('Removed quarantine attributes from binary');
157
194
  }
@@ -163,18 +200,9 @@ You can download the binary from: https://github.com/probelabs/probe/releases
163
200
  }
164
201
  }
165
202
 
166
- if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
167
- console.log('\nProbe binary was successfully downloaded and installed during installation.');
168
- console.log('You can now use the probe command directly from the command line.');
169
- }
170
- } catch (error) {
171
- console.error('Error downloading probe binary:', error);
172
- console.error('\nNote: The probe binary will need to be downloaded when you first use the package.');
173
- console.error('If you encounter any issues, you can manually place the binary in the bin directory.');
174
- console.error('You can download it from: https://github.com/probelabs/probe/releases');
175
-
176
- // Don't fail the installation, just warn the user
177
- return;
203
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
204
+ console.log('\nProbe binary was successfully installed.');
205
+ console.log('You can now use the probe command directly from the command line.');
178
206
  }
179
207
  } catch (error) {
180
208
  console.error(`Error in postinstall script: ${error.message}`);
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Binary extractor for bundled probe binaries
3
+ * @module extractor
4
+ */
5
+
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import tar from 'tar';
9
+ import AdmZip from 'adm-zip';
10
+ import os from 'os';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const BINARY_NAME = "probe";
17
+
18
+ /**
19
+ * Detects the current OS and architecture
20
+ * @returns {Object} Object containing OS and architecture information
21
+ */
22
+ function detectPlatform() {
23
+ const osType = os.platform();
24
+ const archType = os.arch();
25
+
26
+ let platform;
27
+ let extension;
28
+
29
+ // Map to the same format used in release artifacts
30
+ if (osType === 'linux') {
31
+ if (archType === 'x64') {
32
+ platform = 'x86_64-unknown-linux-gnu';
33
+ extension = 'tar.gz';
34
+ } else if (archType === 'arm64') {
35
+ platform = 'aarch64-unknown-linux-gnu';
36
+ extension = 'tar.gz';
37
+ } else {
38
+ throw new Error(`Unsupported Linux architecture: ${archType}`);
39
+ }
40
+ } else if (osType === 'darwin') {
41
+ if (archType === 'x64') {
42
+ platform = 'x86_64-apple-darwin';
43
+ extension = 'tar.gz';
44
+ } else if (archType === 'arm64') {
45
+ platform = 'aarch64-apple-darwin';
46
+ extension = 'tar.gz';
47
+ } else {
48
+ throw new Error(`Unsupported macOS architecture: ${archType}`);
49
+ }
50
+ } else if (osType === 'win32') {
51
+ if (archType === 'x64') {
52
+ platform = 'x86_64-pc-windows-msvc';
53
+ extension = 'zip';
54
+ } else {
55
+ throw new Error(`Unsupported Windows architecture: ${archType}`);
56
+ }
57
+ } else {
58
+ throw new Error(`Unsupported operating system: ${osType}`);
59
+ }
60
+
61
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
62
+ console.log(`Detected platform: ${platform}`);
63
+ }
64
+
65
+ return { platform, extension, osType, archType };
66
+ }
67
+
68
+ /**
69
+ * Validates that a path is within a base directory (prevents path traversal)
70
+ * @param {string} filePath - Path to validate
71
+ * @param {string} baseDir - Base directory that filePath must be within
72
+ * @returns {boolean} True if path is safe
73
+ */
74
+ function isPathSafe(filePath, baseDir) {
75
+ const normalizedBase = path.normalize(baseDir);
76
+ const normalizedPath = path.normalize(filePath);
77
+ const relativePath = path.relative(normalizedBase, normalizedPath);
78
+
79
+ // Path is safe if it doesn't start with '..' and isn't absolute
80
+ return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
81
+ }
82
+
83
+ /**
84
+ * Extracts a tar.gz archive using the tar library
85
+ * @param {string} archivePath - Path to the .tar.gz file
86
+ * @param {string} extractDir - Directory to extract to
87
+ */
88
+ async function extractTarGz(archivePath, extractDir) {
89
+ await tar.extract({
90
+ file: archivePath,
91
+ cwd: extractDir,
92
+ // Security: Prevent path traversal attacks
93
+ onentry: (entry) => {
94
+ const fullPath = path.join(extractDir, entry.path);
95
+ if (!isPathSafe(fullPath, extractDir)) {
96
+ throw new Error(`Path traversal attempt detected: ${entry.path}`);
97
+ }
98
+ }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Extracts a zip archive using adm-zip library
104
+ * @param {string} archivePath - Path to the .zip file
105
+ * @param {string} extractDir - Directory to extract to
106
+ */
107
+ async function extractZip(archivePath, extractDir) {
108
+ const zip = new AdmZip(archivePath);
109
+ const zipEntries = zip.getEntries();
110
+
111
+ // Extract each entry with path validation
112
+ for (const entry of zipEntries) {
113
+ const outputPath = path.join(extractDir, entry.entryName);
114
+
115
+ // Security: Validate path to prevent traversal attacks
116
+ if (!isPathSafe(outputPath, extractDir)) {
117
+ throw new Error(`Path traversal attempt detected: ${entry.entryName}`);
118
+ }
119
+
120
+ if (entry.isDirectory) {
121
+ await fs.ensureDir(outputPath);
122
+ } else {
123
+ await fs.ensureDir(path.dirname(outputPath));
124
+ await fs.writeFile(outputPath, entry.getData());
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Finds the binary file in the extracted directory
131
+ * @param {string} dir - Directory to search
132
+ * @param {string} baseDir - Base directory for path validation
133
+ * @param {string} binaryName - Name of binary to find
134
+ * @param {boolean} isWindows - Whether running on Windows
135
+ * @returns {Promise<string|null>} Path to binary or null
136
+ */
137
+ async function findBinary(dir, baseDir, binaryName, isWindows) {
138
+ const entries = await fs.readdir(dir, { withFileTypes: true });
139
+
140
+ for (const entry of entries) {
141
+ const fullPath = path.join(dir, entry.name);
142
+
143
+ // Security: Validate path to prevent traversal
144
+ if (!isPathSafe(fullPath, baseDir)) {
145
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
146
+ console.log(`Skipping unsafe path: ${fullPath}`);
147
+ }
148
+ continue;
149
+ }
150
+
151
+ if (entry.isDirectory()) {
152
+ const result = await findBinary(fullPath, baseDir, binaryName, isWindows);
153
+ if (result) return result;
154
+ } else if (entry.isFile()) {
155
+ // Check if this is the binary we're looking for
156
+ if (entry.name === binaryName ||
157
+ entry.name === BINARY_NAME ||
158
+ (isWindows && entry.name.endsWith('.exe'))) {
159
+ return fullPath;
160
+ }
161
+ }
162
+ }
163
+
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * Extracts the bundled binary for the current platform
169
+ * @param {string} version - Version string (used for archive naming)
170
+ * @returns {Promise<string>} Path to the extracted binary
171
+ */
172
+ export async function extractBundledBinary(version) {
173
+ const { platform, extension, osType } = detectPlatform();
174
+
175
+ // Construct the archive filename
176
+ const archiveName = `probe-v${version}-${platform}.${extension}`;
177
+
178
+ // Path to the bundled archive
179
+ const binariesDir = path.resolve(__dirname, '..', 'bin', 'binaries');
180
+ const archivePath = path.join(binariesDir, archiveName);
181
+
182
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
183
+ console.log(`Looking for bundled binary at: ${archivePath}`);
184
+ }
185
+
186
+ // Check if the archive exists
187
+ if (!(await fs.pathExists(archivePath))) {
188
+ throw new Error(
189
+ `Bundled binary not found for platform ${platform}.\n` +
190
+ `Expected archive: ${archiveName}\n` +
191
+ `Searched in: ${binariesDir}\n` +
192
+ `\n` +
193
+ `Supported platforms:\n` +
194
+ ` - x86_64-unknown-linux-gnu (Linux x64)\n` +
195
+ ` - aarch64-unknown-linux-gnu (Linux ARM64)\n` +
196
+ ` - x86_64-apple-darwin (macOS Intel)\n` +
197
+ ` - aarch64-apple-darwin (macOS Apple Silicon)\n` +
198
+ ` - x86_64-pc-windows-msvc (Windows x64)\n` +
199
+ `\n` +
200
+ `Your platform: ${platform}`
201
+ );
202
+ }
203
+
204
+ // Determine output binary name and path
205
+ const binDir = path.resolve(__dirname, '..', 'bin');
206
+ const isWindows = osType === 'win32';
207
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
208
+ const binaryPath = path.join(binDir, binaryName);
209
+
210
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
211
+ console.log(`Extracting ${archiveName} to ${binDir}...`);
212
+ }
213
+
214
+ // Create a temporary extraction directory
215
+ const extractDir = path.join(binDir, 'temp_extract');
216
+ await fs.ensureDir(extractDir);
217
+
218
+ try {
219
+ // Extract based on file type using proper libraries (no shell commands!)
220
+ if (extension === 'tar.gz') {
221
+ await extractTarGz(archivePath, extractDir);
222
+ } else if (extension === 'zip') {
223
+ await extractZip(archivePath, extractDir);
224
+ }
225
+
226
+ // Find the binary in the extracted files
227
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
228
+ console.log(`Searching for binary in extracted files...`);
229
+ }
230
+
231
+ const binaryFilePath = await findBinary(extractDir, extractDir, binaryName, isWindows);
232
+
233
+ if (!binaryFilePath) {
234
+ const allFiles = await fs.readdir(extractDir, { recursive: true });
235
+ throw new Error(
236
+ `Binary not found in the archive.\n` +
237
+ `Expected binary name: ${binaryName}\n` +
238
+ `Files in archive: ${allFiles.join(', ')}`
239
+ );
240
+ }
241
+
242
+ // Copy the binary to the final location
243
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
244
+ console.log(`Found binary at ${binaryFilePath}`);
245
+ console.log(`Installing to ${binaryPath}`);
246
+ }
247
+
248
+ await fs.copyFile(binaryFilePath, binaryPath);
249
+
250
+ // Make the binary executable on Unix-like systems
251
+ if (!isWindows) {
252
+ await fs.chmod(binaryPath, 0o755);
253
+ }
254
+
255
+ // Clean up the temporary extraction directory
256
+ await fs.remove(extractDir);
257
+
258
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
259
+ console.log(`Binary successfully extracted to ${binaryPath}`);
260
+ }
261
+
262
+ return binaryPath;
263
+ } catch (error) {
264
+ // Clean up on error
265
+ try {
266
+ await fs.remove(extractDir);
267
+ } catch {}
268
+
269
+ throw new Error(`Failed to extract bundled binary: ${error.message}`);
270
+ }
271
+ }
package/src/index.js CHANGED
@@ -39,8 +39,6 @@ import { searchTool, queryTool, extractTool, delegateTool } from './tools/vercel
39
39
  import { bashTool } from './tools/bash.js';
40
40
  import { ProbeAgent } from './agent/ProbeAgent.js';
41
41
  import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
42
- import { TelemetryConfig, initializeTelemetryFromOptions } from './agent/telemetry.js';
43
- import { AppTracer } from './agent/appTracer.js';
44
42
  import { listFilesToolInstance, searchFilesToolInstance } from './agent/probeTool.js';
45
43
  import { StorageAdapter, InMemoryStorageAdapter } from './agent/storage/index.js';
46
44
  import { HookManager, HOOK_TYPES } from './agent/hooks/index.js';
@@ -64,14 +62,10 @@ export {
64
62
  // Export hooks
65
63
  HookManager,
66
64
  HOOK_TYPES,
67
- // Export simple telemetry classes (no OpenTelemetry dependencies)
65
+ // Export simple telemetry classes (lightweight, no heavy dependencies)
68
66
  SimpleTelemetry,
69
67
  SimpleAppTracer,
70
68
  initializeSimpleTelemetryFromOptions,
71
- // Export full OpenTelemetry telemetry classes
72
- TelemetryConfig,
73
- AppTracer,
74
- initializeTelemetryFromOptions,
75
69
  // Export tool generators directly
76
70
  searchTool,
77
71
  queryTool,