apex-dev 3.10.18 → 3.10.20

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 (3) hide show
  1. package/cli.js +98 -26
  2. package/dist/index.js +14 -4
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -145,6 +145,8 @@ function getDownloadUrl() {
145
145
  return `https://github.com/Marcus-Mok-GH/apex-dev/releases/download/v${VERSION}/apex-dev-${platform}-${arch}`;
146
146
  }
147
147
 
148
+ const DOWNLOAD_TIMEOUT = 10 * 1000;
149
+
148
150
  function downloadBinary(destPath) {
149
151
  const url = getDownloadUrl();
150
152
  console.error(`Downloading apex-dev v${VERSION} for ${detectPlatform().platform}-${detectPlatform().arch}...`);
@@ -152,33 +154,80 @@ function downloadBinary(destPath) {
152
154
 
153
155
  return new Promise((resolve, reject) => {
154
156
  const file = fs.createWriteStream(destPath, { mode: 0o755 });
155
- https
156
- .get(url, (response) => {
157
- if (response.statusCode === 302 || response.statusCode === 301) {
158
- https.get(response.headers.location, (redirectRes) => {
159
- if (redirectRes.statusCode !== 200) {
160
- reject(new Error(`Download failed with status ${redirectRes.statusCode}`));
161
- return;
162
- }
163
- redirectRes.pipe(file);
164
- file.on("finish", () => {
165
- file.close();
166
- resolve();
167
- });
168
- }).on("error", reject);
169
- return;
157
+ let settled = false;
158
+
159
+ function cleanupAndReject(err) {
160
+ if (settled) return;
161
+ settled = true;
162
+ file.destroy();
163
+ try { fs.unlinkSync(destPath); } catch {}
164
+ reject(err);
165
+ }
166
+
167
+ function onFileError(err) {
168
+ cleanupAndReject(new Error(`Write stream error: ${err.message}`));
169
+ }
170
+
171
+ file.on("error", onFileError);
172
+
173
+ const req = https.get(url, (response) => {
174
+ if (response.statusCode === 302 || response.statusCode === 301) {
175
+ const redirectReq = https.get(response.headers.location, (redirectRes) => {
176
+ if (redirectRes.statusCode !== 200) {
177
+ cleanupAndReject(new Error(`Download failed with status ${redirectRes.statusCode}`));
178
+ return;
179
+ }
180
+
181
+ function onFinish() {
182
+ file.removeListener("error", onFileError);
183
+ redirectReq.removeListener("error", onRedirectError);
184
+ redirectReq.removeListener("timeout", onRedirectTimeout);
185
+ file.close();
186
+ resolve();
187
+ }
188
+
189
+ file.on("finish", onFinish);
190
+ redirectRes.pipe(file);
191
+ });
192
+
193
+ function onRedirectError(err) {
194
+ cleanupAndReject(new Error(`Redirect request error: ${err.message}`));
170
195
  }
171
- if (response.statusCode !== 200) {
172
- reject(new Error(`Download failed with status ${response.statusCode}`));
173
- return;
196
+ function onRedirectTimeout() {
197
+ cleanupAndReject(new Error("Download redirect timed out"));
174
198
  }
175
- response.pipe(file);
176
- file.on("finish", () => {
177
- file.close();
178
- resolve();
179
- });
180
- })
181
- .on("error", reject);
199
+
200
+ redirectReq.on("error", onRedirectError);
201
+ redirectReq.setTimeout(DOWNLOAD_TIMEOUT, onRedirectTimeout);
202
+ return;
203
+ }
204
+
205
+ if (response.statusCode !== 200) {
206
+ cleanupAndReject(new Error(`Download failed with status ${response.statusCode}`));
207
+ return;
208
+ }
209
+
210
+ function onFinish() {
211
+ file.removeListener("error", onFileError);
212
+ req.removeListener("error", onReqError);
213
+ req.removeListener("timeout", onReqTimeout);
214
+ file.close();
215
+ resolve();
216
+ }
217
+
218
+ file.on("finish", onFinish);
219
+ response.pipe(file);
220
+ });
221
+
222
+ function onReqError(err) {
223
+ cleanupAndReject(new Error(`Download request error: ${err.message}`));
224
+ }
225
+ function onReqTimeout() {
226
+ cleanupAndReject(new Error("Download timed out"));
227
+ }
228
+
229
+ req.on("error", onReqError);
230
+ req.setTimeout(DOWNLOAD_TIMEOUT, onReqTimeout);
182
231
  });
183
232
  }
184
233
 
@@ -186,7 +235,17 @@ async function ensureBinary() {
186
235
  const localPath = getLocalBinaryPath();
187
236
 
188
237
  if (fs.existsSync(localPath)) {
189
- return localPath;
238
+ // Check for empty/partial downloads and remove them
239
+ try {
240
+ const stat = fs.statSync(localPath);
241
+ if (stat.size === 0) {
242
+ fs.unlinkSync(localPath);
243
+ } else {
244
+ return localPath;
245
+ }
246
+ } catch {
247
+ try { fs.unlinkSync(localPath); } catch {}
248
+ }
190
249
  }
191
250
 
192
251
  try {
@@ -195,6 +254,7 @@ async function ensureBinary() {
195
254
  return localPath;
196
255
  } catch (err) {
197
256
  console.error(`Failed to download binary: ${err.message}`);
257
+ try { fs.unlinkSync(localPath); } catch {}
198
258
  return null;
199
259
  }
200
260
  }
@@ -221,6 +281,10 @@ function runWithBun() {
221
281
  const child = spawn(bunPath, [distPath, ...process.argv.slice(2)], {
222
282
  stdio: "inherit",
223
283
  });
284
+ child.on("error", (err) => {
285
+ console.error(`Failed to launch bun: ${err.message}`);
286
+ process.exit(1);
287
+ });
224
288
  child.on("exit", (code) => {
225
289
  process.exit(code || 0);
226
290
  });
@@ -229,6 +293,10 @@ function runWithBun() {
229
293
  const child = spawn(bunPath, [scriptPath, ...process.argv.slice(2)], {
230
294
  stdio: "inherit",
231
295
  });
296
+ child.on("error", (err) => {
297
+ console.error(`Failed to launch bun: ${err.message}`);
298
+ process.exit(1);
299
+ });
232
300
  child.on("exit", (code) => {
233
301
  process.exit(code || 0);
234
302
  });
@@ -329,6 +397,10 @@ async function main() {
329
397
  const child = spawn(binaryPath, args, {
330
398
  stdio: "inherit",
331
399
  });
400
+ child.on("error", (err) => {
401
+ console.error(`Failed to launch binary: ${err.message}`);
402
+ process.exit(1);
403
+ });
332
404
  child.on("exit", (code) => {
333
405
  process.exit(code || 0);
334
406
  });
package/dist/index.js CHANGED
@@ -867,16 +867,26 @@ The user asks you to implement a new feature. You respond in multiple steps:
867
867
  deepseek: { model: "deepseek/deepseek-chat-v3", temperature: 0.1, maxTokens: 8192 },
868
868
  minimax: { model: "minimax/minimax-01", temperature: 0.1, maxTokens: 8192 }
869
869
  };
870
- const _initialProvider = PROVIDERS[currentProvider];
870
+ const os = __require("os");
871
+ let savedProvider = null;
871
872
  try {
872
- const configPath = path.join(__require("os").homedir(), ".apex-dev", "config.json");
873
+ const configPath = path.join(os.homedir(), ".apex-dev", "config.json");
873
874
  if (fs.existsSync(configPath)) {
874
875
  const savedConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
875
- if (savedConfig[currentProvider] && !process.env[_initialProvider.envKey]) {
876
- process.env[_initialProvider.envKey] = savedConfig[currentProvider];
876
+ const hasEnvKey = Object.values(PROVIDERS).some((p) => process.env[p.envKey]);
877
+ if (!hasEnvKey) {
878
+ for (const [providerKey, provider] of Object.entries(PROVIDERS)) {
879
+ if (savedConfig[providerKey]) {
880
+ currentProvider = providerKey;
881
+ process.env[provider.envKey] = savedConfig[providerKey];
882
+ savedProvider = providerKey;
883
+ break;
884
+ }
885
+ }
877
886
  }
878
887
  }
879
888
  } catch (e) {}
889
+ const _initialProvider = PROVIDERS[currentProvider];
880
890
  const _initialKey = process.env[_initialProvider.envKey] || "no-key";
881
891
  let _internalClient = new OpenAI({
882
892
  apiKey: _initialKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-dev",
3
- "version": "3.10.18",
3
+ "version": "3.10.20",
4
4
  "description": "Apex AI - a friendly agentic coding assistant for the terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {