@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.6.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 };