iterate-skill-installer 3.2.3 → 3.3.1

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 (2) hide show
  1. package/lib/installer.js +38 -10
  2. package/package.json +1 -1
package/lib/installer.js CHANGED
@@ -193,14 +193,43 @@ function isGithubApiUrl(url) {
193
193
  return hostname === 'api.github.com' || hostname.endsWith('.api.github.com');
194
194
  }
195
195
 
196
+ /**
197
+ * Normalize a caller-supplied GitHub token.
198
+ *
199
+ * Tokens frequently arrive with surrounding whitespace (a trailing newline
200
+ * from `export GITHUB_TOKEN=$(cat secret)` or a shell alias that echoes the
201
+ * value); an untrimmed token makes every authenticated call fail with 401
202
+ * while silently burning the user's GitHub API rate limit. An empty result is
203
+ * treated as "no token".
204
+ */
205
+ function normalizeToken(raw) {
206
+ if (raw == null) return null;
207
+ const trimmed = String(raw).trim();
208
+ return trimmed === '' ? null : trimmed;
209
+ }
210
+
211
+ /**
212
+ * Compose the curl flags that attach the caller's PAT — and only those. The
213
+ * token is never attached to a non-API host; and marking it up differently
214
+ * from install.py would let the two installers disagree about credentials.
215
+ *
216
+ * Critically, a custom `-H "Authorization: Bearer ..."` header is echoed by
217
+ * curl onto EVERY host in a `-L` redirect chain (unlike `--user`, curl has no
218
+ * notion of stripping auth on a host change for raw headers). When the token
219
+ * is attached we therefore forbid redirects outright: a redirect then fails
220
+ * loudly instead of leaking the PAT to a foreign host.
221
+ */
222
+ function buildAuthFlags(url, token) {
223
+ if (!token) return [];
224
+ if (!isGithubApiUrl(url)) return [];
225
+ return ['-H', `Authorization: Bearer ${token}`, '--max-redirs', '0'];
226
+ }
227
+
196
228
  async function fetchJson(url, token) {
197
229
  // Prefer curl over Node.js fetch because curl uses the system CA store
198
230
  // and avoids Node-specific certificate issues in some environments.
199
231
  const failFlag = (await supportsCurlFailWithBody()) ? '--fail-with-body' : '--fail';
200
- const args = ['-sSL', failFlag, '--max-time', CURL_MAX_TIME_SECONDS, '--max-filesize', String(JSON_MAX_BYTES), '-H', 'Accept: application/vnd.github+json', '-H', 'X-GitHub-Api-Version: 2022-11-28', '-H', 'User-Agent: iterate-skill-installer'];
201
- if (token && isGithubApiUrl(url)) {
202
- args.push('-H', `Authorization: Bearer ${token}`);
203
- }
232
+ const args = ['-sSL', failFlag, '--max-time', CURL_MAX_TIME_SECONDS, '--max-filesize', String(JSON_MAX_BYTES), '-H', 'Accept: application/vnd.github+json', '-H', 'X-GitHub-Api-Version: 2022-11-28', '-H', 'User-Agent: iterate-skill-installer', ...buildAuthFlags(url, token)];
204
233
  args.push(url);
205
234
  const stdout = await runCommand('curl', args);
206
235
  try {
@@ -224,11 +253,8 @@ async function fetchJson(url, token) {
224
253
  */
225
254
  async function downloadFile(url, destPath, token, { progress = false, maxBytes = TARBALL_MAX_BYTES } = {}) {
226
255
  const failFlag = (await supportsCurlFailWithBody()) ? '--fail-with-body' : '--fail';
227
- const args = ['-sSL', failFlag, '--max-time', CURL_MAX_TIME_SECONDS, '--max-filesize', String(maxBytes), '-o', destPath, '-H', 'User-Agent: iterate-skill-installer'];
256
+ const args = ['-sSL', failFlag, '--max-time', CURL_MAX_TIME_SECONDS, '--max-filesize', String(maxBytes), '-o', destPath, '-H', 'User-Agent: iterate-skill-installer', ...buildAuthFlags(url, token)];
228
257
  if (progress) args.push('--progress-bar');
229
- if (token && isGithubApiUrl(url)) {
230
- args.push('-H', `Authorization: Bearer ${token}`);
231
- }
232
258
  args.push(url);
233
259
  if (progress) {
234
260
  // Let curl write its progress bar directly to the terminal.
@@ -591,7 +617,7 @@ function parseArgs(argv) {
591
617
  target: null,
592
618
  force: false,
593
619
  noCli: false,
594
- token: process.env.GITHUB_TOKEN || null,
620
+ token: normalizeToken(process.env.GITHUB_TOKEN),
595
621
  // Non-install action requested via -h/--help/-v/--version. bin/cli.js
596
622
  // inspects this before running main() and exits 0 without installing.
597
623
  mode: null,
@@ -660,7 +686,7 @@ function parseArgs(argv) {
660
686
  console.error('Error: --token requires a value (a flag was found instead)');
661
687
  process.exit(1);
662
688
  }
663
- options.token = next;
689
+ options.token = normalizeToken(next);
664
690
  i++;
665
691
  break;
666
692
  default:
@@ -860,5 +886,7 @@ module.exports = {
860
886
  extractTarball,
861
887
  rejectLinkEntries,
862
888
  isGithubApiUrl,
889
+ normalizeToken,
890
+ buildAuthFlags,
863
891
  supportsCurlFailWithBody,
864
892
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iterate-skill-installer",
3
- "version": "3.2.3",
3
+ "version": "3.3.1",
4
4
  "description": "One-command installer for iterate-skill across AI coding assistants",
5
5
  "main": "lib/installer.js",
6
6
  "files": [