git0 0.1.6 → 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 +179 -141
  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,40 +204,38 @@ 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
  }
@@ -220,59 +244,60 @@ function provideInstallationInstructions(filePath, asset) {
220
244
  async function showPackageMenu(selectedRepo) {
221
245
  const currentPlatform = getCurrentPlatform();
222
246
  const releaseChoices = [];
223
-
247
+ const limitReleases = 2;
248
+
224
249
  // Add section headers and organize by platform
225
- selectedRepo.allReleases.forEach(release => {
250
+ selectedRepo.allReleases.slice(0, limitReleases).forEach(release => {
226
251
  const platforms = ['windows', 'macos', 'linux', 'universal'];
227
-
252
+
228
253
  platforms.forEach(platform => {
229
254
  const assets = release.platformAssets[platform];
230
255
  if (assets.length > 0) {
231
256
  // Add platform header
232
257
  const platformEmoji = {
233
258
  windows: '🪟',
234
- macos: '🍎',
259
+ macos: '🍎',
235
260
  linux: '🐧',
236
261
  universal: '🌐'
237
262
  };
238
-
263
+
239
264
  const platformName = {
240
265
  windows: 'Windows',
241
266
  macos: 'macOS',
242
267
  linux: 'Linux',
243
268
  universal: 'Universal'
244
269
  };
245
-
270
+
246
271
  const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
247
- const platformHeader = isCurrentPlatform
272
+ const platformHeader = isCurrentPlatform
248
273
  ? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
249
274
  : chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
250
-
275
+
251
276
  // Add separator if not first platform in this release
252
- const needsSeparator = releaseChoices.length > 0 &&
277
+ const needsSeparator = releaseChoices.length > 0 &&
253
278
  !releaseChoices[releaseChoices.length - 1].name.includes('────');
254
-
279
+
255
280
  if (needsSeparator) {
256
281
  releaseChoices.push({
257
282
  name: chalk.gray('────────────────────────────────'),
258
283
  disabled: true
259
284
  });
260
285
  }
261
-
286
+
262
287
  releaseChoices.push({
263
288
  name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
264
289
  disabled: true
265
290
  });
266
-
291
+
267
292
  // Add assets for this platform
268
293
  assets.forEach(asset => {
269
294
  const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
270
- const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
271
- ? chalk.cyan(`[${asset.detectedArch}]`)
295
+ const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
296
+ ? chalk.cyan(`[${asset.detectedArch}]`)
272
297
  : '';
273
-
298
+
274
299
  const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
275
-
300
+
276
301
  releaseChoices.push({
277
302
  name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
278
303
  value: { release, asset }
@@ -283,7 +308,7 @@ async function showPackageMenu(selectedRepo) {
283
308
  });
284
309
 
285
310
  if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
286
- console.log(chalk.yellow('No packages found for download.'));
311
+ log(chalk.yellow('No packages found for download.'));
287
312
  return;
288
313
  }
289
314
 
@@ -295,7 +320,7 @@ async function showPackageMenu(selectedRepo) {
295
320
  pageSize: 15
296
321
  });
297
322
 
298
- const downloadDir = path.resolve(process.cwd(), 'downloads');
323
+ const downloadDir = path.resolve(process.cwd());
299
324
  fs.mkdirSync(downloadDir, { recursive: true });
300
325
  await downloadPackage(selectedPackage.asset, downloadDir);
301
326
  }
@@ -328,7 +353,7 @@ function getIdeCommand() {
328
353
  export function openInIDE(targetDir) {
329
354
  const ide = getIdeCommand();
330
355
  if (!ide) {
331
- console.log(chalk.yellow('⚠️ No supported IDE found'));
356
+ log(chalk.yellow('⚠️ No supported IDE found'));
332
357
  return;
333
358
  }
334
359
 
@@ -343,17 +368,26 @@ export function openInIDE(targetDir) {
343
368
  shell: process.platform === 'win32'
344
369
  }).unref();
345
370
 
346
- const args2 = ide.cmd === 'code-server'
347
- ? ['./readme.md', '--open']
348
- : ['./README.md'];
349
-
350
- spawn(ide.cmd, args2, {
351
- detached: true,
352
- stdio: 'ignore',
353
- shell: process.platform === 'win32'
354
- }).unref();
371
+ // open readme after 3 seconds
372
+ setTimeout(() => {
355
373
 
356
- 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}`));
357
391
  } catch (error) {
358
392
  console.error(chalk.red(`❌ Failed to open ${ide.name}:`), error.message);
359
393
  }
@@ -413,14 +447,14 @@ export async function installDependencies(targetDir) {
413
447
  // Run detections and installations
414
448
  Object.entries(detectors).forEach(([name, check]) => {
415
449
  if (check()) {
416
- console.log(chalk.yellow(`⚙️ Detected ${name} project`));
450
+ // log(chalk.yellow(`⚙️ Detected ${name} project`));
417
451
  installers[name]?.();
418
452
  }
419
453
  });
420
454
  }
421
455
 
422
456
  function runCommand(cmd) {
423
- console.log(chalk.cyan(`🚀 Running: ${cmd}`));
457
+ log(chalk.cyan(`🚀 Running: ${cmd}`));
424
458
  try {
425
459
  execSync(cmd, { stdio: 'inherit' });
426
460
  } catch (error) {
@@ -430,24 +464,26 @@ function runCommand(cmd) {
430
464
 
431
465
  export async function searchRepositories(query) {
432
466
  try {
433
- const response = await axios.get(GITHUB_API, {
434
- params: {
435
- q: `${query} in:name`,
436
- sort: 'stars',
437
- order: 'desc',
438
- per_page: RESULTS_PER_PAGE
439
- },
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,
440
473
  headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
441
474
  });
442
-
475
+
476
+ if (response.error || !response.items)
477
+ return log("No response")
478
+
443
479
  // Check for releases for each repository
444
480
  const reposWithReleases = await Promise.all(
445
- response.data.items.map(async (repo) => {
481
+ response.items.map(async (repo) => {
446
482
  const releases = await getRepositoryReleases(repo.owner.login, repo.name);
447
483
  const currentPlatform = getCurrentPlatform();
448
484
  const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
449
485
  const categorizedReleases = categorizeReleasesByPlatform(releases);
450
-
486
+
451
487
  return {
452
488
  ...repo,
453
489
  hasReleases: releases.length > 0,
@@ -457,49 +493,50 @@ export async function searchRepositories(query) {
457
493
  };
458
494
  })
459
495
  );
460
-
496
+
461
497
  return reposWithReleases;
462
498
  } catch (error) {
463
- console.error(chalk.red('Search failed:'), error.response?.data?.message || error.message);
499
+ console.error(chalk.red('Search failed:'), error.message);
464
500
  process.exit(1);
465
501
  }
466
502
  }
467
503
 
468
504
  export async function downloadRepo(repo) {
469
- const parsed = gitUrlParse(`https://github.com/${repo}`);
505
+ const parsed = gitUrlParse(repo);
470
506
  const defaultDir = path.resolve(process.cwd(), parsed.name);
471
507
  const extractPath = getAvailableDirectoryName(defaultDir);
472
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
+
473
513
  fs.mkdirSync(extractPath, { recursive: true });
474
- console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${path.basename(extractPath)}...`));
475
- 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'}`;
476
518
 
477
519
  try {
478
- let response;
479
- try {
480
- response = await axios({
481
- url,
482
- method: 'GET',
483
- responseType: 'stream',
484
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
485
- });
486
- } catch (error) {
487
- url = url.replace("/main", "/master");
488
- response = await axios({
489
- url,
490
- method: 'GET',
491
- responseType: 'stream',
492
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
493
- });
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
+ }
494
533
  }
495
534
 
496
- await pipeline(
497
- response.data,
498
- tar.x({
499
- C: extractPath,
500
- strip: 1
501
- })
502
- );
535
+ var response = await grab(url, params);
536
+
537
+ if (response.error)
538
+ response = await grab(url.replace("/master", "/main"), params);
539
+
503
540
 
504
541
  setTimeout(() => {
505
542
  openInIDE(extractPath);
@@ -523,16 +560,16 @@ function getAvailableDirectoryName(basePath) {
523
560
  }
524
561
 
525
562
  async function main() {
563
+ printLogo()
526
564
  const args = process.argv.slice(2);
527
565
  if (!args.length) {
528
- console.log(chalk.yellow('Usage: gg <search-query>'));
566
+ log(chalk.yellow('Usage: gg <search-query>'));
529
567
  process.exit(1);
530
568
  }
531
569
 
532
570
  const query = args.join(' ');
533
571
  const currentPlatform = getCurrentPlatform();
534
-
535
- console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
572
+
536
573
 
537
574
  let repoUrl = null;
538
575
 
@@ -553,22 +590,23 @@ async function main() {
553
590
 
554
591
  const results = await searchRepositories(query);
555
592
 
556
- if (!results.length) {
557
- console.log(chalk.yellow('No repositories found'));
593
+ if (!results || !results.length) {
594
+ log(chalk.yellow('No repositories found'));
558
595
  return;
559
596
  }
560
597
 
598
+
561
599
  const { selectedRepo } = await inquirer.prompt({
562
600
  type: 'list',
563
601
  name: 'selectedRepo',
564
602
  message: 'Select a repository to download:',
565
603
  choices: results.map(repo => {
566
- const packageInfo = repo.hasCompatibleReleases
567
- ? chalk.green(' 📦 Packages available')
568
- : repo.hasReleases
569
- ? chalk.yellow(' 📦 Packages (other platforms)')
604
+ const packageInfo = repo.hasCompatibleReleases
605
+ ? chalk.green(' 📦 Packages available')
606
+ : repo.hasReleases
607
+ ? chalk.yellow(' 📦 Packages (other platforms)')
570
608
  : '';
571
-
609
+
572
610
  return {
573
611
  name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
574
612
  ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
@@ -578,7 +616,7 @@ async function main() {
578
616
  });
579
617
 
580
618
  // If the selected repo has any releases, show download options
581
- if (selectedRepo.hasReleases) {
619
+ if (selectedRepo.hasReleases || selectedRepo.hasCompatibleReleases) {
582
620
  const { downloadChoice } = await inquirer.prompt({
583
621
  type: 'list',
584
622
  name: 'downloadChoice',
@@ -595,11 +633,11 @@ async function main() {
595
633
  }
596
634
 
597
635
  if (downloadChoice === 'source' || downloadChoice === 'both') {
598
- await downloadRepo(selectedRepo.html_url);
636
+ await downloadRepo(selectedRepo.url);
599
637
  }
600
638
  } else {
601
639
  // No packages, just download source
602
- await downloadRepo(selectedRepo.html_url);
640
+ await downloadRepo(selectedRepo.url);
603
641
  }
604
642
  }
605
643
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git0",
3
- "version": "0.1.6",
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