git0 0.1.4 → 0.1.6

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 +360 -30
  2. package/package.json +3 -2
  3. package/readme.md +7 -6
package/gg.js CHANGED
@@ -9,12 +9,297 @@ import { pipeline } from 'stream/promises';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
11
  import gitUrlParse from 'git-url-parse';
12
-
12
+ import os from 'os';
13
13
 
14
14
  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
+ // Detect current operating system and architecture
19
+ function getCurrentPlatform() {
20
+ const platform = os.platform();
21
+ const arch = os.arch();
22
+
23
+ const platformMap = {
24
+ 'win32': 'windows',
25
+ 'darwin': 'macos',
26
+ 'linux': 'linux'
27
+ };
28
+
29
+ const archMap = {
30
+ 'x64': 'x86_64',
31
+ 'arm64': 'arm64',
32
+ 'arm': 'arm',
33
+ 'ia32': 'i386'
34
+ };
35
+
36
+ return {
37
+ os: platformMap[platform] || platform,
38
+ arch: archMap[arch] || arch,
39
+ platform,
40
+ architecture: arch
41
+ };
42
+ }
43
+
44
+ // Get releases for a repository
45
+ async function getRepositoryReleases(owner, repo) {
46
+ try {
47
+ const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/releases`, {
48
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
49
+ params: { per_page: 5 } // Get latest 5 releases
50
+ });
51
+ return response.data;
52
+ } catch (error) {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ // Categorize releases by platform
58
+ function categorizeReleasesByPlatform(releases) {
59
+ const platformKeywords = {
60
+ windows: ['win', 'windows', 'win32', 'win64', '.exe', '.msi'],
61
+ macos: ['mac', 'macos', 'darwin', 'osx', '.dmg', '.pkg'],
62
+ linux: ['linux', 'ubuntu', 'debian', '.deb', '.rpm', '.tar.gz', '.AppImage']
63
+ };
64
+
65
+ const archKeywords = {
66
+ x86_64: ['x86_64', 'x64', 'amd64', '64'],
67
+ arm64: ['arm64', 'aarch64'],
68
+ arm: ['arm', 'armv7'],
69
+ i386: ['i386', 'x86', '32']
70
+ };
71
+
72
+ const categorizedReleases = [];
73
+
74
+ releases.forEach(release => {
75
+ const platformAssets = {
76
+ windows: [],
77
+ macos: [],
78
+ linux: [],
79
+ universal: []
80
+ };
81
+
82
+ release.assets.forEach(asset => {
83
+ const name = asset.name.toLowerCase();
84
+ let categorized = false;
85
+
86
+ // Check each platform
87
+ Object.entries(platformKeywords).forEach(([platform, keywords]) => {
88
+ if (keywords.some(keyword => name.includes(keyword.toLowerCase()))) {
89
+ // Determine architecture
90
+ let detectedArch = 'unknown';
91
+ Object.entries(archKeywords).forEach(([arch, archKeys]) => {
92
+ if (archKeys.some(archKey => name.includes(archKey.toLowerCase()))) {
93
+ detectedArch = arch;
94
+ }
95
+ });
96
+
97
+ platformAssets[platform].push({
98
+ ...asset,
99
+ detectedArch,
100
+ platform
101
+ });
102
+ categorized = true;
103
+ }
104
+ });
105
+
106
+ // 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')))) {
109
+ platformAssets.universal.push({
110
+ ...asset,
111
+ detectedArch: 'universal',
112
+ platform: 'universal'
113
+ });
114
+ }
115
+ });
116
+
117
+ // Only include releases that have assets
118
+ const hasAssets = Object.values(platformAssets).some(assets => assets.length > 0);
119
+ if (hasAssets) {
120
+ categorizedReleases.push({
121
+ ...release,
122
+ platformAssets
123
+ });
124
+ }
125
+ });
126
+
127
+ return categorizedReleases;
128
+ }
129
+
130
+ // Filter releases for current platform (for compatibility indicator)
131
+ function filterReleasesByPlatform(releases, currentPlatform) {
132
+ const categorized = categorizeReleasesByPlatform(releases);
133
+ return categorized.filter(release =>
134
+ release.platformAssets[currentPlatform.os].length > 0 ||
135
+ release.platformAssets.universal.length > 0
136
+ );
137
+ }
138
+
139
+ // Download and install package
140
+ async function downloadPackage(asset, targetDir) {
141
+ const fileName = asset.name;
142
+ const downloadPath = path.join(targetDir, fileName);
143
+
144
+ console.log(chalk.blue(`📦 Downloading ${fileName}...`));
145
+
146
+ try {
147
+ const response = await axios({
148
+ url: asset.browser_download_url,
149
+ method: 'GET',
150
+ responseType: 'stream',
151
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
152
+ });
153
+
154
+ const writer = fs.createWriteStream(downloadPath);
155
+ await pipeline(response.data, writer);
156
+
157
+ console.log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
158
+
159
+ // Try to make executable if it's a binary
160
+ if (process.platform !== 'win32' && !fileName.includes('.')) {
161
+ try {
162
+ fs.chmodSync(downloadPath, '755');
163
+ console.log(chalk.green(`✅ Made ${fileName} executable`));
164
+ } catch (error) {
165
+ console.log(chalk.yellow(`⚠️ Could not make ${fileName} executable`));
166
+ }
167
+ }
168
+
169
+ // Provide installation instructions
170
+ provideInstallationInstructions(downloadPath, asset);
171
+
172
+ } catch (error) {
173
+ console.error(chalk.red(`❌ Failed to download ${fileName}:`), error.message);
174
+ }
175
+ }
176
+
177
+ // Provide platform-specific installation instructions
178
+ function provideInstallationInstructions(filePath, asset) {
179
+ const fileName = asset.name;
180
+ const platform = getCurrentPlatform();
181
+
182
+ console.log(chalk.cyan('\n📋 Installation Instructions:'));
183
+
184
+ if (platform.platform === 'win32') {
185
+ if (fileName.endsWith('.exe')) {
186
+ console.log(chalk.white(' Run the executable:'));
187
+ console.log(chalk.gray(` ${filePath}`));
188
+ } else if (fileName.endsWith('.msi')) {
189
+ console.log(chalk.white(' Install the MSI package:'));
190
+ console.log(chalk.gray(` msiexec /i "${filePath}"`));
191
+ }
192
+ } else if (platform.platform === 'darwin') {
193
+ if (fileName.endsWith('.dmg')) {
194
+ console.log(chalk.white(' Mount and install the DMG:'));
195
+ console.log(chalk.gray(` open "${filePath}"`));
196
+ } else if (fileName.endsWith('.pkg')) {
197
+ console.log(chalk.white(' Install the package:'));
198
+ console.log(chalk.gray(` sudo installer -pkg "${filePath}" -target /`));
199
+ }
200
+ } else {
201
+ if (fileName.endsWith('.deb')) {
202
+ console.log(chalk.white(' Install the DEB package:'));
203
+ console.log(chalk.gray(` sudo dpkg -i "${filePath}"`));
204
+ } else if (fileName.endsWith('.rpm')) {
205
+ console.log(chalk.white(' Install the RPM package:'));
206
+ console.log(chalk.gray(` sudo rpm -i "${filePath}"`));
207
+ } else if (fileName.endsWith('.AppImage')) {
208
+ console.log(chalk.white(' Run the AppImage:'));
209
+ console.log(chalk.gray(` chmod +x "${filePath}" && "${filePath}"`));
210
+ } 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/`));
215
+ }
216
+ }
217
+ }
218
+
219
+ // Show package selection menu organized by platform
220
+ async function showPackageMenu(selectedRepo) {
221
+ const currentPlatform = getCurrentPlatform();
222
+ const releaseChoices = [];
223
+
224
+ // Add section headers and organize by platform
225
+ selectedRepo.allReleases.forEach(release => {
226
+ const platforms = ['windows', 'macos', 'linux', 'universal'];
227
+
228
+ platforms.forEach(platform => {
229
+ const assets = release.platformAssets[platform];
230
+ if (assets.length > 0) {
231
+ // Add platform header
232
+ const platformEmoji = {
233
+ windows: '🪟',
234
+ macos: '🍎',
235
+ linux: '🐧',
236
+ universal: '🌐'
237
+ };
238
+
239
+ const platformName = {
240
+ windows: 'Windows',
241
+ macos: 'macOS',
242
+ linux: 'Linux',
243
+ universal: 'Universal'
244
+ };
245
+
246
+ const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
247
+ const platformHeader = isCurrentPlatform
248
+ ? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
249
+ : chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
250
+
251
+ // Add separator if not first platform in this release
252
+ const needsSeparator = releaseChoices.length > 0 &&
253
+ !releaseChoices[releaseChoices.length - 1].name.includes('────');
254
+
255
+ if (needsSeparator) {
256
+ releaseChoices.push({
257
+ name: chalk.gray('────────────────────────────────'),
258
+ disabled: true
259
+ });
260
+ }
261
+
262
+ releaseChoices.push({
263
+ name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
264
+ disabled: true
265
+ });
266
+
267
+ // Add assets for this platform
268
+ assets.forEach(asset => {
269
+ const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
270
+ const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
271
+ ? chalk.cyan(`[${asset.detectedArch}]`)
272
+ : '';
273
+
274
+ const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
275
+
276
+ releaseChoices.push({
277
+ name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
278
+ value: { release, asset }
279
+ });
280
+ });
281
+ }
282
+ });
283
+ });
284
+
285
+ if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
286
+ console.log(chalk.yellow('No packages found for download.'));
287
+ return;
288
+ }
289
+
290
+ const { selectedPackage } = await inquirer.prompt({
291
+ type: 'list',
292
+ name: 'selectedPackage',
293
+ message: 'Select a package to download:',
294
+ choices: releaseChoices,
295
+ pageSize: 15
296
+ });
297
+
298
+ const downloadDir = path.resolve(process.cwd(), 'downloads');
299
+ fs.mkdirSync(downloadDir, { recursive: true });
300
+ await downloadPackage(selectedPackage.asset, downloadDir);
301
+ }
302
+
18
303
  function getIdeCommand() {
19
304
  const ides = [
20
305
  { name: 'Cursor', cmd: 'cursor' },
@@ -58,7 +343,6 @@ export function openInIDE(targetDir) {
58
343
  shell: process.platform === 'win32'
59
344
  }).unref();
60
345
 
61
-
62
346
  const args2 = ide.cmd === 'code-server'
63
347
  ? ['./readme.md', '--open']
64
348
  : ['./README.md'];
@@ -89,7 +373,6 @@ export async function installDependencies(targetDir) {
89
373
 
90
374
  // Install commands for each project type
91
375
  const installers = {
92
-
93
376
  nodejs: () => {
94
377
  try {
95
378
  execSync(
@@ -144,6 +427,7 @@ function runCommand(cmd) {
144
427
  console.error(chalk.red(`❌ Failed: ${cmd}`));
145
428
  }
146
429
  }
430
+
147
431
  export async function searchRepositories(query) {
148
432
  try {
149
433
  const response = await axios.get(GITHUB_API, {
@@ -155,12 +439,32 @@ export async function searchRepositories(query) {
155
439
  },
156
440
  headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
157
441
  });
158
- return response.data.items;
442
+
443
+ // Check for releases for each repository
444
+ const reposWithReleases = await Promise.all(
445
+ response.data.items.map(async (repo) => {
446
+ const releases = await getRepositoryReleases(repo.owner.login, repo.name);
447
+ const currentPlatform = getCurrentPlatform();
448
+ const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
449
+ const categorizedReleases = categorizeReleasesByPlatform(releases);
450
+
451
+ return {
452
+ ...repo,
453
+ hasReleases: releases.length > 0,
454
+ hasCompatibleReleases: compatibleReleases.length > 0,
455
+ releases: compatibleReleases,
456
+ allReleases: categorizedReleases
457
+ };
458
+ })
459
+ );
460
+
461
+ return reposWithReleases;
159
462
  } catch (error) {
160
463
  console.error(chalk.red('Search failed:'), error.response?.data?.message || error.message);
161
464
  process.exit(1);
162
465
  }
163
466
  }
467
+
164
468
  export async function downloadRepo(repo) {
165
469
  const parsed = gitUrlParse(`https://github.com/${repo}`);
166
470
  const defaultDir = path.resolve(process.cwd(), parsed.name);
@@ -168,24 +472,20 @@ export async function downloadRepo(repo) {
168
472
 
169
473
  fs.mkdirSync(extractPath, { recursive: true });
170
474
  console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${path.basename(extractPath)}...`));
171
- let url = `${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
172
-
173
- fs.mkdirSync(extractPath, { recursive: true });
174
- console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${repo.name}...`));
475
+ let url = `https://api.github.com/repos/${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
175
476
 
176
- console.log(url);
177
477
  try {
178
478
  let response;
179
- try{
180
- response= await axios({
181
- url,
182
- method: 'GET',
183
- responseType: 'stream',
184
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
185
- });
479
+ try {
480
+ response = await axios({
481
+ url,
482
+ method: 'GET',
483
+ responseType: 'stream',
484
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
485
+ });
186
486
  } catch (error) {
187
- url = url.replace("/main", "/master")
188
- response= await axios({
487
+ url = url.replace("/main", "/master");
488
+ response = await axios({
189
489
  url,
190
490
  method: 'GET',
191
491
  responseType: 'stream',
@@ -204,7 +504,7 @@ export async function downloadRepo(repo) {
204
504
  setTimeout(() => {
205
505
  openInIDE(extractPath);
206
506
  }, 1000);
207
- installDependencies(extractPath); // Add this line
507
+ installDependencies(extractPath);
208
508
  } catch (error) {
209
509
  console.error(chalk.red('Download failed:'), error.message);
210
510
  process.exit(1);
@@ -230,6 +530,9 @@ async function main() {
230
530
  }
231
531
 
232
532
  const query = args.join(' ');
533
+ const currentPlatform = getCurrentPlatform();
534
+
535
+ console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
233
536
 
234
537
  let repoUrl = null;
235
538
 
@@ -243,11 +546,7 @@ async function main() {
243
546
  parsed = gitUrlParse(`https://github.com/${query}`);
244
547
  }
245
548
 
246
-
247
549
  if (parsed && parsed.owner && parsed.name) {
248
- // Reconstruct the canonical HTTPS URL for download
249
- // repoUrl = `https://github.com/${parsed.owner}/${parsed.name}`;
250
- // console.log(chalk.green(`Detected GitHub repo: ${repoUrl}`));
251
550
  await downloadRepo(parsed.href);
252
551
  return;
253
552
  }
@@ -263,14 +562,45 @@ async function main() {
263
562
  type: 'list',
264
563
  name: 'selectedRepo',
265
564
  message: 'Select a repository to download:',
266
- choices: results.map(repo => ({
267
- name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
268
- ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}`,
269
- value: repo
270
- }))
565
+ choices: results.map(repo => {
566
+ const packageInfo = repo.hasCompatibleReleases
567
+ ? chalk.green(' 📦 Packages available')
568
+ : repo.hasReleases
569
+ ? chalk.yellow(' 📦 Packages (other platforms)')
570
+ : '';
571
+
572
+ return {
573
+ name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
574
+ ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
575
+ value: repo
576
+ };
577
+ })
271
578
  });
272
579
 
273
- await downloadRepo(selectedRepo.url);
580
+ // If the selected repo has any releases, show download options
581
+ if (selectedRepo.hasReleases) {
582
+ const { downloadChoice } = await inquirer.prompt({
583
+ type: 'list',
584
+ name: 'downloadChoice',
585
+ message: 'This repository has downloadable packages. What would you like to do?',
586
+ choices: [
587
+ { name: '📦 Download package/binary', value: 'package' },
588
+ { name: '📂 Download source code', value: 'source' },
589
+ { name: '📦📂 Download both package and source', value: 'both' }
590
+ ]
591
+ });
592
+
593
+ if (downloadChoice === 'package' || downloadChoice === 'both') {
594
+ await showPackageMenu(selectedRepo);
595
+ }
596
+
597
+ if (downloadChoice === 'source' || downloadChoice === 'both') {
598
+ await downloadRepo(selectedRepo.html_url);
599
+ }
600
+ } else {
601
+ // No packages, just download source
602
+ await downloadRepo(selectedRepo.html_url);
603
+ }
274
604
  }
275
605
 
276
- main();
606
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git0",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A CLI manager for downloading GitHub repos.",
5
5
  "author": "vtempest",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "type": "module",
24
24
  "bin": {
25
- "gg": "./gg.js"
25
+ "gg": "./gg.js",
26
+ "git0": "./gg.js"
26
27
  }
27
28
  }
package/readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://i.imgur.com/zG1QI1q.png" />
2
+ <img src="https://i.imgur.com/poOtI3N.png" />
3
3
  </p>
4
4
  <p align="center">
5
5
  <a href="https://discord.gg/SJdBqBz3tV">
@@ -26,7 +26,7 @@
26
26
  </p>
27
27
 
28
28
 
29
- # GG - GitHub Repo Downloader
29
+ # Git0: GitHub Zero-Step Repo Downloader
30
30
 
31
31
  A fast and smart CLI tool to search, download, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
32
32
 
@@ -34,11 +34,11 @@ A fast and smart CLI tool to search, download, and instantly set up GitHub repos
34
34
  ## 🚀 Installation
35
35
 
36
36
  ```bash
37
- npm install -g git-gg
37
+ npm install -g git0
38
38
  ```
39
39
 
40
40
  ```bash
41
- bun install -g git-gg
41
+ bun install -g git0
42
42
  ```
43
43
 
44
44
  ![preview](https://i.imgur.com/K22NiBq.png)
@@ -65,10 +65,11 @@ gg react starter
65
65
  gg https://github.com/facebook/react
66
66
 
67
67
  # Download using owner/repo shorthand
68
- gg facebook/react
68
+ ## gg and git0 both work
69
+ git0 facebook/react
69
70
 
70
71
  ## Use without installing
71
- npx git-gg react starter
72
+ npx git0 react starter
72
73
  ```
73
74
 
74
75
  ### Supported Project Types