takomi 2.1.34 → 2.1.35

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/src/utils.js CHANGED
@@ -2,10 +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';
5
+ import { execFile } from 'child_process';
6
6
  import { promisify } from 'util';
7
7
 
8
- const execAsync = promisify(exec);
8
+ const execFileAsync = promisify(execFile);
9
9
 
10
10
  // Reduced timeout to fail fast on bad connections
11
11
  const FETCH_TIMEOUT = 10000;
@@ -17,9 +17,18 @@ const __dirname = path.dirname(__filename);
17
17
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
18
18
 
19
19
  // GitHub repository configuration
20
- const GITHUB_REPO = 'JStaRFilms/VibeCode-Protocol-Suite';
20
+ const GITHUB_REPO = 'J-StaR-Films-Studios/VibeCode-Protocol-Suite';
21
21
  const GITHUB_BRANCH = 'main';
22
- const GITHUB_RAW_URL = `https://raw.githubusercontent.com/${GITHUB_REPO}/${GITHUB_BRANCH}`;
22
+
23
+ function githubRef(options = {}) {
24
+ const repo = options.repo || GITHUB_REPO;
25
+ const branch = options.branch || GITHUB_BRANCH;
26
+ return {
27
+ repo,
28
+ branch,
29
+ rawBaseUrl: `https://raw.githubusercontent.com/${repo}/${branch}`,
30
+ };
31
+ }
23
32
 
24
33
  export const PATHS = {
25
34
  root: PACKAGE_ROOT,
@@ -134,9 +143,10 @@ function delay(ms) {
134
143
  * @param {number} retries - Number of retries (default: 3)
135
144
  * @returns {Promise<string>} File content
136
145
  */
137
- export async function fetchFromGitHub(relativePath, retries = 3) {
146
+ export async function fetchFromGitHub(relativePath, retries = 3, options = {}) {
138
147
  // Use raw GitHub content URL to avoid API rate limits
139
- const rawUrl = `${GITHUB_RAW_URL}/${relativePath}`;
148
+ const { rawBaseUrl } = githubRef(options);
149
+ const rawUrl = `${rawBaseUrl}/${relativePath}`;
140
150
 
141
151
  return new Promise((resolve, reject) => {
142
152
  const attempt = (remainingRetries) => {
@@ -146,6 +156,19 @@ export async function fetchFromGitHub(relativePath, retries = 3) {
146
156
  'User-Agent': 'takomi-cli'
147
157
  }
148
158
  }, (res) => {
159
+ if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
160
+ res.resume();
161
+ const redirectedUrl = new URL(res.headers.location, rawUrl).toString();
162
+ https.get(redirectedUrl, {
163
+ timeout: FETCH_TIMEOUT,
164
+ headers: { 'User-Agent': 'takomi-cli' }
165
+ }, (redirectRes) => {
166
+ let redirectedData = '';
167
+ redirectRes.on('data', chunk => redirectedData += chunk);
168
+ redirectRes.on('end', () => redirectRes.statusCode === 200 ? resolve(redirectedData) : reject(new Error(`HTTP ${redirectRes.statusCode}`)));
169
+ }).on('error', reject);
170
+ return;
171
+ }
149
172
  let data = '';
150
173
  res.on('data', chunk => data += chunk);
151
174
  res.on('end', () => {
@@ -167,8 +190,8 @@ export async function fetchFromGitHub(relativePath, retries = 3) {
167
190
  } else {
168
191
  // Fallback to curl if Node https fails (e.g. proxy/network issues)
169
192
  try {
170
- // Ensure we use a timeout with curl too
171
- const { stdout } = await execAsync(`curl -sL --max-time ${CURL_TIMEOUT} "${rawUrl}"`);
193
+ // Ensure we use a timeout with curl too, without invoking a shell.
194
+ const { stdout } = await execFileAsync('curl', ['-sL', '--max-time', String(CURL_TIMEOUT), rawUrl]);
172
195
  if (stdout) resolve(stdout);
173
196
  else reject(err);
174
197
  } catch (curlErr) {
@@ -192,36 +215,49 @@ export async function fetchFromGitHub(relativePath, retries = 3) {
192
215
  * @param {string} relativePath - Path relative to repo root
193
216
  * @returns {Promise<Array>} Array of file objects
194
217
  */
195
- export async function fetchDirectoryListing(relativePath) {
196
- const apiUrl = `https://api.github.com/repos/${GITHUB_REPO}/contents/${relativePath}?ref=${GITHUB_BRANCH}`;
218
+ export async function fetchDirectoryListing(relativePath, options = {}) {
219
+ const { repo, branch } = githubRef(options);
220
+ const apiUrl = `https://api.github.com/repos/${repo}/contents/${relativePath}?ref=${branch}`;
197
221
 
198
222
  return new Promise((resolve, reject) => {
199
- https.get(apiUrl, {
200
- timeout: 10000,
201
- headers: {
202
- 'User-Agent': 'takomi-cli',
203
- 'Accept': 'application/vnd.github.v3+json'
204
- }
205
- }, (res) => {
206
- let data = '';
207
- res.on('data', chunk => data += chunk);
208
- res.on('end', () => {
209
- try {
210
- if (res.statusCode === 200) {
211
- const items = JSON.parse(data);
212
- resolve(Array.isArray(items) ? items : []);
213
- } else if (res.statusCode === 404) {
214
- resolve([]);
215
- } else if (res.statusCode === 403) {
216
- reject(new Error('GitHub API rate limit exceeded. Please try again later.'));
217
- } else {
218
- reject(new Error(`GitHub API error: ${res.statusCode}`));
219
- }
220
- } catch (e) {
221
- reject(new Error('Failed to parse GitHub API response'));
223
+ const requestUrl = (url, redirectsRemaining = 3) => {
224
+ const req = https.get(url, {
225
+ timeout: 10000,
226
+ headers: {
227
+ 'User-Agent': 'takomi-cli',
228
+ 'Accept': 'application/vnd.github.v3+json'
222
229
  }
230
+ }, (res) => {
231
+ if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location && redirectsRemaining > 0) {
232
+ res.resume();
233
+ requestUrl(new URL(res.headers.location, url).toString(), redirectsRemaining - 1);
234
+ return;
235
+ }
236
+
237
+ let data = '';
238
+ res.on('data', chunk => data += chunk);
239
+ res.on('end', () => {
240
+ try {
241
+ if (res.statusCode === 200) {
242
+ const items = JSON.parse(data);
243
+ resolve(Array.isArray(items) ? items : []);
244
+ } else if (res.statusCode === 404) {
245
+ resolve([]);
246
+ } else if (res.statusCode === 403) {
247
+ reject(new Error('GitHub API rate limit exceeded. Please try again later.'));
248
+ } else {
249
+ reject(new Error(`GitHub API error: ${res.statusCode}`));
250
+ }
251
+ } catch (e) {
252
+ reject(new Error('Failed to parse GitHub API response'));
253
+ }
254
+ });
223
255
  });
224
- }).on('error', reject);
256
+ req.on('timeout', () => req.destroy(new Error('Request timed out')));
257
+ req.on('error', reject);
258
+ };
259
+
260
+ requestUrl(apiUrl);
225
261
  });
226
262
  }
227
263
 
@@ -231,9 +267,9 @@ export async function fetchDirectoryListing(relativePath) {
231
267
  * @param {string} destPath - Local destination path
232
268
  * @returns {Promise<boolean>} Success status
233
269
  */
234
- export async function downloadFromGitHub(relativePath, destPath) {
270
+ export async function downloadFromGitHub(relativePath, destPath, options = {}) {
235
271
  try {
236
- const content = await fetchFromGitHub(relativePath);
272
+ const content = await fetchFromGitHub(relativePath, 3, options);
237
273
  await fs.ensureDir(path.dirname(destPath));
238
274
  await fs.writeFile(destPath, content, 'utf8');
239
275
  return true;
@@ -265,11 +301,11 @@ export async function downloadFromGitHub(relativePath, destPath) {
265
301
  * @param {number} delayMs - Delay between file downloads in ms (default: 100)
266
302
  * @returns {Promise<number>} Number of files downloaded
267
303
  */
268
- export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null, delayMs = 250) {
304
+ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null, delayMs = 250, options = {}) {
269
305
  let count = 0;
270
306
 
271
307
  try {
272
- const items = await fetchDirectoryListing(relativePath);
308
+ const items = await fetchDirectoryListing(relativePath, options);
273
309
 
274
310
  for (const item of items) {
275
311
  if (!useGitHub) break; // Allow aborting if we disabled GitHub midway
@@ -277,7 +313,7 @@ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter
277
313
 
278
314
  if (item.type === 'file') {
279
315
  const fileDest = path.join(destPath, item.name);
280
- const success = await downloadFromGitHub(item.path, fileDest);
316
+ const success = await downloadFromGitHub(item.path, fileDest, options);
281
317
  if (success) {
282
318
  count++;
283
319
  // Add small delay between file downloads to be nice to GitHub
@@ -287,12 +323,13 @@ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter
287
323
  }
288
324
  } else if (item.type === 'dir') {
289
325
  const subDirDest = path.join(destPath, item.name);
290
- const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter, delayMs);
326
+ const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter, delayMs, options);
291
327
  count += subCount;
292
328
  }
293
329
  }
294
330
  } catch (error) {
295
331
  console.error(`Error downloading directory ${relativePath}: ${error.message}`);
332
+ if (options.throwOnError) throw error;
296
333
  }
297
334
 
298
335
  return count;
@@ -459,7 +496,7 @@ export async function copyLegacyManual(destFolder) {
459
496
  export async function updateWorkflows(destFolder) {
460
497
  console.log('📡 Fetching latest workflows from GitHub...');
461
498
  await fs.ensureDir(destFolder);
462
- await downloadDirectoryFromGitHub('.agent/workflows', destFolder);
499
+ return downloadDirectoryFromGitHub('assets/.agent/workflows', destFolder, null, 250, { throwOnError: true });
463
500
  }
464
501
 
465
502
  /**
@@ -468,7 +505,7 @@ export async function updateWorkflows(destFolder) {
468
505
  export async function updateSkills(destFolder) {
469
506
  console.log('📡 Fetching latest skills from GitHub...');
470
507
  await fs.ensureDir(destFolder);
471
- await downloadDirectoryFromGitHub('.agent/skills', destFolder);
508
+ return downloadDirectoryFromGitHub('assets/.agent/skills', destFolder, null, 250, { throwOnError: true });
472
509
  }
473
510
 
474
511
  /**
@@ -477,7 +514,7 @@ export async function updateSkills(destFolder) {
477
514
  export async function updateAgentYamls(destFolder) {
478
515
  console.log('📡 Fetching latest Agent YAMLs from GitHub...');
479
516
  await fs.ensureDir(destFolder);
480
- await downloadDirectoryFromGitHub('Takomi-Agents', destFolder);
517
+ return downloadDirectoryFromGitHub('assets/Takomi-Agents', destFolder, null, 250, { throwOnError: true });
481
518
  }
482
519
 
483
520
  /**
@@ -486,5 +523,5 @@ export async function updateAgentYamls(destFolder) {
486
523
  export async function updateLegacyManual(destFolder) {
487
524
  console.log('📡 Fetching latest Legacy Protocols from GitHub...');
488
525
  await fs.ensureDir(destFolder);
489
- await downloadDirectoryFromGitHub('Legacy (Manual Method)', destFolder);
526
+ return downloadDirectoryFromGitHub('assets/Legacy', destFolder, null, 250, { throwOnError: true });
490
527
  }