@qodo/sdk 0.6.0 → 0.7.0
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.
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
zJsonAny,
|
|
24
24
|
QodoSchemaError,
|
|
25
25
|
QodoAuthError,
|
|
26
|
+
z
|
|
26
27
|
} from '@qodo/sdk';
|
|
27
|
-
import { z } from 'zod';
|
|
28
28
|
|
|
29
29
|
// =============================================================================
|
|
30
30
|
// STEP 1: Define Output Schemas
|
|
@@ -278,6 +278,7 @@ async function main() {
|
|
|
278
278
|
if (refactorResult.success) {
|
|
279
279
|
console.log('Refactor analysis complete!');
|
|
280
280
|
console.log('Changes that would be made:');
|
|
281
|
+
// @ts-ignore
|
|
281
282
|
refactorResult.structured_output?.changes_made?.forEach((change: any) => {
|
|
282
283
|
console.log(` - ${change.file}: ${change.description}`);
|
|
283
284
|
});
|
|
@@ -293,7 +294,7 @@ async function main() {
|
|
|
293
294
|
const promptResult = await sdk.prompt(
|
|
294
295
|
'What are the most common patterns you see in this codebase?'
|
|
295
296
|
);
|
|
296
|
-
|
|
297
|
+
// @ts-ignore
|
|
297
298
|
console.log('Response:', promptResult.final_output);
|
|
298
299
|
} catch (error) {
|
|
299
300
|
// Handle specific error types
|
package/bin/rg
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qodo/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Qodo SDK for building AI-powered agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
+
"bin",
|
|
26
|
+
"scripts",
|
|
25
27
|
".claude/skills",
|
|
26
28
|
"README.md",
|
|
27
29
|
"LICENSE"
|
|
@@ -47,7 +49,8 @@
|
|
|
47
49
|
"typecheck": "tsc --noEmit",
|
|
48
50
|
"test": "vitest run",
|
|
49
51
|
"test:watch": "vitest",
|
|
50
|
-
"prepublishOnly": "npm run build && npm run test"
|
|
52
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
53
|
+
"postinstall": "node scripts/download-ripgrep.js"
|
|
51
54
|
},
|
|
52
55
|
"repository": {
|
|
53
56
|
"type": "git",
|
|
@@ -74,7 +77,8 @@
|
|
|
74
77
|
"uuid": "^11.1.0",
|
|
75
78
|
"ws": "^8.18.3",
|
|
76
79
|
"yaml": "^2.8.1",
|
|
77
|
-
"zod-to-json-schema": "^3.25.1"
|
|
80
|
+
"zod-to-json-schema": "^3.25.1",
|
|
81
|
+
"tar": "^7.4.3"
|
|
78
82
|
},
|
|
79
83
|
"peerDependencies": {
|
|
80
84
|
"zod": "^3.25 || ^4.0",
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { pipeline } from 'stream/promises';
|
|
7
|
+
import { createWriteStream, createReadStream } from 'fs';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { exec } from 'child_process';
|
|
10
|
+
import https from 'https';
|
|
11
|
+
import zlib from 'zlib';
|
|
12
|
+
import * as tar from 'tar';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
// Constants
|
|
18
|
+
const RIPGREP_VERSION = 'v13.0.0-13'; // Latest version from microsoft/ripgrep-prebuilt
|
|
19
|
+
const RIPGREP_PRIMARY_BASE = 'https://rg.prod.qodo.ai'; // Primary CDN source
|
|
20
|
+
const RIPGREP_FALLBACK_BASE = 'https://github.com/microsoft/ripgrep-prebuilt/releases/download'; // Fallback source
|
|
21
|
+
const MAX_RETRIES = 3;
|
|
22
|
+
const RETRY_DELAY = 1000; // 1 second
|
|
23
|
+
|
|
24
|
+
// Platform mapping
|
|
25
|
+
const PLATFORM_MAP = {
|
|
26
|
+
'darwin-arm64': 'aarch64-apple-darwin',
|
|
27
|
+
'darwin-x64': 'x86_64-apple-darwin',
|
|
28
|
+
'linux-x64': 'x86_64-unknown-linux-musl',
|
|
29
|
+
'linux-arm64': 'aarch64-unknown-linux-musl',
|
|
30
|
+
'win32-x64': 'x86_64-pc-windows-msvc',
|
|
31
|
+
'win32-arm64': 'aarch64-pc-windows-msvc'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Get platform info
|
|
35
|
+
function getPlatformInfo() {
|
|
36
|
+
const platform = process.platform;
|
|
37
|
+
const arch = process.arch === 'x64' ? 'x64' : process.arch;
|
|
38
|
+
const key = `${platform}-${arch}`;
|
|
39
|
+
|
|
40
|
+
const ripgrepPlatform = PLATFORM_MAP[key];
|
|
41
|
+
if (!ripgrepPlatform) {
|
|
42
|
+
throw new Error(`Unsupported platform: ${key}. Supported platforms: ${Object.keys(PLATFORM_MAP).join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
platform,
|
|
47
|
+
arch,
|
|
48
|
+
ripgrepPlatform,
|
|
49
|
+
isWindows: platform === 'win32'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Sleep utility for retry delays
|
|
54
|
+
function sleep(ms) {
|
|
55
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Download file with progress and retry logic
|
|
59
|
+
async function downloadFile(url, dest, retryCount = 0) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const file = createWriteStream(dest);
|
|
62
|
+
|
|
63
|
+
https.get(url, (response) => {
|
|
64
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
65
|
+
// Handle redirect
|
|
66
|
+
file.close();
|
|
67
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
68
|
+
downloadFile(response.headers.location, dest, retryCount).then(resolve).catch(reject);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (response.statusCode !== 200) {
|
|
73
|
+
file.close();
|
|
74
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
75
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
80
|
+
let downloadedSize = 0;
|
|
81
|
+
|
|
82
|
+
response.on('data', (chunk) => {
|
|
83
|
+
downloadedSize += chunk.length;
|
|
84
|
+
if (totalSize) {
|
|
85
|
+
const percent = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
86
|
+
process.stdout.write(`\rDownloading ripgrep: ${percent}%`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
response.pipe(file);
|
|
91
|
+
|
|
92
|
+
file.on('finish', () => {
|
|
93
|
+
file.close();
|
|
94
|
+
console.log('\nDownload complete');
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
file.on('error', (err) => {
|
|
99
|
+
file.close();
|
|
100
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
101
|
+
reject(err);
|
|
102
|
+
});
|
|
103
|
+
}).on('error', (err) => {
|
|
104
|
+
file.close();
|
|
105
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
106
|
+
reject(err);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Download with retry and fallback logic
|
|
112
|
+
async function downloadWithRetry(urls, dest) {
|
|
113
|
+
for (let urlIndex = 0; urlIndex < urls.length; urlIndex++) {
|
|
114
|
+
const url = urls[urlIndex];
|
|
115
|
+
const isLastUrl = urlIndex === urls.length - 1;
|
|
116
|
+
|
|
117
|
+
for (let retry = 0; retry < MAX_RETRIES; retry++) {
|
|
118
|
+
const isLastRetry = retry === MAX_RETRIES - 1;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
console.log(`${retry > 0 ? 'Retrying' : 'Downloading'} from: ${url}${retry > 0 ? ` (attempt ${retry + 1}/${MAX_RETRIES})` : ''}`);
|
|
122
|
+
await downloadFile(url, dest);
|
|
123
|
+
return; // Success
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.log(`\nDownload failed: ${error.message}`);
|
|
126
|
+
|
|
127
|
+
if (isLastRetry && isLastUrl) {
|
|
128
|
+
// This was our last attempt with the last URL
|
|
129
|
+
throw error;
|
|
130
|
+
} else if (isLastRetry) {
|
|
131
|
+
// Last retry for this URL, try next URL
|
|
132
|
+
console.log(`Trying fallback source...`);
|
|
133
|
+
break;
|
|
134
|
+
} else {
|
|
135
|
+
// Not last retry, wait and try again with same URL
|
|
136
|
+
console.log(`Waiting ${RETRY_DELAY}ms before retry...`);
|
|
137
|
+
await sleep(RETRY_DELAY);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Extract ripgrep binary from archive
|
|
145
|
+
async function extractBinary(archivePath, platform, destDir) {
|
|
146
|
+
const binaryName = platform.isWindows ? 'rg.exe' : 'rg';
|
|
147
|
+
const destPath = path.join(destDir, binaryName);
|
|
148
|
+
|
|
149
|
+
if (platform.isWindows) {
|
|
150
|
+
// For Windows, it's a zip file
|
|
151
|
+
// We'll use the built-in tar command which supports zip on Windows 10+
|
|
152
|
+
try {
|
|
153
|
+
await execAsync(`tar -xf "${archivePath}" -C "${destDir}" "${binaryName}"`);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Fallback: try PowerShell
|
|
156
|
+
console.log('Trying PowerShell extraction...');
|
|
157
|
+
await execAsync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`);
|
|
158
|
+
// Move the binary from the extracted folder
|
|
159
|
+
const extractedDir = fs.readdirSync(destDir).find(f => f.startsWith('ripgrep-'));
|
|
160
|
+
if (extractedDir) {
|
|
161
|
+
const sourcePath = path.join(destDir, extractedDir, binaryName);
|
|
162
|
+
await fs.promises.rename(sourcePath, destPath);
|
|
163
|
+
await fs.promises.rmdir(path.join(destDir, extractedDir), { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// For Unix systems, it's a tar.gz
|
|
168
|
+
await tar.x({
|
|
169
|
+
file: archivePath,
|
|
170
|
+
cwd: destDir,
|
|
171
|
+
filter: (path) => path.endsWith(binaryName)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Make executable on Unix
|
|
176
|
+
if (!platform.isWindows) {
|
|
177
|
+
await fs.promises.chmod(destPath, 0o755);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return destPath;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Main function
|
|
184
|
+
async function main() {
|
|
185
|
+
console.log('Setting up ripgrep binary for @qodo/sdk...');
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Get platform info
|
|
189
|
+
const platformInfo = getPlatformInfo();
|
|
190
|
+
console.log(`Platform detected: ${platformInfo.platform}-${platformInfo.arch}`);
|
|
191
|
+
|
|
192
|
+
// Setup paths
|
|
193
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
194
|
+
const tempDir = path.join(__dirname, '..', '.tmp-ripgrep');
|
|
195
|
+
|
|
196
|
+
// Ensure directories exist
|
|
197
|
+
await fs.promises.mkdir(binDir, { recursive: true });
|
|
198
|
+
await fs.promises.mkdir(tempDir, { recursive: true });
|
|
199
|
+
|
|
200
|
+
// Check if ripgrep already exists
|
|
201
|
+
const binaryName = platformInfo.isWindows ? 'rg.exe' : 'rg';
|
|
202
|
+
const finalPath = path.join(binDir, binaryName);
|
|
203
|
+
|
|
204
|
+
if (fs.existsSync(finalPath)) {
|
|
205
|
+
console.log('Ripgrep binary already exists, verifying...');
|
|
206
|
+
try {
|
|
207
|
+
const { stdout } = await execAsync(`"${finalPath}" --version`);
|
|
208
|
+
console.log(`Existing ripgrep version: ${stdout.trim()}`);
|
|
209
|
+
|
|
210
|
+
// Clean up temp directory
|
|
211
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
212
|
+
return;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.log('Existing binary is invalid, re-downloading...');
|
|
215
|
+
await fs.promises.unlink(finalPath);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Build download URLs (primary and fallback)
|
|
220
|
+
const archiveExt = platformInfo.isWindows ? '.zip' : '.tar.gz';
|
|
221
|
+
const fileName = `ripgrep-${RIPGREP_VERSION}-${platformInfo.ripgrepPlatform}${archiveExt}`;
|
|
222
|
+
const primaryUrl = `${RIPGREP_PRIMARY_BASE}/${fileName}`;
|
|
223
|
+
const fallbackUrl = `${RIPGREP_FALLBACK_BASE}/${RIPGREP_VERSION}/${fileName}`;
|
|
224
|
+
const archivePath = path.join(tempDir, fileName);
|
|
225
|
+
|
|
226
|
+
// Download the archive with retry and fallback
|
|
227
|
+
await downloadWithRetry([primaryUrl, fallbackUrl], archivePath);
|
|
228
|
+
|
|
229
|
+
// Extract the binary
|
|
230
|
+
console.log('Extracting ripgrep binary...');
|
|
231
|
+
const extractedPath = await extractBinary(archivePath, platformInfo, tempDir);
|
|
232
|
+
|
|
233
|
+
// Move to final location
|
|
234
|
+
await fs.promises.rename(extractedPath, finalPath);
|
|
235
|
+
|
|
236
|
+
// Verify installation
|
|
237
|
+
const { stdout } = await execAsync(`"${finalPath}" --version`);
|
|
238
|
+
console.log(`Successfully installed: ${stdout.trim()}`);
|
|
239
|
+
|
|
240
|
+
// Clean up
|
|
241
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
242
|
+
|
|
243
|
+
console.log('Ripgrep setup complete!');
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('Error setting up ripgrep:', error.message);
|
|
246
|
+
|
|
247
|
+
// Clean up on error
|
|
248
|
+
const tempDir = path.join(__dirname, '..', '.tmp-ripgrep');
|
|
249
|
+
if (fs.existsSync(tempDir)) {
|
|
250
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check if running in CI or with --ignore-scripts
|
|
254
|
+
if (process.env.CI || process.env.npm_config_ignore_scripts) {
|
|
255
|
+
console.log('Running in CI or with --ignore-scripts, skipping ripgrep setup.');
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log('\nNote: @qodo/sdk will fall back to system ripgrep if available.');
|
|
260
|
+
process.exit(0); // Exit gracefully to not break npm install
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Run if called directly
|
|
265
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
266
|
+
main().catch(console.error);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { main as downloadRipgrep };
|