git0 0.1.5 → 0.2.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 (3) hide show
  1. package/gg.js +246 -206
  2. package/package.json +7 -5
  3. package/readme.md +5 -2
package/gg.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import inquirer from 'inquirer';
4
4
  import chalk from 'chalk';
5
5
  import { execSync, spawn } from 'child_process';
6
- import axios from 'axios';
6
+ import grab from 'grab-api.js';
7
7
  import * as tar from 'tar'
8
8
  import { pipeline } from 'stream/promises';
9
9
  import fs from 'fs';
@@ -15,24 +15,47 @@ const GITHUB_API = 'https://api.github.com/search/repositories';
15
15
  const RESULTS_PER_PAGE = 10;
16
16
  const TOKEN = process.env.GITHUB_TOKEN;
17
17
 
18
+ const githubHelpUrl = 'https://github.com/settings/personal-access-tokens/new'
19
+ grab('', {
20
+ setDefaults: true,
21
+ debug: false,
22
+ onError: (error) => {
23
+ if (error.includes('403')) {
24
+ log(chalk.red('Rate limit exceeded. Please set env var GITHUB_TOKEN. Help:\n' + githubHelpUrl));
25
+ process.exit(1);
26
+
27
+ }
28
+ }
29
+ }
30
+ )
31
+
32
+ function printLogo() {
33
+ console.log(chalk.cyan(` ___
34
+ __ _(_)‾|_ / _ \\
35
+ / _ | | __| | | |
36
+ | (_| | | |_| |_| |
37
+ \\__, |_|\\__|\\___/
38
+ |___/`))
39
+ }
40
+
18
41
  // Detect current operating system and architecture
19
42
  function getCurrentPlatform() {
20
43
  const platform = os.platform();
21
44
  const arch = os.arch();
22
-
45
+
23
46
  const platformMap = {
24
47
  'win32': 'windows',
25
48
  'darwin': 'macos',
26
49
  'linux': 'linux'
27
50
  };
28
-
51
+
29
52
  const archMap = {
30
53
  'x64': 'x86_64',
31
54
  'arm64': 'arm64',
32
55
  'arm': 'arm',
33
56
  'ia32': 'i386'
34
57
  };
35
-
58
+
36
59
  return {
37
60
  os: platformMap[platform] || platform,
38
61
  arch: archMap[arch] || arch,
@@ -44,11 +67,13 @@ function getCurrentPlatform() {
44
67
  // Get releases for a repository
45
68
  async function getRepositoryReleases(owner, repo) {
46
69
  try {
47
- const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/releases`, {
70
+ const response = await grab(`https://api.github.com/repos/${owner}/${repo}/releases`, {
48
71
  headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
49
- params: { per_page: 5 } // Get latest 5 releases
72
+ per_page: 5 // Get latest 5 releases
73
+
50
74
  });
51
- return response.data;
75
+
76
+ return response;
52
77
  } catch (error) {
53
78
  return [];
54
79
  }
@@ -61,28 +86,28 @@ function categorizeReleasesByPlatform(releases) {
61
86
  macos: ['mac', 'macos', 'darwin', 'osx', '.dmg', '.pkg'],
62
87
  linux: ['linux', 'ubuntu', 'debian', '.deb', '.rpm', '.tar.gz', '.AppImage']
63
88
  };
64
-
89
+
65
90
  const archKeywords = {
66
91
  x86_64: ['x86_64', 'x64', 'amd64', '64'],
67
92
  arm64: ['arm64', 'aarch64'],
68
93
  arm: ['arm', 'armv7'],
69
94
  i386: ['i386', 'x86', '32']
70
95
  };
71
-
96
+
72
97
  const categorizedReleases = [];
73
-
74
- releases.forEach(release => {
98
+
99
+ Object.entries(releases).forEach(([key, release]) => {
75
100
  const platformAssets = {
76
101
  windows: [],
77
102
  macos: [],
78
103
  linux: [],
79
104
  universal: []
80
105
  };
81
-
106
+
82
107
  release.assets.forEach(asset => {
83
108
  const name = asset.name.toLowerCase();
84
109
  let categorized = false;
85
-
110
+
86
111
  // Check each platform
87
112
  Object.entries(platformKeywords).forEach(([platform, keywords]) => {
88
113
  if (keywords.some(keyword => name.includes(keyword.toLowerCase()))) {
@@ -93,7 +118,7 @@ function categorizeReleasesByPlatform(releases) {
93
118
  detectedArch = arch;
94
119
  }
95
120
  });
96
-
121
+
97
122
  platformAssets[platform].push({
98
123
  ...asset,
99
124
  detectedArch,
@@ -102,10 +127,10 @@ function categorizeReleasesByPlatform(releases) {
102
127
  categorized = true;
103
128
  }
104
129
  });
105
-
130
+
106
131
  // If not categorized, check for universal binaries
107
- if (!categorized && (name.includes('universal') || name.includes('all') ||
108
- (!name.includes('win') && !name.includes('mac') && !name.includes('linux')))) {
132
+ if (!categorized && (name.includes('universal') || name.includes('all') ||
133
+ (!name.includes('win') && !name.includes('mac') && !name.includes('linux')))) {
109
134
  platformAssets.universal.push({
110
135
  ...asset,
111
136
  detectedArch: 'universal',
@@ -113,7 +138,7 @@ function categorizeReleasesByPlatform(releases) {
113
138
  });
114
139
  }
115
140
  });
116
-
141
+
117
142
  // Only include releases that have assets
118
143
  const hasAssets = Object.values(platformAssets).some(assets => assets.length > 0);
119
144
  if (hasAssets) {
@@ -123,52 +148,53 @@ function categorizeReleasesByPlatform(releases) {
123
148
  });
124
149
  }
125
150
  });
126
-
151
+
127
152
  return categorizedReleases;
128
153
  }
129
154
 
130
155
  // Filter releases for current platform (for compatibility indicator)
131
156
  function filterReleasesByPlatform(releases, currentPlatform) {
132
157
  const categorized = categorizeReleasesByPlatform(releases);
133
- return categorized.filter(release =>
158
+ return categorized.filter(release =>
134
159
  release.platformAssets[currentPlatform.os].length > 0 ||
135
160
  release.platformAssets.universal.length > 0
136
161
  );
137
162
  }
138
163
 
164
+
139
165
  // Download and install package
140
166
  async function downloadPackage(asset, targetDir) {
141
167
  const fileName = asset.name;
142
168
  const downloadPath = path.join(targetDir, fileName);
143
-
144
- console.log(chalk.blue(`📦 Downloading ${fileName}...`));
145
-
169
+
170
+ log(chalk.blue(`📦 Downloading ${fileName}...`));
171
+ printLogo()
146
172
  try {
147
- const response = await axios({
148
- url: asset.browser_download_url,
149
- method: 'GET',
150
- responseType: 'stream',
151
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
173
+ const response = await grab(asset.browser_download_url, {
174
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
175
+ onStream: async (res) => {
176
+ const nodeStream = (await import('stream'))?.Readable.fromWeb(res);
177
+ await new Promise((resolve, reject) => {
178
+ nodeStream.pipe(fs.createWriteStream(downloadPath)).on('finish', resolve).on('error', reject);
179
+ });
180
+ }
152
181
  });
153
-
154
- const writer = fs.createWriteStream(downloadPath);
155
- await pipeline(response.data, writer);
156
-
157
- console.log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
158
-
182
+
183
+ log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
184
+
159
185
  // Try to make executable if it's a binary
160
186
  if (process.platform !== 'win32' && !fileName.includes('.')) {
161
187
  try {
162
188
  fs.chmodSync(downloadPath, '755');
163
- console.log(chalk.green(`✅ Made ${fileName} executable`));
189
+ log(chalk.green(`✅ Made ${fileName} executable`));
164
190
  } catch (error) {
165
- console.log(chalk.yellow(`⚠️ Could not make ${fileName} executable`));
191
+ log(chalk.yellow(`⚠️ Could not make ${fileName} executable`));
166
192
  }
167
193
  }
168
-
194
+
169
195
  // Provide installation instructions
170
196
  provideInstallationInstructions(downloadPath, asset);
171
-
197
+
172
198
  } catch (error) {
173
199
  console.error(chalk.red(`❌ Failed to download ${fileName}:`), error.message);
174
200
  }
@@ -178,44 +204,127 @@ async function downloadPackage(asset, targetDir) {
178
204
  function provideInstallationInstructions(filePath, asset) {
179
205
  const fileName = asset.name;
180
206
  const platform = getCurrentPlatform();
181
-
182
- console.log(chalk.cyan('\n📋 Installation Instructions:'));
183
-
207
+
184
208
  if (platform.platform === 'win32') {
185
209
  if (fileName.endsWith('.exe')) {
186
- console.log(chalk.white(' Run the executable:'));
187
- console.log(chalk.gray(` ${filePath}`));
210
+ log(chalk.white(' Run the executable:'));
211
+ log(chalk.gray(` ${filePath}`));
188
212
  } else if (fileName.endsWith('.msi')) {
189
- console.log(chalk.white(' Install the MSI package:'));
190
- console.log(chalk.gray(` msiexec /i "${filePath}"`));
213
+ log(chalk.white(' Install the MSI package:'));
214
+ log(chalk.gray(` msiexec /i "${filePath}"`));
191
215
  }
192
216
  } else if (platform.platform === 'darwin') {
193
217
  if (fileName.endsWith('.dmg')) {
194
- console.log(chalk.white(' Mount and install the DMG:'));
195
- console.log(chalk.gray(` open "${filePath}"`));
218
+ log(chalk.white(' Mount and install the DMG:'));
219
+ log(chalk.gray(` open "${filePath}"`));
196
220
  } else if (fileName.endsWith('.pkg')) {
197
- console.log(chalk.white(' Install the package:'));
198
- console.log(chalk.gray(` sudo installer -pkg "${filePath}" -target /`));
221
+ log(chalk.white(' Install the package:'));
222
+ log(chalk.gray(` sudo installer -pkg "${filePath}" -target /`));
199
223
  }
200
224
  } else {
201
225
  if (fileName.endsWith('.deb')) {
202
- console.log(chalk.white(' Install the DEB package:'));
203
- console.log(chalk.gray(` sudo dpkg -i "${filePath}"`));
226
+ log(chalk.white(' Install the DEB package:'));
227
+ log(chalk.gray(` sudo dpkg -i "${filePath}"`));
204
228
  } else if (fileName.endsWith('.rpm')) {
205
- console.log(chalk.white(' Install the RPM package:'));
206
- console.log(chalk.gray(` sudo rpm -i "${filePath}"`));
229
+ log(chalk.white(' Install the RPM package:'));
230
+ log(chalk.gray(` sudo rpm -i "${filePath}"`));
207
231
  } else if (fileName.endsWith('.AppImage')) {
208
- console.log(chalk.white(' Run the AppImage:'));
209
- console.log(chalk.gray(` chmod +x "${filePath}" && "${filePath}"`));
232
+ log(chalk.white(' Run the AppImage:'));
233
+ log(chalk.gray(` chmod +x "${filePath}" && "${filePath}"`));
210
234
  } else if (!fileName.includes('.')) {
211
- console.log(chalk.white(' Binary is ready to use:'));
212
- console.log(chalk.gray(` "${filePath}"`));
213
- console.log(chalk.white(' Consider moving to PATH:'));
214
- console.log(chalk.gray(` sudo mv "${filePath}" /usr/local/bin/`));
235
+ log(chalk.white(' Binary is ready to use:'));
236
+ log(chalk.gray(` "${filePath}"`));
237
+ log(chalk.white(' Consider moving to PATH:'));
238
+ log(chalk.gray(` sudo mv "${filePath}" /usr/local/bin/`));
215
239
  }
216
240
  }
217
241
  }
218
242
 
243
+ // Show package selection menu organized by platform
244
+ async function showPackageMenu(selectedRepo) {
245
+ const currentPlatform = getCurrentPlatform();
246
+ const releaseChoices = [];
247
+ const limitReleases = 2;
248
+
249
+ // Add section headers and organize by platform
250
+ selectedRepo.allReleases.slice(0, limitReleases).forEach(release => {
251
+ const platforms = ['windows', 'macos', 'linux', 'universal'];
252
+
253
+ platforms.forEach(platform => {
254
+ const assets = release.platformAssets[platform];
255
+ if (assets.length > 0) {
256
+ // Add platform header
257
+ const platformEmoji = {
258
+ windows: '🪟',
259
+ macos: '🍎',
260
+ linux: '🐧',
261
+ universal: '🌐'
262
+ };
263
+
264
+ const platformName = {
265
+ windows: 'Windows',
266
+ macos: 'macOS',
267
+ linux: 'Linux',
268
+ universal: 'Universal'
269
+ };
270
+
271
+ const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
272
+ const platformHeader = isCurrentPlatform
273
+ ? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
274
+ : chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
275
+
276
+ // Add separator if not first platform in this release
277
+ const needsSeparator = releaseChoices.length > 0 &&
278
+ !releaseChoices[releaseChoices.length - 1].name.includes('────');
279
+
280
+ if (needsSeparator) {
281
+ releaseChoices.push({
282
+ name: chalk.gray('────────────────────────────────'),
283
+ disabled: true
284
+ });
285
+ }
286
+
287
+ releaseChoices.push({
288
+ name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
289
+ disabled: true
290
+ });
291
+
292
+ // Add assets for this platform
293
+ assets.forEach(asset => {
294
+ const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
295
+ const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
296
+ ? chalk.cyan(`[${asset.detectedArch}]`)
297
+ : '';
298
+
299
+ const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
300
+
301
+ releaseChoices.push({
302
+ name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
303
+ value: { release, asset }
304
+ });
305
+ });
306
+ }
307
+ });
308
+ });
309
+
310
+ if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
311
+ log(chalk.yellow('No packages found for download.'));
312
+ return;
313
+ }
314
+
315
+ const { selectedPackage } = await inquirer.prompt({
316
+ type: 'list',
317
+ name: 'selectedPackage',
318
+ message: 'Select a package to download:',
319
+ choices: releaseChoices,
320
+ pageSize: 15
321
+ });
322
+
323
+ const downloadDir = path.resolve(process.cwd());
324
+ fs.mkdirSync(downloadDir, { recursive: true });
325
+ await downloadPackage(selectedPackage.asset, downloadDir);
326
+ }
327
+
219
328
  function getIdeCommand() {
220
329
  const ides = [
221
330
  { name: 'Cursor', cmd: 'cursor' },
@@ -244,7 +353,7 @@ function getIdeCommand() {
244
353
  export function openInIDE(targetDir) {
245
354
  const ide = getIdeCommand();
246
355
  if (!ide) {
247
- console.log(chalk.yellow('⚠️ No supported IDE found'));
356
+ log(chalk.yellow('⚠️ No supported IDE found'));
248
357
  return;
249
358
  }
250
359
 
@@ -259,17 +368,26 @@ export function openInIDE(targetDir) {
259
368
  shell: process.platform === 'win32'
260
369
  }).unref();
261
370
 
262
- const args2 = ide.cmd === 'code-server'
263
- ? ['./readme.md', '--open']
264
- : ['./README.md'];
265
-
266
- spawn(ide.cmd, args2, {
267
- detached: true,
268
- stdio: 'ignore',
269
- shell: process.platform === 'win32'
270
- }).unref();
371
+ // open readme after 3 seconds
372
+ setTimeout(() => {
271
373
 
272
- console.log(chalk.green(`🚀 Opening ${path.basename(targetDir)} in ${ide.name}`));
374
+ const readme = fs.existsSync('./readme.md') ? './readme.md' :
375
+ fs.existsSync('./Readme.md') ? './Readme.md' :
376
+ fs.existsSync('./README.md') ? './README.md' :
377
+ fs.existsSync('./package.json') ? './package.json' :
378
+ null;
379
+
380
+ if (readme)
381
+ spawn(ide.cmd, ide.cmd === 'code-server'
382
+ ? [readme, '--open']
383
+ : [readme], {
384
+ detached: true,
385
+ stdio: 'ignore',
386
+ shell: process.platform === 'win32'
387
+ }).unref();
388
+ }, 3000);
389
+
390
+ log(chalk.green(`🚀 Opening ${path.basename(targetDir)} in ${ide.name}`));
273
391
  } catch (error) {
274
392
  console.error(chalk.red(`❌ Failed to open ${ide.name}:`), error.message);
275
393
  }
@@ -329,14 +447,14 @@ export async function installDependencies(targetDir) {
329
447
  // Run detections and installations
330
448
  Object.entries(detectors).forEach(([name, check]) => {
331
449
  if (check()) {
332
- console.log(chalk.yellow(`⚙️ Detected ${name} project`));
450
+ // log(chalk.yellow(`⚙️ Detected ${name} project`));
333
451
  installers[name]?.();
334
452
  }
335
453
  });
336
454
  }
337
455
 
338
456
  function runCommand(cmd) {
339
- console.log(chalk.cyan(`🚀 Running: ${cmd}`));
457
+ log(chalk.cyan(`🚀 Running: ${cmd}`));
340
458
  try {
341
459
  execSync(cmd, { stdio: 'inherit' });
342
460
  } catch (error) {
@@ -346,24 +464,26 @@ function runCommand(cmd) {
346
464
 
347
465
  export async function searchRepositories(query) {
348
466
  try {
349
- const response = await axios.get(GITHUB_API, {
350
- params: {
351
- q: `${query} in:name`,
352
- sort: 'stars',
353
- order: 'desc',
354
- per_page: RESULTS_PER_PAGE
355
- },
467
+ const response = await grab(GITHUB_API, {
468
+ q: `${query} in:name`,
469
+ sort: 'stars',
470
+ order: 'desc',
471
+ per_page: RESULTS_PER_PAGE,
472
+ debug: false,
356
473
  headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
357
474
  });
358
-
475
+
476
+ if (response.error || !response.items)
477
+ return log("No response")
478
+
359
479
  // Check for releases for each repository
360
480
  const reposWithReleases = await Promise.all(
361
- response.data.items.map(async (repo) => {
481
+ response.items.map(async (repo) => {
362
482
  const releases = await getRepositoryReleases(repo.owner.login, repo.name);
363
483
  const currentPlatform = getCurrentPlatform();
364
484
  const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
365
485
  const categorizedReleases = categorizeReleasesByPlatform(releases);
366
-
486
+
367
487
  return {
368
488
  ...repo,
369
489
  hasReleases: releases.length > 0,
@@ -373,49 +493,50 @@ export async function searchRepositories(query) {
373
493
  };
374
494
  })
375
495
  );
376
-
496
+
377
497
  return reposWithReleases;
378
498
  } catch (error) {
379
- console.error(chalk.red('Search failed:'), error.response?.data?.message || error.message);
499
+ console.error(chalk.red('Search failed:'), error.message);
380
500
  process.exit(1);
381
501
  }
382
502
  }
383
503
 
384
504
  export async function downloadRepo(repo) {
385
- const parsed = gitUrlParse(`https://github.com/${repo}`);
505
+ const parsed = gitUrlParse(repo);
386
506
  const defaultDir = path.resolve(process.cwd(), parsed.name);
387
507
  const extractPath = getAvailableDirectoryName(defaultDir);
388
508
 
509
+ // if it picks up a larger owner name, slice to the last part
510
+ if (parsed.owner.includes('/'))
511
+ parsed.owner = parsed.owner.split('/').slice(-1).join('');
512
+
389
513
  fs.mkdirSync(extractPath, { recursive: true });
390
- console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${path.basename(extractPath)}...`));
391
- let url = `https://api.github.com/repos/${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
514
+ log(chalk.blue(`📦 Downloading ${parsed.name} into ${path.basename(extractPath)}...`));
515
+ printLogo()
516
+
517
+ let url = `https://api.github.com/repos/${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'master'}`;
392
518
 
393
519
  try {
394
- let response;
395
- try {
396
- response = await axios({
397
- url,
398
- method: 'GET',
399
- responseType: 'stream',
400
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
401
- });
402
- } catch (error) {
403
- url = url.replace("/main", "/master");
404
- response = await axios({
405
- url,
406
- method: 'GET',
407
- responseType: 'stream',
408
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
409
- });
520
+
521
+ var params = {
522
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
523
+ onStream: async (res) => {
524
+
525
+ const nodeStream = (await import('stream'))?.Readable.fromWeb(res);
526
+ await new Promise((resolve, reject) => {
527
+ nodeStream.pipe(tar.x({
528
+ C: extractPath,
529
+ strip: 1
530
+ })).on('finish', resolve).on('error', reject);
531
+ });
532
+ }
410
533
  }
411
534
 
412
- await pipeline(
413
- response.data,
414
- tar.x({
415
- C: extractPath,
416
- strip: 1
417
- })
418
- );
535
+ var response = await grab(url, params);
536
+
537
+ if (response.error)
538
+ response = await grab(url.replace("/master", "/main"), params);
539
+
419
540
 
420
541
  setTimeout(() => {
421
542
  openInIDE(extractPath);
@@ -427,89 +548,7 @@ export async function downloadRepo(repo) {
427
548
  }
428
549
  }
429
550
 
430
- // Show package selection menu organized by platform
431
- async function showPackageMenu(selectedRepo) {
432
- const currentPlatform = getCurrentPlatform();
433
- const releaseChoices = [];
434
-
435
- // Add section headers and organize by platform
436
- selectedRepo.allReleases.forEach(release => {
437
- const platforms = ['windows', 'macos', 'linux', 'universal'];
438
-
439
- platforms.forEach(platform => {
440
- const assets = release.platformAssets[platform];
441
- if (assets.length > 0) {
442
- // Add platform header
443
- const platformEmoji = {
444
- windows: '🪟',
445
- macos: '🍎',
446
- linux: '🐧',
447
- universal: '🌐'
448
- };
449
-
450
- const platformName = {
451
- windows: 'Windows',
452
- macos: 'macOS',
453
- linux: 'Linux',
454
- universal: 'Universal'
455
- };
456
-
457
- const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
458
- const platformHeader = isCurrentPlatform
459
- ? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
460
- : chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
461
-
462
- // Add separator if not first platform in this release
463
- const needsSeparator = releaseChoices.length > 0 &&
464
- !releaseChoices[releaseChoices.length - 1].name.includes('────');
465
-
466
- if (needsSeparator) {
467
- releaseChoices.push({
468
- name: chalk.gray('────────────────────────────────'),
469
- disabled: true
470
- });
471
- }
472
-
473
- releaseChoices.push({
474
- name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
475
- disabled: true
476
- });
477
-
478
- // Add assets for this platform
479
- assets.forEach(asset => {
480
- const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
481
- const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
482
- ? chalk.cyan(`[${asset.detectedArch}]`)
483
- : '';
484
-
485
- const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
486
-
487
- releaseChoices.push({
488
- name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
489
- value: { release, asset }
490
- });
491
- });
492
- }
493
- });
494
- });
495
-
496
- if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
497
- console.log(chalk.yellow('No packages found for download.'));
498
- return;
499
- }
500
-
501
- const { selectedPackage } = await inquirer.prompt({
502
- type: 'list',
503
- name: 'selectedPackage',
504
- message: 'Select a package to download:',
505
- choices: releaseChoices,
506
- pageSize: 15
507
- });
508
-
509
- const downloadDir = path.resolve(process.cwd(), 'downloads');
510
- fs.mkdirSync(downloadDir, { recursive: true });
511
- await downloadPackage(selectedPackage.asset, downloadDir);
512
- }
551
+ function getAvailableDirectoryName(basePath) {
513
552
  if (!fs.existsSync(basePath)) return basePath;
514
553
  let counter = 2;
515
554
  let newPath;
@@ -521,16 +560,16 @@ async function showPackageMenu(selectedRepo) {
521
560
  }
522
561
 
523
562
  async function main() {
563
+ printLogo()
524
564
  const args = process.argv.slice(2);
525
565
  if (!args.length) {
526
- console.log(chalk.yellow('Usage: gg <search-query>'));
566
+ log(chalk.yellow('Usage: gg <search-query>'));
527
567
  process.exit(1);
528
568
  }
529
569
 
530
570
  const query = args.join(' ');
531
571
  const currentPlatform = getCurrentPlatform();
532
-
533
- console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
572
+
534
573
 
535
574
  let repoUrl = null;
536
575
 
@@ -551,22 +590,23 @@ async function main() {
551
590
 
552
591
  const results = await searchRepositories(query);
553
592
 
554
- if (!results.length) {
555
- console.log(chalk.yellow('No repositories found'));
593
+ if (!results || !results.length) {
594
+ log(chalk.yellow('No repositories found'));
556
595
  return;
557
596
  }
558
597
 
598
+
559
599
  const { selectedRepo } = await inquirer.prompt({
560
600
  type: 'list',
561
601
  name: 'selectedRepo',
562
602
  message: 'Select a repository to download:',
563
603
  choices: results.map(repo => {
564
- const packageInfo = repo.hasCompatibleReleases
565
- ? chalk.green(' 📦 Packages available')
566
- : repo.hasReleases
567
- ? chalk.yellow(' 📦 Packages (other platforms)')
604
+ const packageInfo = repo.hasCompatibleReleases
605
+ ? chalk.green(' 📦 Packages available')
606
+ : repo.hasReleases
607
+ ? chalk.yellow(' 📦 Packages (other platforms)')
568
608
  : '';
569
-
609
+
570
610
  return {
571
611
  name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
572
612
  ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
@@ -576,7 +616,7 @@ async function main() {
576
616
  });
577
617
 
578
618
  // If the selected repo has any releases, show download options
579
- if (selectedRepo.hasReleases) {
619
+ if (selectedRepo.hasReleases || selectedRepo.hasCompatibleReleases) {
580
620
  const { downloadChoice } = await inquirer.prompt({
581
621
  type: 'list',
582
622
  name: 'downloadChoice',
@@ -593,11 +633,11 @@ async function main() {
593
633
  }
594
634
 
595
635
  if (downloadChoice === 'source' || downloadChoice === 'both') {
596
- await downloadRepo(selectedRepo.html_url);
636
+ await downloadRepo(selectedRepo.url);
597
637
  }
598
638
  } else {
599
639
  // No packages, just download source
600
- await downloadRepo(selectedRepo.html_url);
640
+ await downloadRepo(selectedRepo.url);
601
641
  }
602
642
  }
603
643
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git0",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "A CLI manager for downloading GitHub repos.",
5
5
  "author": "vtempest",
6
6
  "license": "MIT",
@@ -10,11 +10,13 @@
10
10
  "download"
11
11
  ],
12
12
  "dependencies": {
13
- "axios": "^1.7.2",
14
- "chalk": "^5.3.0",
13
+ "axios": "^1.9.0",
14
+ "chalk": "^5.4.1",
15
15
  "git-url-parse": "^16.1.0",
16
- "inquirer": "^9.2.16",
17
- "tar": "^6.2.1"
16
+ "grab-api.js": "^0.9.120",
17
+ "inquirer": "^12.6.3",
18
+ "streamable": "^0.6.0",
19
+ "tar": "^7.4.3"
18
20
  },
19
21
  "scripts": {
20
22
  "demo": "bun gg.js react template",
package/readme.md CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  # Git0: GitHub Zero-Step Repo Downloader
30
30
 
31
- A fast and smart CLI tool to search, download, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
31
+ A fast and smart CLI tool to search, download source & releases for your system, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
32
32
 
33
33
 
34
34
  ## 🚀 Installation
@@ -41,12 +41,15 @@ npm install -g git0
41
41
  bun install -g git0
42
42
  ```
43
43
 
44
+ ![livepreview](https://i.imgur.com/Io3ukRC.gif)
44
45
  ![preview](https://i.imgur.com/K22NiBq.png)
45
46
 
47
+
46
48
  ## ✨ Features
47
49
 
48
50
  - **Search GitHub repositories** by name with fuzzy matching
49
51
  - **Download repositories** directly from GitHub URLs or owner/repo shortcuts
52
+ - **Get Releases** instantly download latest release for your system or all systems
50
53
  - **Automatic dependency detection** and installation for multiple project types
51
54
  - **Smart IDE integration** - automatically opens projects in your preferred editor
52
55
  - **Cross-platform support** - works on Windows, macOS, and Linux
@@ -98,7 +101,7 @@ GG automatically detects and opens projects in your preferred IDE:
98
101
 
99
102
  ### GitHub Token (Optional)
100
103
 
101
- For higher API rate limits, set your GitHub token:
104
+ For higher API rate limits, set [your GitHub token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token):
102
105
 
103
106
  ```bash
104
107
  export GITHUB_TOKEN=your_github_token_here