@atlas-coder/atlas-agent 0.2.1 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlas-coder/atlas-agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Atlas Agent — terminal-first AI coding assistant. Downloads the native binary on install.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -1,6 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  // Postinstall: download the platform-specific binary from GitHub Releases.
3
- // Errors are printed to stderr and exit 1 so npm install surfaces them.
3
+ //
4
+ // Robustness upgrades vs the original:
5
+ // * Retries transient network errors and 5xx responses (3 attempts, jittered
6
+ // exponential backoff) so a flaky network doesn't break a fresh install.
7
+ // * Treats 404 distinctly: surfaces a clear "release not found" error
8
+ // instead of a generic 404, telling the user to upgrade the wrapper or
9
+ // re-trigger the GitHub release workflow.
10
+ // * Streams the response to disk (same as before) so a 100MB+ download
11
+ // doesn't buffer in memory.
12
+
4
13
  const fs = require('fs');
5
14
  const path = require('path');
6
15
  const https = require('https');
@@ -16,55 +25,114 @@ const REPO = 'Omerfaruk-aydn/Atlas-Agent';
16
25
  const VERSION = require('../package.json').version;
17
26
  const TAG = `v${VERSION}`;
18
27
 
28
+ const MAX_ATTEMPTS = 3;
29
+ const BASE_DELAY_MS = 500;
30
+
19
31
  const binDir = path.join(__dirname, '..', 'bin');
20
32
  fs.mkdirSync(binDir, { recursive: true });
21
33
  const dest = path.join(binDir, assetName);
22
34
 
23
- function download(url, redirectsLeft) {
35
+ function sleep(ms) {
36
+ return new Promise((r) => setTimeout(r, ms));
37
+ }
38
+
39
+ function download(url, redirectsLeft, attempt) {
24
40
  if (redirectsLeft == null) redirectsLeft = 5;
41
+ if (attempt == null) attempt = 1;
25
42
  return new Promise((resolve, reject) => {
26
43
  const req = https.get(url, { headers: { 'User-Agent': 'atlas-agent-installer' } }, (res) => {
27
- // Follow redirects
28
44
  if ([301, 302, 303, 307, 308].includes(res.statusCode)) {
29
45
  if (redirectsLeft <= 0) return reject(new Error('Too many redirects for ' + url));
30
46
  const next = res.headers.location;
31
47
  if (!next) return reject(new Error('Redirect with no Location header'));
32
48
  res.resume();
33
- return resolve(download(new URL(next, url).toString(), redirectsLeft - 1));
49
+ return resolve(download(new URL(next, url).toString(), redirectsLeft - 1, attempt));
50
+ }
51
+ if (res.statusCode === 404) {
52
+ res.resume();
53
+ return reject(new Error(
54
+ `HTTP 404: release ${TAG} has no ${assetName} asset. ` +
55
+ `The npm wrapper is at v${VERSION} but no matching GitHub release exists. ` +
56
+ `Either upgrade the wrapper (npm i -g @atlas-coder/atlas-agent@latest) ` +
57
+ `or re-run the Atlas Agent release workflow on GitHub.`
58
+ ));
59
+ }
60
+ if (res.statusCode >= 500 && attempt < MAX_ATTEMPTS) {
61
+ res.resume();
62
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 250);
63
+ console.warn(
64
+ `atlas-agent installer: HTTP ${res.statusCode} from ${url} ` +
65
+ `(attempt ${attempt}/${MAX_ATTEMPTS}), retrying in ${delay}ms`
66
+ );
67
+ return sleep(delay).then(() => resolve(download(url, redirectsLeft, attempt + 1)));
34
68
  }
35
69
  if (res.statusCode !== 200) {
70
+ res.resume();
36
71
  return reject(new Error('HTTP ' + res.statusCode + ' for ' + url));
37
72
  }
38
73
  const tmp = dest + '.part';
39
74
  const out = fs.createWriteStream(tmp);
40
75
  res.pipe(out);
41
- out.on('finish', () => out.close(() => {
42
- try { fs.renameSync(tmp, dest); resolve(); }
43
- catch (e) { reject(e); }
44
- }));
76
+ out.on('finish', () =>
77
+ out.close(() => {
78
+ try {
79
+ fs.renameSync(tmp, dest);
80
+ resolve();
81
+ } catch (e) {
82
+ reject(e);
83
+ }
84
+ })
85
+ );
45
86
  out.on('error', reject);
46
- res.on('error', reject);
87
+ res.on('error', (err) => {
88
+ // Connection-level errors (ECONNRESET, ETIMEDOUT, ENOTFOUND...) — retry.
89
+ if (attempt < MAX_ATTEMPTS) {
90
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 250);
91
+ console.warn(
92
+ `atlas-agent installer: ${err.message} (attempt ${attempt}/${MAX_ATTEMPTS}), ` +
93
+ `retrying in ${delay}ms`
94
+ );
95
+ sleep(delay).then(() => resolve(download(url, redirectsLeft, attempt + 1)));
96
+ } else {
97
+ reject(err);
98
+ }
99
+ });
100
+ });
101
+ req.on('error', (err) => {
102
+ if (attempt < MAX_ATTEMPTS) {
103
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 250);
104
+ console.warn(
105
+ `atlas-agent installer: ${err.message} (attempt ${attempt}/${MAX_ATTEMPTS}), ` +
106
+ `retrying in ${delay}ms`
107
+ );
108
+ sleep(delay).then(() => resolve(download(url, redirectsLeft, attempt + 1)));
109
+ } else {
110
+ reject(err);
111
+ }
112
+ });
113
+ req.setTimeout(60000, () => {
114
+ req.destroy(new Error('Download timeout (60s)'));
47
115
  });
48
- req.on('error', reject);
49
- req.setTimeout(60000, () => { req.destroy(new Error('Download timeout (60s)')); });
50
116
  });
51
117
  }
52
118
 
53
119
  (async () => {
54
120
  const url = `https://github.com/${REPO}/releases/download/${TAG}/${assetName}`;
55
121
  console.log(`Atlas Agent postinstall: downloading ${assetName} from ${url}`);
56
- await download(url);
122
+ try {
123
+ await download(url);
124
+ } catch (err) {
125
+ console.error('Atlas Agent postinstall FAILED: ' + err.message);
126
+ console.error('URL: ' + url);
127
+ console.error('You can manually download it from:');
128
+ console.error(' https://github.com/' + REPO + '/releases/tag/' + TAG);
129
+ console.error('and place it at:');
130
+ console.error(' ' + dest);
131
+ process.exit(1);
132
+ }
57
133
  if (process.platform !== 'win32') {
58
134
  fs.chmodSync(dest, 0o755);
59
135
  }
60
136
  const size = (fs.statSync(dest).size / 1024 / 1024).toFixed(1);
61
137
  console.log(`Atlas Agent postinstall: installed ${assetName} (${size} MB)`);
62
- })().catch((err) => {
63
- console.error('Atlas Agent postinstall FAILED: ' + err.message);
64
- console.error('URL: https://github.com/' + REPO + '/releases/download/' + TAG + '/' + assetName);
65
- console.error('You can manually download it from:');
66
- console.error(' https://github.com/' + REPO + '/releases/tag/' + TAG);
67
- console.error('and place it at:');
68
- console.error(' ' + dest);
69
- process.exit(1);
70
- });
138
+ })();