vibesuite 1.0.1 → 1.0.2

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/package.json +1 -1
  2. package/src/utils.js +56 -50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibesuite",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A Complete System for AI-Human Software Development Collaboration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/utils.js CHANGED
@@ -2,6 +2,10 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import https from 'https';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const execAsync = promisify(exec);
5
9
 
6
10
  const __filename = fileURLToPath(import.meta.url);
7
11
  const __dirname = path.dirname(__filename);
@@ -23,81 +27,74 @@ export const PATHS = {
23
27
  };
24
28
 
25
29
  /**
26
- * Fetch file content from GitHub API (which works better than raw.githubusercontent.com in some networks)
27
- * Handles large files by detecting when content is truncated
30
+ * Delay utility for rate limiting
31
+ * @param {number} ms - Milliseconds to delay
32
+ * @returns {Promise<void>}
33
+ */
34
+ function delay(ms) {
35
+ return new Promise(resolve => setTimeout(resolve, ms));
36
+ }
37
+
38
+ /**
39
+ * Fetch file content from raw.githubusercontent.com
40
+ * This avoids GitHub API rate limits (60 requests/hour for unauthenticated)
41
+ * Raw URLs have much higher limits and work better for file downloads
28
42
  * @param {string} relativePath - Path relative to repo root
29
43
  * @param {number} retries - Number of retries (default: 3)
30
44
  * @returns {Promise<string>} File content
31
45
  */
32
46
  export async function fetchFromGitHub(relativePath, retries = 3) {
33
- // Use GitHub API to get file content (base64 encoded)
34
- const apiUrl = `https://api.github.com/repos/${GITHUB_REPO}/contents/${relativePath}?ref=${GITHUB_BRANCH}`;
47
+ // Use raw GitHub content URL to avoid API rate limits
48
+ const rawUrl = `${GITHUB_RAW_URL}/${relativePath}`;
35
49
 
36
50
  return new Promise((resolve, reject) => {
37
51
  const attempt = (remainingRetries) => {
38
- https.get(apiUrl, {
52
+ const req = https.get(rawUrl, {
39
53
  timeout: 15000,
40
54
  headers: {
41
- 'User-Agent': 'vibesuite-cli',
42
- 'Accept': 'application/vnd.github.v3+json'
55
+ 'User-Agent': 'vibesuite-cli'
43
56
  }
44
57
  }, (res) => {
45
58
  let data = '';
46
59
  res.on('data', chunk => data += chunk);
47
60
  res.on('end', () => {
48
- try {
49
- if (res.statusCode === 200) {
50
- const json = JSON.parse(data);
51
- if (json.content) {
52
- // Decode base64 content (handle both single line and multi-line base64)
53
- const base64Content = json.content.replace(/\n/g, '');
54
- const content = Buffer.from(base64Content, 'base64').toString('utf8');
55
- resolve(content);
56
- } else if (json.download_url) {
57
- // Large files may not have content inline - this is a limitation
58
- // For now, reject so fallback to local files can occur
59
- reject(new Error('File too large for API fetch'));
60
- } else {
61
- reject(new Error('No content in response'));
62
- }
63
- } else if (res.statusCode === 404) {
64
- reject(new Error('File not found (404)'));
65
- } else if (res.statusCode === 403) {
66
- reject(new Error('GitHub API rate limit exceeded (403)'));
67
- } else {
68
- reject(new Error(`HTTP ${res.statusCode}`));
69
- }
70
- } catch (e) {
71
- reject(new Error('Failed to parse response'));
61
+ if (res.statusCode === 200) {
62
+ resolve(data);
63
+ } else if (res.statusCode === 404) {
64
+ reject(new Error('File not found (404)'));
65
+ } else if (res.statusCode === 403) {
66
+ reject(new Error('Access forbidden (403)'));
67
+ } else {
68
+ reject(new Error(`HTTP ${res.statusCode}`));
72
69
  }
73
70
  });
74
- }).on('error', (err) => {
71
+ });
72
+
73
+ req.on('error', async (err) => {
75
74
  if (remainingRetries > 0) {
76
75
  setTimeout(() => attempt(remainingRetries - 1), 2000);
77
76
  } else {
78
- reject(err);
77
+ // Fallback to curl if Node https fails (e.g. proxy/network issues)
78
+ try {
79
+ // Ensure we use a timeout with curl too
80
+ const { stdout } = await execAsync(`curl -sL --max-time 15 "${rawUrl}"`);
81
+ if (stdout) resolve(stdout);
82
+ else reject(err);
83
+ } catch (curlErr) {
84
+ reject(err); // Return original error if curl also fails
85
+ }
79
86
  }
80
87
  });
88
+
89
+ req.on('timeout', () => {
90
+ req.destroy(new Error('Request timed out'));
91
+ });
81
92
  };
82
93
 
83
94
  attempt(retries);
84
95
  });
85
96
  }
86
97
 
87
- function handleResponse(res, resolve, reject) {
88
- if (res.statusCode === 200) {
89
- let data = '';
90
- res.on('data', chunk => data += chunk);
91
- res.on('end', () => resolve(data));
92
- } else if (res.statusCode === 404) {
93
- reject(new Error(`File not found (404)`));
94
- } else if (res.statusCode === 403) {
95
- reject(new Error(`GitHub rate limit exceeded or access forbidden (403)`));
96
- } else {
97
- reject(new Error(`HTTP ${res.statusCode}`));
98
- }
99
- }
100
-
101
98
  /**
102
99
  * Fetch directory listing from GitHub API
103
100
  * @param {string} relativePath - Path relative to repo root
@@ -156,12 +153,15 @@ export async function downloadFromGitHub(relativePath, destPath) {
156
153
 
157
154
  /**
158
155
  * Recursively download a directory from GitHub
156
+ * Uses API only for directory listings, raw URLs for file downloads
157
+ * Includes rate limiting delays to avoid hitting API limits
159
158
  * @param {string} relativePath - Path relative to repo root
160
159
  * @param {string} destPath - Local destination path
161
160
  * @param {Function} filter - Optional filter function for items
161
+ * @param {number} delayMs - Delay between file downloads in ms (default: 100)
162
162
  * @returns {Promise<number>} Number of files downloaded
163
163
  */
164
- export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null) {
164
+ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null, delayMs = 100) {
165
165
  let count = 0;
166
166
 
167
167
  try {
@@ -173,10 +173,16 @@ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter
173
173
  if (item.type === 'file') {
174
174
  const fileDest = path.join(destPath, item.name);
175
175
  const success = await downloadFromGitHub(item.path, fileDest);
176
- if (success) count++;
176
+ if (success) {
177
+ count++;
178
+ // Add small delay between file downloads to be nice to GitHub
179
+ if (delayMs > 0) {
180
+ await delay(delayMs);
181
+ }
182
+ }
177
183
  } else if (item.type === 'dir') {
178
184
  const subDirDest = path.join(destPath, item.name);
179
- const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter);
185
+ const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter, delayMs);
180
186
  count += subCount;
181
187
  }
182
188
  }