@qoder-ai/qodercli 0.0.10-preview → 0.0.13-preview
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 +5 -5
- package/package.json +12 -12
- package/scripts/install.js +84 -36
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ Qoder CLI is a powerful terminal-based AI assistant that understands your codeba
|
|
|
8
8
|
|
|
9
9
|
Install Qoder CLI using your preferred method:
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Curl + Bash
|
|
12
12
|
```sh
|
|
13
|
-
|
|
13
|
+
curl -fsSL https://qoder.com/install | bash
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
### Homebrew (macOS & Linux)
|
|
@@ -18,9 +18,9 @@ npm install -g @qoder-ai/qodercli
|
|
|
18
18
|
brew install --cask QoderAI/qoder/qodercli
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### NPM
|
|
22
22
|
```sh
|
|
23
|
-
|
|
23
|
+
npm install -g @qoder-ai/qodercli
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Usage
|
|
@@ -31,7 +31,7 @@ curl -fsSL https://qoder.com/install | bash
|
|
|
31
31
|
|
|
32
32
|
## Reporting Bugs
|
|
33
33
|
|
|
34
|
-
We welcome feedback. Use the `/
|
|
34
|
+
We welcome feedback. Use the `/feedback` command to report issues directly within Qoder CLI.
|
|
35
35
|
|
|
36
36
|
## Data collection, usage, and retention
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qoder-ai/qodercli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13-preview",
|
|
4
4
|
"description": "qodercli - npm installer",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -40,37 +40,37 @@
|
|
|
40
40
|
],
|
|
41
41
|
"preferGlobal": true,
|
|
42
42
|
"binaries": {
|
|
43
|
-
"version": "0.0.
|
|
43
|
+
"version": "0.0.13-preview",
|
|
44
44
|
"files": [
|
|
45
45
|
{
|
|
46
46
|
"os": "linux",
|
|
47
47
|
"arch": "amd64",
|
|
48
|
-
"url": "https://download.qoder.com/qodercli/releases/0.0.
|
|
49
|
-
"sha256": "
|
|
48
|
+
"url": "https://download.qoder.com/qodercli/releases/0.0.13-preview/qodercli_0.0.13-preview_linux_amd64.tar.gz",
|
|
49
|
+
"sha256": "057df68be713d018e0d50c65ecef8a6ddc95a75fffebdc9f89de0c5cbb4538b4"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"os": "linux",
|
|
53
53
|
"arch": "arm64",
|
|
54
|
-
"url": "https://download.qoder.com/qodercli/releases/0.0.
|
|
55
|
-
"sha256": "
|
|
54
|
+
"url": "https://download.qoder.com/qodercli/releases/0.0.13-preview/qodercli_0.0.13-preview_linux_arm64.tar.gz",
|
|
55
|
+
"sha256": "443123e1fc8478bf9670714d8bbcb30791219b6e525d6791e6313163df9df05f"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"os": "darwin",
|
|
59
59
|
"arch": "amd64",
|
|
60
|
-
"url": "https://download.qoder.com/qodercli/releases/0.0.
|
|
61
|
-
"sha256": "
|
|
60
|
+
"url": "https://download.qoder.com/qodercli/releases/0.0.13-preview/qodercli_0.0.13-preview_darwin_amd64.zip",
|
|
61
|
+
"sha256": "6975e931a150045df720095af0e05f9875ace65fd57ab93ee3eed31865b77bc3"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"os": "darwin",
|
|
65
65
|
"arch": "arm64",
|
|
66
|
-
"url": "https://download.qoder.com/qodercli/releases/0.0.
|
|
67
|
-
"sha256": "
|
|
66
|
+
"url": "https://download.qoder.com/qodercli/releases/0.0.13-preview/qodercli_0.0.13-preview_darwin_arm64.zip",
|
|
67
|
+
"sha256": "121e6320427adc9baf8cb7e6c3d3eabc3f95881f4b09fa07fe6e67c3290dad95"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"os": "windows",
|
|
71
71
|
"arch": "amd64",
|
|
72
|
-
"url": "https://download.qoder.com/qodercli/releases/0.0.
|
|
73
|
-
"sha256": "
|
|
72
|
+
"url": "https://download.qoder.com/qodercli/releases/0.0.13-preview/qodercli_0.0.13-preview_windows_amd64.zip",
|
|
73
|
+
"sha256": "e104df8f06fbbcf89f5b0e66a286c8eab6931ab59ede94878deae78e0562e879"
|
|
74
74
|
}
|
|
75
75
|
]
|
|
76
76
|
}
|
package/scripts/install.js
CHANGED
|
@@ -72,14 +72,18 @@ class QoderInstaller {
|
|
|
72
72
|
async downloadBinary(url, expectedSha256) {
|
|
73
73
|
console.log(`Downloading binary: ${url}`);
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// Create temporary directory for download operations
|
|
76
|
+
const os = require('os');
|
|
77
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qodercli-install-'));
|
|
78
|
+
|
|
79
|
+
// Ensure target directory exists
|
|
76
80
|
if (!fs.existsSync(BIN_DIR)) {
|
|
77
81
|
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
// Download file
|
|
84
|
+
// Download file to temporary directory
|
|
81
85
|
const filename = path.basename(url);
|
|
82
|
-
const archivePath = path.join(
|
|
86
|
+
const archivePath = path.join(tempDir, filename);
|
|
83
87
|
|
|
84
88
|
try {
|
|
85
89
|
await this.downloadFile(url, archivePath);
|
|
@@ -91,12 +95,19 @@ class QoderInstaller {
|
|
|
91
95
|
throw new Error(`Checksum mismatch. Expected: ${expectedSha256}, Got: ${actualSha256}`);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
// Extract file
|
|
98
|
+
// Extract file to temporary directory first
|
|
95
99
|
console.log('Extracting binary...');
|
|
96
|
-
|
|
100
|
+
const extractDir = path.join(tempDir, 'extract');
|
|
101
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
102
|
+
await this.extractArchive(archivePath, filename, extractDir);
|
|
103
|
+
|
|
104
|
+
// Move extracted binary to final destination
|
|
105
|
+
const extractedBinary = this.findExtractedBinary(extractDir);
|
|
106
|
+
if (extractedBinary.length === 0) {
|
|
107
|
+
throw new Error(`Binary file not found after extraction in ${extractDir}`);
|
|
108
|
+
}
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
fs.unlinkSync(archivePath);
|
|
110
|
+
fs.renameSync(extractedBinary[0], this.binPath);
|
|
100
111
|
|
|
101
112
|
// Set executable permission
|
|
102
113
|
if (process.platform !== 'win32') {
|
|
@@ -111,41 +122,33 @@ class QoderInstaller {
|
|
|
111
122
|
this.verifyInstallation();
|
|
112
123
|
|
|
113
124
|
} catch (error) {
|
|
114
|
-
// Cleanup failed download
|
|
115
|
-
if (fs.existsSync(archivePath)) {
|
|
116
|
-
fs.unlinkSync(archivePath);
|
|
117
|
-
}
|
|
118
125
|
throw error;
|
|
126
|
+
} finally {
|
|
127
|
+
// Always cleanup temporary directory
|
|
128
|
+
try {
|
|
129
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
130
|
+
} catch (cleanupError) {
|
|
131
|
+
console.warn('Warning: Failed to cleanup temporary directory:', cleanupError.message);
|
|
132
|
+
}
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
async extractArchive(archivePath, filename) {
|
|
136
|
+
async extractArchive(archivePath, filename, extractDir) {
|
|
123
137
|
if (filename.endsWith('.zip')) {
|
|
124
138
|
// Extract ZIP file
|
|
125
139
|
if (process.platform === 'win32') {
|
|
126
140
|
// Windows: Use PowerShell
|
|
127
141
|
try {
|
|
128
|
-
execSync(`powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${
|
|
142
|
+
execSync(`powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force"`, {
|
|
129
143
|
stdio: 'pipe'
|
|
130
144
|
});
|
|
131
|
-
|
|
132
|
-
// Additional verification - check if binary file exists after extraction
|
|
133
|
-
if (!fs.existsSync(this.binPath)) {
|
|
134
|
-
// Maybe the binary is in a subdirectory, let's check
|
|
135
|
-
const extractedFiles = this.findExtractedBinary(BIN_DIR);
|
|
136
|
-
if (extractedFiles.length > 0) {
|
|
137
|
-
fs.renameSync(extractedFiles[0], this.binPath);
|
|
138
|
-
} else {
|
|
139
|
-
throw new Error(`Binary file not found after extraction. Expected at: ${this.binPath}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
145
|
} catch (error) {
|
|
143
146
|
throw new Error(`ZIP extraction failed: ${error.message}. Please ensure PowerShell is available.`);
|
|
144
147
|
}
|
|
145
148
|
} else {
|
|
146
149
|
// Unix: Use unzip command
|
|
147
150
|
try {
|
|
148
|
-
execSync(`unzip -o "${archivePath}" -d "${
|
|
151
|
+
execSync(`unzip -o "${archivePath}" -d "${extractDir}"`, {
|
|
149
152
|
stdio: 'pipe'
|
|
150
153
|
});
|
|
151
154
|
} catch (error) {
|
|
@@ -155,7 +158,7 @@ class QoderInstaller {
|
|
|
155
158
|
} else {
|
|
156
159
|
// Extract tar.gz file
|
|
157
160
|
try {
|
|
158
|
-
execSync(`tar -xzf "${archivePath}" -C "${
|
|
161
|
+
execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, {
|
|
159
162
|
stdio: 'pipe'
|
|
160
163
|
});
|
|
161
164
|
} catch (error) {
|
|
@@ -217,19 +220,37 @@ class QoderInstaller {
|
|
|
217
220
|
return new Promise((resolve, reject) => {
|
|
218
221
|
const file = fs.createWriteStream(filePath);
|
|
219
222
|
const client = url.startsWith('https:') ? https : http;
|
|
223
|
+
let cleanupDone = false;
|
|
224
|
+
|
|
225
|
+
const cleanup = () => {
|
|
226
|
+
if (cleanupDone) return;
|
|
227
|
+
cleanupDone = true;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
file.close();
|
|
231
|
+
} catch (e) {
|
|
232
|
+
// Ignore errors during cleanup
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
if (fs.existsSync(filePath)) {
|
|
237
|
+
fs.unlinkSync(filePath);
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
// Ignore errors during cleanup
|
|
241
|
+
}
|
|
242
|
+
};
|
|
220
243
|
|
|
221
244
|
const request = client.get(url, (response) => {
|
|
222
245
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
223
246
|
// Handle redirect
|
|
224
|
-
|
|
225
|
-
fs.unlinkSync(filePath);
|
|
247
|
+
cleanup();
|
|
226
248
|
return this.downloadFile(response.headers.location, filePath, timeout)
|
|
227
249
|
.then(resolve).catch(reject);
|
|
228
250
|
}
|
|
229
251
|
|
|
230
252
|
if (response.statusCode !== 200) {
|
|
231
|
-
|
|
232
|
-
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
253
|
+
cleanup();
|
|
233
254
|
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
234
255
|
return;
|
|
235
256
|
}
|
|
@@ -237,26 +258,53 @@ class QoderInstaller {
|
|
|
237
258
|
response.pipe(file);
|
|
238
259
|
|
|
239
260
|
file.on('finish', () => {
|
|
240
|
-
|
|
241
|
-
|
|
261
|
+
if (!cleanupDone) {
|
|
262
|
+
file.close();
|
|
263
|
+
resolve();
|
|
264
|
+
}
|
|
242
265
|
});
|
|
243
266
|
|
|
244
267
|
file.on('error', (error) => {
|
|
245
|
-
|
|
268
|
+
cleanup();
|
|
246
269
|
reject(error);
|
|
247
270
|
});
|
|
248
271
|
}).on('error', (error) => {
|
|
249
|
-
|
|
272
|
+
cleanup();
|
|
250
273
|
reject(error);
|
|
251
274
|
});
|
|
252
275
|
|
|
253
276
|
// Set timeout
|
|
254
277
|
request.setTimeout(timeout, () => {
|
|
255
278
|
request.destroy();
|
|
256
|
-
|
|
257
|
-
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
279
|
+
cleanup();
|
|
258
280
|
reject(new Error(`Download timeout (${timeout}ms): ${url}`));
|
|
259
281
|
});
|
|
282
|
+
|
|
283
|
+
// Handle process interruption signals
|
|
284
|
+
const handleSignal = () => {
|
|
285
|
+
request.destroy();
|
|
286
|
+
cleanup();
|
|
287
|
+
reject(new Error('Download interrupted by signal'));
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
process.once('SIGINT', handleSignal);
|
|
291
|
+
process.once('SIGTERM', handleSignal);
|
|
292
|
+
|
|
293
|
+
// Clean up signal handlers when promise resolves/rejects
|
|
294
|
+
const originalResolve = resolve;
|
|
295
|
+
const originalReject = reject;
|
|
296
|
+
|
|
297
|
+
resolve = (...args) => {
|
|
298
|
+
process.removeListener('SIGINT', handleSignal);
|
|
299
|
+
process.removeListener('SIGTERM', handleSignal);
|
|
300
|
+
originalResolve(...args);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
reject = (...args) => {
|
|
304
|
+
process.removeListener('SIGINT', handleSignal);
|
|
305
|
+
process.removeListener('SIGTERM', handleSignal);
|
|
306
|
+
originalReject(...args);
|
|
307
|
+
};
|
|
260
308
|
});
|
|
261
309
|
}
|
|
262
310
|
|