@probelabs/probe 0.6.0-rc100

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.
Files changed (115) hide show
  1. package/README.md +583 -0
  2. package/bin/.gitkeep +0 -0
  3. package/bin/probe +158 -0
  4. package/bin/probe-binary +0 -0
  5. package/build/agent/ProbeAgent.d.ts +199 -0
  6. package/build/agent/ProbeAgent.js +1486 -0
  7. package/build/agent/acp/README.md +347 -0
  8. package/build/agent/acp/connection.js +237 -0
  9. package/build/agent/acp/connection.test.js +311 -0
  10. package/build/agent/acp/examples/simple-client.js +212 -0
  11. package/build/agent/acp/examples/tool-lifecycle.js +230 -0
  12. package/build/agent/acp/final-test.js +173 -0
  13. package/build/agent/acp/index.js +5 -0
  14. package/build/agent/acp/integration.test.js +385 -0
  15. package/build/agent/acp/manual-test.js +410 -0
  16. package/build/agent/acp/protocol-test.js +190 -0
  17. package/build/agent/acp/server.js +448 -0
  18. package/build/agent/acp/server.test.js +371 -0
  19. package/build/agent/acp/test-runner.js +216 -0
  20. package/build/agent/acp/test-utils/README.md +315 -0
  21. package/build/agent/acp/test-utils/acp-tester.js +484 -0
  22. package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
  23. package/build/agent/acp/tools.js +368 -0
  24. package/build/agent/acp/tools.test.js +334 -0
  25. package/build/agent/acp/types.js +218 -0
  26. package/build/agent/acp/types.test.js +327 -0
  27. package/build/agent/appTracer.js +360 -0
  28. package/build/agent/fileSpanExporter.js +169 -0
  29. package/build/agent/index.js +7426 -0
  30. package/build/agent/mcp/client.js +338 -0
  31. package/build/agent/mcp/config.js +313 -0
  32. package/build/agent/mcp/index.js +64 -0
  33. package/build/agent/mcp/xmlBridge.js +371 -0
  34. package/build/agent/mockProvider.js +53 -0
  35. package/build/agent/probeTool.js +257 -0
  36. package/build/agent/schemaUtils.js +1726 -0
  37. package/build/agent/simpleTelemetry.js +267 -0
  38. package/build/agent/telemetry.js +225 -0
  39. package/build/agent/tokenCounter.js +395 -0
  40. package/build/agent/tools.js +163 -0
  41. package/build/cli.js +49 -0
  42. package/build/delegate.js +267 -0
  43. package/build/directory-resolver.js +237 -0
  44. package/build/downloader.js +750 -0
  45. package/build/extract.js +149 -0
  46. package/build/index.js +70 -0
  47. package/build/mcp/index.js +514 -0
  48. package/build/mcp/index.ts +608 -0
  49. package/build/query.js +116 -0
  50. package/build/search.js +247 -0
  51. package/build/tools/common.js +410 -0
  52. package/build/tools/index.js +40 -0
  53. package/build/tools/langchain.js +88 -0
  54. package/build/tools/system-message.js +121 -0
  55. package/build/tools/vercel.js +271 -0
  56. package/build/utils/file-lister.js +193 -0
  57. package/build/utils.js +128 -0
  58. package/cjs/agent/ProbeAgent.cjs +5829 -0
  59. package/cjs/index.cjs +6217 -0
  60. package/cjs/package.json +3 -0
  61. package/index.d.ts +401 -0
  62. package/package.json +114 -0
  63. package/scripts/postinstall.js +172 -0
  64. package/src/agent/ProbeAgent.d.ts +199 -0
  65. package/src/agent/ProbeAgent.js +1486 -0
  66. package/src/agent/acp/README.md +347 -0
  67. package/src/agent/acp/connection.js +237 -0
  68. package/src/agent/acp/connection.test.js +311 -0
  69. package/src/agent/acp/examples/simple-client.js +212 -0
  70. package/src/agent/acp/examples/tool-lifecycle.js +230 -0
  71. package/src/agent/acp/final-test.js +173 -0
  72. package/src/agent/acp/index.js +5 -0
  73. package/src/agent/acp/integration.test.js +385 -0
  74. package/src/agent/acp/manual-test.js +410 -0
  75. package/src/agent/acp/protocol-test.js +190 -0
  76. package/src/agent/acp/server.js +448 -0
  77. package/src/agent/acp/server.test.js +371 -0
  78. package/src/agent/acp/test-runner.js +216 -0
  79. package/src/agent/acp/test-utils/README.md +315 -0
  80. package/src/agent/acp/test-utils/acp-tester.js +484 -0
  81. package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
  82. package/src/agent/acp/tools.js +368 -0
  83. package/src/agent/acp/tools.test.js +334 -0
  84. package/src/agent/acp/types.js +218 -0
  85. package/src/agent/acp/types.test.js +327 -0
  86. package/src/agent/appTracer.js +360 -0
  87. package/src/agent/fileSpanExporter.js +169 -0
  88. package/src/agent/index.js +813 -0
  89. package/src/agent/mcp/client.js +338 -0
  90. package/src/agent/mcp/config.js +313 -0
  91. package/src/agent/mcp/index.js +64 -0
  92. package/src/agent/mcp/xmlBridge.js +371 -0
  93. package/src/agent/mockProvider.js +53 -0
  94. package/src/agent/probeTool.js +257 -0
  95. package/src/agent/schemaUtils.js +1726 -0
  96. package/src/agent/simpleTelemetry.js +267 -0
  97. package/src/agent/telemetry.js +225 -0
  98. package/src/agent/tokenCounter.js +395 -0
  99. package/src/agent/tools.js +163 -0
  100. package/src/cli.js +49 -0
  101. package/src/delegate.js +267 -0
  102. package/src/directory-resolver.js +237 -0
  103. package/src/downloader.js +750 -0
  104. package/src/extract.js +149 -0
  105. package/src/index.js +70 -0
  106. package/src/mcp/index.ts +608 -0
  107. package/src/query.js +116 -0
  108. package/src/search.js +247 -0
  109. package/src/tools/common.js +410 -0
  110. package/src/tools/index.js +40 -0
  111. package/src/tools/langchain.js +88 -0
  112. package/src/tools/system-message.js +121 -0
  113. package/src/tools/vercel.js +271 -0
  114. package/src/utils/file-lister.js +193 -0
  115. package/src/utils.js +128 -0
@@ -0,0 +1,750 @@
1
+ /**
2
+ * Binary downloader for the probe package
3
+ * @module downloader
4
+ */
5
+
6
+ import axios from 'axios';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import { createHash } from 'crypto';
10
+ import { promisify } from 'util';
11
+ import { exec as execCallback } from 'child_process';
12
+ import tar from 'tar';
13
+ import os from 'os';
14
+ import { fileURLToPath } from 'url';
15
+ import { ensureBinDirectory } from './utils.js';
16
+ import { getPackageBinDir } from './directory-resolver.js';
17
+
18
+ const exec = promisify(execCallback);
19
+
20
+ // GitHub repository information
21
+ const REPO_OWNER = "probelabs";
22
+ const REPO_NAME = "probe";
23
+ const BINARY_NAME = "probe";
24
+
25
+ // Get the directory of the current module
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+
29
+ // Note: LOCAL_DIR and VERSION_INFO_PATH are now resolved dynamically
30
+ // using getPackageBinDir() to handle different installation environments
31
+
32
+ /**
33
+ * Detects the current OS and architecture
34
+ * @returns {Object} Object containing OS and architecture information
35
+ */
36
+ function detectOsArch() {
37
+ const osType = os.platform();
38
+ const archType = os.arch();
39
+
40
+ let osInfo;
41
+ let archInfo;
42
+
43
+ // Detect OS
44
+ switch (osType) {
45
+ case 'linux':
46
+ osInfo = {
47
+ type: 'linux',
48
+ keywords: ['linux', 'Linux', 'gnu']
49
+ };
50
+ break;
51
+ case 'darwin':
52
+ osInfo = {
53
+ type: 'darwin',
54
+ keywords: ['darwin', 'Darwin', 'mac', 'Mac', 'apple', 'Apple', 'osx', 'OSX']
55
+ };
56
+ break;
57
+ case 'win32':
58
+ osInfo = {
59
+ type: 'windows',
60
+ keywords: ['windows', 'Windows', 'msvc', 'pc-windows']
61
+ };
62
+ break;
63
+ default:
64
+ throw new Error(`Unsupported operating system: ${osType}`);
65
+ }
66
+
67
+ // Detect architecture
68
+ switch (archType) {
69
+ case 'x64':
70
+ archInfo = {
71
+ type: 'x86_64',
72
+ keywords: ['x86_64', 'amd64', 'x64', '64bit', '64-bit']
73
+ };
74
+ break;
75
+ case 'arm64':
76
+ archInfo = {
77
+ type: 'aarch64',
78
+ keywords: ['arm64', 'aarch64', 'arm', 'ARM']
79
+ };
80
+ break;
81
+ default:
82
+ throw new Error(`Unsupported architecture: ${archType}`);
83
+ }
84
+
85
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
86
+ console.log(`Detected OS: ${osInfo.type}, Architecture: ${archInfo.type}`);
87
+ }
88
+ return { os: osInfo, arch: archInfo };
89
+ }
90
+
91
+ /**
92
+ * Constructs the asset name and download URL directly based on version and platform
93
+ * @param {string} version - The version to download (e.g., "0.6.0-rc60")
94
+ * @param {Object} osInfo - OS information from detectOsArch()
95
+ * @param {Object} archInfo - Architecture information from detectOsArch()
96
+ * @returns {Object} Asset information with name and url
97
+ */
98
+ function constructAssetInfo(version, osInfo, archInfo) {
99
+ let platform;
100
+ let extension;
101
+
102
+ // Map OS and arch to the expected format in release names
103
+ switch (osInfo.type) {
104
+ case 'linux':
105
+ platform = `${archInfo.type}-unknown-linux-gnu`;
106
+ extension = 'tar.gz';
107
+ break;
108
+ case 'darwin':
109
+ platform = `${archInfo.type}-apple-darwin`;
110
+ extension = 'tar.gz';
111
+ break;
112
+ case 'windows':
113
+ platform = `${archInfo.type}-pc-windows-msvc`;
114
+ extension = 'zip';
115
+ break;
116
+ default:
117
+ throw new Error(`Unsupported OS type: ${osInfo.type}`);
118
+ }
119
+
120
+ const assetName = `probe-v${version}-${platform}.${extension}`;
121
+ const checksumName = `${assetName}.sha256`;
122
+
123
+ const baseUrl = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${version}`;
124
+ const assetUrl = `${baseUrl}/${assetName}`;
125
+ const checksumUrl = `${baseUrl}/${checksumName}`;
126
+
127
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
128
+ console.log(`Constructed asset URL: ${assetUrl}`);
129
+ }
130
+
131
+ return {
132
+ name: assetName,
133
+ url: assetUrl,
134
+ checksumName: checksumName,
135
+ checksumUrl: checksumUrl
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Gets the latest release information from GitHub
141
+ * @param {string} [version] - Specific version to get
142
+ * @returns {Promise<Object>} Release information
143
+ */
144
+ async function getLatestRelease(version) {
145
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
146
+ console.log('Fetching release information...');
147
+ }
148
+
149
+ try {
150
+ let releaseUrl;
151
+ if (version) {
152
+ // Always use the specified version
153
+ releaseUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/v${version}`;
154
+ } else {
155
+ // Get all releases to find the most recent one (including prereleases)
156
+ releaseUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases`;
157
+ }
158
+
159
+ const response = await axios.get(releaseUrl);
160
+
161
+ if (response.status !== 200) {
162
+ throw new Error(`Failed to fetch release information: ${response.statusText}`);
163
+ }
164
+
165
+ let releaseData;
166
+ if (version) {
167
+ // Single release for specific version
168
+ releaseData = response.data;
169
+ } else {
170
+ // Array of releases, pick the most recent one (first in the array)
171
+ if (!Array.isArray(response.data) || response.data.length === 0) {
172
+ throw new Error('No releases found');
173
+ }
174
+ releaseData = response.data[0];
175
+ }
176
+
177
+ const tag = releaseData.tag_name;
178
+ const assets = releaseData.assets.map(asset => ({
179
+ name: asset.name,
180
+ url: asset.browser_download_url
181
+ }));
182
+
183
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
184
+ console.log(`Found release: ${tag} with ${assets.length} assets`);
185
+ }
186
+ return { tag, assets };
187
+ } catch (error) {
188
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
189
+ // If the specific version is not found, try to get all releases
190
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
191
+ console.log(`Release v${version} not found, trying to fetch all releases...`);
192
+ }
193
+
194
+ const response = await axios.get(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases`);
195
+
196
+ if (response.data.length === 0) {
197
+ throw new Error('No releases found');
198
+ }
199
+
200
+ // Try to find a release that matches the version
201
+ let bestRelease = response.data[0]; // Default to latest release
202
+
203
+ if (version && version !== '0.0.0') {
204
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
205
+ console.log(`Looking for releases matching version: ${version}`);
206
+ console.log(`Available releases: ${response.data.slice(0, 5).map(r => r.tag_name).join(', ')}...`);
207
+ }
208
+
209
+ // Try to find exact match first
210
+ for (const release of response.data) {
211
+ const releaseTag = release.tag_name.startsWith('v') ?
212
+ release.tag_name.substring(1) : release.tag_name;
213
+
214
+ if (releaseTag === version) {
215
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
216
+ console.log(`Found exact matching release: ${release.tag_name}`);
217
+ }
218
+ bestRelease = release;
219
+ break;
220
+ }
221
+ }
222
+
223
+ // If no exact match, try to find a release with matching major.minor version
224
+ if (bestRelease === response.data[0]) {
225
+ const versionParts = version.split(/[\.-]/);
226
+ const majorMinor = versionParts.slice(0, 2).join('.');
227
+
228
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
229
+ console.log(`Looking for releases matching major.minor: ${majorMinor}`);
230
+ }
231
+
232
+ for (const release of response.data) {
233
+ const releaseTag = release.tag_name.startsWith('v') ?
234
+ release.tag_name.substring(1) : release.tag_name;
235
+ const releaseVersionParts = releaseTag.split(/[\.-]/);
236
+ const releaseMajorMinor = releaseVersionParts.slice(0, 2).join('.');
237
+
238
+ if (releaseMajorMinor === majorMinor) {
239
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
240
+ console.log(`Found matching major.minor release: ${release.tag_name}`);
241
+ }
242
+ bestRelease = release;
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ const tag = bestRelease.tag_name;
250
+ const assets = bestRelease.assets.map(asset => ({
251
+ name: asset.name,
252
+ url: asset.browser_download_url
253
+ }));
254
+
255
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
256
+ console.log(`Using release: ${tag} with ${assets.length} assets`);
257
+ }
258
+ return { tag, assets };
259
+ }
260
+
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Finds the best matching asset for the current OS and architecture
267
+ * @param {Array} assets - List of assets
268
+ * @param {Object} osInfo - OS information
269
+ * @param {Object} archInfo - Architecture information
270
+ * @returns {Object} Best matching asset
271
+ */
272
+ function findBestAsset(assets, osInfo, archInfo) {
273
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
274
+ console.log(`Finding appropriate binary for ${osInfo.type} ${archInfo.type}...`);
275
+ }
276
+
277
+ let bestAsset = null;
278
+ let bestScore = 0;
279
+
280
+ for (const asset of assets) {
281
+ // Skip checksum files
282
+ if (asset.name.endsWith('.sha256') || asset.name.endsWith('.md5') || asset.name.endsWith('.asc')) {
283
+ continue;
284
+ }
285
+
286
+ if (osInfo.type === 'windows' && asset.name.match(/darwin|linux/)) {
287
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
288
+ console.log(`Skipping non-Windows binary: ${asset.name}`);
289
+ }
290
+ continue;
291
+ } else if (osInfo.type === 'darwin' && asset.name.match(/windows|msvc|linux/)) {
292
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
293
+ console.log(`Skipping non-macOS binary: ${asset.name}`);
294
+ }
295
+ continue;
296
+ } else if (osInfo.type === 'linux' && asset.name.match(/darwin|windows|msvc/)) {
297
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
298
+ console.log(`Skipping non-Linux binary: ${asset.name}`);
299
+ }
300
+ continue;
301
+ }
302
+
303
+ let score = 0;
304
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
305
+ console.log(`Evaluating asset: ${asset.name}`);
306
+ }
307
+
308
+ // Check for OS match - give higher priority to exact OS matches
309
+ let osMatched = false;
310
+ for (const keyword of osInfo.keywords) {
311
+ if (asset.name.includes(keyword)) {
312
+ score += 10;
313
+ osMatched = true;
314
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
315
+ console.log(` OS match found (${keyword}): +10, score = ${score}`);
316
+ }
317
+ break;
318
+ }
319
+ }
320
+
321
+ // Check for architecture match
322
+ for (const keyword of archInfo.keywords) {
323
+ if (asset.name.includes(keyword)) {
324
+ score += 5;
325
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
326
+ console.log(` Arch match found (${keyword}): +5, score = ${score}`);
327
+ }
328
+ break;
329
+ }
330
+ }
331
+
332
+ // Prefer exact matches for binary name
333
+ if (asset.name.startsWith(`${BINARY_NAME}-`)) {
334
+ score += 3;
335
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
336
+ console.log(` Binary name match: +3, score = ${score}`);
337
+ }
338
+ }
339
+
340
+ if (osMatched && score >= 15) {
341
+ score += 5;
342
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
343
+ console.log(` OS+Arch bonus: +5, score = ${score}`);
344
+ }
345
+ }
346
+
347
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
348
+ console.log(` Final score for ${asset.name}: ${score}`);
349
+ }
350
+
351
+ // If we have a perfect match, use it immediately
352
+ if (score === 23) {
353
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
354
+ console.log(`Found perfect match: ${asset.name}`);
355
+ }
356
+ return asset;
357
+ }
358
+
359
+ // Otherwise, keep track of the best match so far
360
+ if (score > bestScore) {
361
+ bestScore = score;
362
+ bestAsset = asset;
363
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
364
+ console.log(` New best asset: ${asset.name} (score: ${score})`);
365
+ }
366
+ }
367
+ }
368
+
369
+ if (!bestAsset) {
370
+ throw new Error(`Could not find a suitable binary for ${osInfo.type} ${archInfo.type}`);
371
+ }
372
+
373
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
374
+ console.log(`Selected asset: ${bestAsset.name} (score: ${bestScore})`);
375
+ }
376
+ return bestAsset;
377
+ }
378
+
379
+ /**
380
+ * Downloads the asset and its checksum
381
+ * @param {Object} asset - Asset to download
382
+ * @param {string} outputDir - Directory to save to
383
+ * @returns {Promise<Object>} Paths to the asset and checksum
384
+ */
385
+ async function downloadAsset(asset, outputDir) {
386
+ await fs.ensureDir(outputDir);
387
+
388
+ const assetPath = path.join(outputDir, asset.name);
389
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
390
+ console.log(`Downloading ${asset.name}...`);
391
+ }
392
+
393
+ // Download the asset
394
+ const assetResponse = await axios.get(asset.url, { responseType: 'arraybuffer' });
395
+ await fs.writeFile(assetPath, Buffer.from(assetResponse.data));
396
+
397
+ // Try to download the checksum
398
+ const checksumUrl = asset.checksumUrl || `${asset.url}.sha256`;
399
+ const checksumFileName = asset.checksumName || `${asset.name}.sha256`;
400
+ let checksumPath = null;
401
+
402
+ try {
403
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
404
+ console.log(`Downloading checksum...`);
405
+ }
406
+ const checksumResponse = await axios.get(checksumUrl);
407
+ checksumPath = path.join(outputDir, checksumFileName);
408
+ await fs.writeFile(checksumPath, checksumResponse.data);
409
+ } catch (error) {
410
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
411
+ console.log('No checksum file found, skipping verification');
412
+ }
413
+ }
414
+
415
+ return { assetPath, checksumPath };
416
+ }
417
+
418
+ /**
419
+ * Verifies the checksum of the downloaded asset
420
+ * @param {string} assetPath - Path to the asset
421
+ * @param {string|null} checksumPath - Path to the checksum file
422
+ * @returns {Promise<boolean>} Whether verification succeeded
423
+ */
424
+ async function verifyChecksum(assetPath, checksumPath) {
425
+ if (!checksumPath) {
426
+ return true;
427
+ }
428
+
429
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
430
+ console.log(`Verifying checksum...`);
431
+ }
432
+
433
+ // Read the expected checksum
434
+ const checksumContent = await fs.readFile(checksumPath, 'utf-8');
435
+ const expectedChecksum = checksumContent.trim().split(' ')[0];
436
+
437
+ // Calculate the actual checksum
438
+ const fileBuffer = await fs.readFile(assetPath);
439
+ const actualChecksum = createHash('sha256').update(fileBuffer).digest('hex');
440
+
441
+ if (expectedChecksum !== actualChecksum) {
442
+ console.error(`Checksum verification failed!`);
443
+ console.error(`Expected: ${expectedChecksum}`);
444
+ console.error(`Actual: ${actualChecksum}`);
445
+ return false;
446
+ }
447
+
448
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
449
+ console.log(`Checksum verified successfully`);
450
+ }
451
+ return true;
452
+ }
453
+
454
+ /**
455
+ * Extracts and installs the binary
456
+ * @param {string} assetPath - Path to the asset
457
+ * @param {string} outputDir - Directory to extract to
458
+ * @returns {Promise<string>} Path to the extracted binary
459
+ */
460
+ async function extractBinary(assetPath, outputDir) {
461
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
462
+ console.log(`Extracting ${path.basename(assetPath)}...`);
463
+ }
464
+
465
+ const assetName = path.basename(assetPath);
466
+ const isWindows = os.platform() === 'win32';
467
+ // Use the correct binary name: probe.exe for Windows, probe-binary for Unix
468
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
469
+ const binaryPath = path.join(outputDir, binaryName);
470
+
471
+ try {
472
+ // Create a temporary extraction directory
473
+ const extractDir = path.join(outputDir, 'temp_extract');
474
+ await fs.ensureDir(extractDir);
475
+
476
+ // Determine file type and extract accordingly
477
+ if (assetName.endsWith('.tar.gz') || assetName.endsWith('.tgz')) {
478
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
479
+ console.log(`Extracting tar.gz to ${extractDir}...`);
480
+ }
481
+ await tar.extract({
482
+ file: assetPath,
483
+ cwd: extractDir
484
+ });
485
+ } else if (assetName.endsWith('.zip')) {
486
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
487
+ console.log(`Extracting zip to ${extractDir}...`);
488
+ }
489
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
490
+ } else {
491
+ // Assume it's a direct binary
492
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
493
+ console.log(`Copying binary directly to ${binaryPath}`);
494
+ }
495
+ await fs.copyFile(assetPath, binaryPath);
496
+
497
+ // Make the binary executable
498
+ if (!isWindows) {
499
+ await fs.chmod(binaryPath, 0o755);
500
+ }
501
+
502
+ // Clean up the extraction directory
503
+ await fs.remove(extractDir);
504
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
505
+ console.log(`Binary installed to ${binaryPath}`);
506
+ }
507
+ return binaryPath;
508
+ }
509
+
510
+ // Find the binary in the extracted files
511
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
512
+ console.log(`Searching for binary in extracted files...`);
513
+ }
514
+ const findBinary = async (dir) => {
515
+ const entries = await fs.readdir(dir, { withFileTypes: true });
516
+
517
+ for (const entry of entries) {
518
+ const fullPath = path.join(dir, entry.name);
519
+
520
+ if (entry.isDirectory()) {
521
+ const result = await findBinary(fullPath);
522
+ if (result) return result;
523
+ } else if (entry.isFile()) {
524
+ // Check if this is the binary we're looking for
525
+ if (entry.name === binaryName ||
526
+ entry.name === BINARY_NAME ||
527
+ (isWindows && entry.name.endsWith('.exe'))) {
528
+ return fullPath;
529
+ }
530
+ }
531
+ }
532
+
533
+ return null;
534
+ };
535
+
536
+ const binaryFilePath = await findBinary(extractDir);
537
+
538
+ if (!binaryFilePath) {
539
+ // List all extracted files for debugging
540
+ const allFiles = await fs.readdir(extractDir, { recursive: true });
541
+ console.error(`Binary not found in extracted files. Found: ${allFiles.join(', ')}`);
542
+ throw new Error(`Binary not found in the archive.`);
543
+ }
544
+
545
+ // Copy the binary directly to the final location
546
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
547
+ console.log(`Found binary at ${binaryFilePath}`);
548
+ console.log(`Copying binary to ${binaryPath}`);
549
+ }
550
+ await fs.copyFile(binaryFilePath, binaryPath);
551
+
552
+ // Make the binary executable
553
+ if (!isWindows) {
554
+ await fs.chmod(binaryPath, 0o755);
555
+ }
556
+
557
+ // Clean up
558
+ await fs.remove(extractDir);
559
+
560
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
561
+ console.log(`Binary successfully installed to ${binaryPath}`);
562
+ }
563
+ return binaryPath;
564
+ } catch (error) {
565
+ console.error(`Error extracting binary: ${error instanceof Error ? error.message : String(error)}`);
566
+ throw error;
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Gets version info from the version file
572
+ * @returns {Promise<Object|null>} Version information
573
+ */
574
+ async function getVersionInfo(binDir) {
575
+ try {
576
+ const versionInfoPath = path.join(binDir, 'version-info.json');
577
+ if (await fs.pathExists(versionInfoPath)) {
578
+ const content = await fs.readFile(versionInfoPath, 'utf-8');
579
+ return JSON.parse(content);
580
+ }
581
+ return null;
582
+ } catch (error) {
583
+ console.warn(`Warning: Could not read version info: ${error}`);
584
+ return null;
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Saves version info to the version file
590
+ * @param {string} version - Version to save
591
+ * @param {string} binDir - Directory where version info should be saved
592
+ * @returns {Promise<void>}
593
+ */
594
+ async function saveVersionInfo(version, binDir) {
595
+ const versionInfo = {
596
+ version,
597
+ lastUpdated: new Date().toISOString()
598
+ };
599
+
600
+ const versionInfoPath = path.join(binDir, 'version-info.json');
601
+ await fs.writeFile(versionInfoPath, JSON.stringify(versionInfo, null, 2));
602
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
603
+ console.log(`Version info saved: ${version} at ${versionInfoPath}`);
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Gets the package version from package.json
609
+ * @returns {Promise<string>} Package version
610
+ */
611
+ async function getPackageVersion() {
612
+ try {
613
+ // Try multiple possible locations for package.json
614
+ const possiblePaths = [
615
+ path.resolve(__dirname, '..', 'package.json'), // When installed from npm: src/../package.json
616
+ path.resolve(__dirname, '..', '..', 'package.json') // In development: src/../../package.json
617
+ ];
618
+
619
+ for (const packageJsonPath of possiblePaths) {
620
+ try {
621
+ if (fs.existsSync(packageJsonPath)) {
622
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
623
+ console.log(`Found package.json at: ${packageJsonPath}`);
624
+ }
625
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
626
+ if (packageJson.version) {
627
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
628
+ console.log(`Using version from package.json: ${packageJson.version}`);
629
+ }
630
+ return packageJson.version;
631
+ }
632
+ }
633
+ } catch (err) {
634
+ console.error(`Error reading package.json at ${packageJsonPath}:`, err);
635
+ }
636
+ }
637
+
638
+ // If we can't find the version in package.json, return a default version
639
+ return '0.0.0';
640
+ } catch (error) {
641
+ console.error('Error getting package version:', error);
642
+ return '0.0.0';
643
+ }
644
+ }
645
+
646
+ /**
647
+ * Downloads the probe binary
648
+ * @param {string} [version] - Specific version to download
649
+ * @returns {Promise<string>} Path to the downloaded binary
650
+ */
651
+ export async function downloadProbeBinary(version) {
652
+ try {
653
+ // Get writable directory for binary storage (handles CI, npx, Docker scenarios)
654
+ const localDir = await getPackageBinDir();
655
+
656
+ // If no version is specified, use the package version
657
+ if (!version || version === '0.0.0') {
658
+ version = await getPackageVersion();
659
+ }
660
+
661
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
662
+ console.log(`Downloading probe binary (version: ${version || 'latest'})...`);
663
+ console.log(`Using binary directory: ${localDir}`);
664
+ }
665
+
666
+ const isWindows = os.platform() === 'win32';
667
+ // Use the correct binary name: probe.exe for Windows, probe-binary for Unix
668
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
669
+ const binaryPath = path.join(localDir, binaryName);
670
+
671
+ // Check if the binary already exists and version matches
672
+ if (await fs.pathExists(binaryPath)) {
673
+ const versionInfo = await getVersionInfo(localDir);
674
+
675
+ // If versions match, use existing binary
676
+ if (versionInfo && versionInfo.version === version) {
677
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
678
+ console.log(`Using existing binary at ${binaryPath} (version: ${versionInfo.version})`);
679
+ }
680
+ return binaryPath;
681
+ }
682
+
683
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
684
+ console.log(`Existing binary version (${versionInfo?.version || 'unknown'}) doesn't match requested version (${version}). Downloading new version...`);
685
+ }
686
+ }
687
+
688
+ // Get OS and architecture information
689
+ const { os: osInfo, arch: archInfo } = detectOsArch();
690
+
691
+ // Determine which version to download
692
+ let versionToUse = version;
693
+ let bestAsset;
694
+ let tagVersion;
695
+
696
+ if (!versionToUse || versionToUse === '0.0.0') {
697
+ // No specific version - use GitHub API to get the latest release
698
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
699
+ console.log('No specific version requested, will use the latest release');
700
+ }
701
+ const { tag, assets } = await getLatestRelease(undefined);
702
+ tagVersion = tag.startsWith('v') ? tag.substring(1) : tag;
703
+ bestAsset = findBestAsset(assets, osInfo, archInfo);
704
+
705
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
706
+ console.log(`Found release version: ${tagVersion}`);
707
+ }
708
+ } else {
709
+ // Specific version requested - construct download URL directly
710
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
711
+ console.log(`Direct download for version: ${versionToUse}`);
712
+ }
713
+ tagVersion = versionToUse;
714
+ bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
715
+ }
716
+ const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
717
+
718
+ // Verify checksum if available
719
+ const checksumValid = await verifyChecksum(assetPath, checksumPath);
720
+ if (!checksumValid) {
721
+ throw new Error('Checksum verification failed');
722
+ }
723
+
724
+ // Extract the binary
725
+ const extractedBinaryPath = await extractBinary(assetPath, localDir);
726
+
727
+ // Save the version information
728
+ await saveVersionInfo(tagVersion, localDir);
729
+
730
+ // Clean up the downloaded archive
731
+ try {
732
+ await fs.remove(assetPath);
733
+ if (checksumPath) {
734
+ await fs.remove(checksumPath);
735
+ }
736
+ } catch (err) {
737
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
738
+ console.log(`Warning: Could not clean up temporary files: ${err}`);
739
+ }
740
+ }
741
+
742
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
743
+ console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
744
+ }
745
+ return extractedBinaryPath;
746
+ } catch (error) {
747
+ console.error('Error downloading probe binary:', error);
748
+ throw error;
749
+ }
750
+ }