@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.
- package/README.md +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- 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
|
+
}
|