gut-cli 0.1.13 → 0.1.14
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/.gut/gitignore.md +42 -0
- package/README.md +28 -0
- package/dist/index.js +190 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
You are an expert at creating .gitignore files.
|
|
2
|
+
|
|
3
|
+
Analyze the following project structure and configuration files to generate an appropriate .gitignore file.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{{files}}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
{{#configFiles}}
|
|
12
|
+
## Detected Config Files
|
|
13
|
+
|
|
14
|
+
{{configFiles}}
|
|
15
|
+
{{/configFiles}}
|
|
16
|
+
|
|
17
|
+
{{#existingGitignore}}
|
|
18
|
+
## Existing .gitignore
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
{{existingGitignore}}
|
|
22
|
+
```
|
|
23
|
+
{{/existingGitignore}}
|
|
24
|
+
|
|
25
|
+
## Rules
|
|
26
|
+
|
|
27
|
+
- Detect the language/framework being used (Node.js, Python, Go, Rust, Java, Ruby, PHP, .NET, etc.)
|
|
28
|
+
- Include common ignore patterns for the detected stack
|
|
29
|
+
- Include OS-specific files (.DS_Store, Thumbs.db, etc.)
|
|
30
|
+
- Include IDE/editor files (.vscode/, .idea/, *.swp, etc.)
|
|
31
|
+
- Include environment files (.env, .env.local, etc.)
|
|
32
|
+
- Include build outputs and dependencies based on detected stack
|
|
33
|
+
- Include log files and temporary files
|
|
34
|
+
- Do NOT ignore files that should be tracked (source code, configs, etc.)
|
|
35
|
+
- Keep the file organized with comments for each section
|
|
36
|
+
{{#existingGitignore}}
|
|
37
|
+
- Preserve any project-specific patterns from the existing .gitignore
|
|
38
|
+
{{/existingGitignore}}
|
|
39
|
+
|
|
40
|
+
## Output
|
|
41
|
+
|
|
42
|
+
Respond with ONLY the .gitignore content, nothing else. Include section comments for clarity.
|
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ npm install -g gut-cli
|
|
|
35
35
|
| `gut config` | Manage configuration (language, etc.) |
|
|
36
36
|
| `gut lang` | Set or show output language |
|
|
37
37
|
| `gut init` | Initialize .gut/ templates in your project |
|
|
38
|
+
| `gut gitignore` | Generate .gitignore from codebase |
|
|
38
39
|
|
|
39
40
|
### `gut commit`
|
|
40
41
|
|
|
@@ -389,6 +390,32 @@ gut init --provider openai
|
|
|
389
390
|
|
|
390
391
|
Templates are automatically translated to your configured language (set via `gut lang`).
|
|
391
392
|
|
|
393
|
+
### `gut gitignore`
|
|
394
|
+
|
|
395
|
+
Generate a .gitignore file by analyzing your project structure.
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# Generate .gitignore (prompts before overwriting)
|
|
399
|
+
gut gitignore
|
|
400
|
+
|
|
401
|
+
# Auto-overwrite without confirmation
|
|
402
|
+
gut gitignore --yes
|
|
403
|
+
|
|
404
|
+
# Print to stdout instead of file
|
|
405
|
+
gut gitignore --stdout
|
|
406
|
+
|
|
407
|
+
# Use specific provider
|
|
408
|
+
gut gitignore --provider openai
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**How it works:**
|
|
412
|
+
- Scans your project structure (files and directories)
|
|
413
|
+
- Detects config files (package.json, Cargo.toml, go.mod, pyproject.toml, etc.)
|
|
414
|
+
- Identifies the language/framework stack
|
|
415
|
+
- Generates appropriate ignore patterns
|
|
416
|
+
|
|
417
|
+
**Template Support**: Create `.gut/gitignore.md` to customize the generation prompt.
|
|
418
|
+
|
|
392
419
|
### `gut cleanup`
|
|
393
420
|
|
|
394
421
|
Delete merged branches safely.
|
|
@@ -457,6 +484,7 @@ gut looks for template files in your repository's `.gut/` folder. Each template
|
|
|
457
484
|
| `.gut/changelog.md` | Changelog format |
|
|
458
485
|
| `.gut/stash.md` | Stash name prompt |
|
|
459
486
|
| `.gut/summary.md` | Work summary format |
|
|
487
|
+
| `.gut/gitignore.md` | Gitignore generation prompt |
|
|
460
488
|
| `.github/pull_request_template.md` | GitHub PR template (prioritized over `.gut/pr.md`) |
|
|
461
489
|
|
|
462
490
|
## Development
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command19 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/cleanup.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -735,6 +735,20 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
735
735
|
});
|
|
736
736
|
return result.object;
|
|
737
737
|
}
|
|
738
|
+
async function generateGitignore(context, options, template) {
|
|
739
|
+
const model = await getModel(options);
|
|
740
|
+
const prompt = applyTemplate(template, "gitignore", {
|
|
741
|
+
files: context.files,
|
|
742
|
+
configFiles: context.configFiles,
|
|
743
|
+
existingGitignore: context.existingGitignore
|
|
744
|
+
});
|
|
745
|
+
const result = await generateText({
|
|
746
|
+
model,
|
|
747
|
+
prompt,
|
|
748
|
+
maxTokens: 2e3
|
|
749
|
+
});
|
|
750
|
+
return result.text.trim();
|
|
751
|
+
}
|
|
738
752
|
|
|
739
753
|
// src/commands/commit.ts
|
|
740
754
|
var commitCommand = new Command3("commit").description("Generate a commit message using AI").option("-p, --provider <provider>", "AI provider (gemini, openai, anthropic)", "gemini").option("-m, --model <model>", "Model to use (provider-specific)").option("-c, --commit", "Automatically commit with the generated message").option("-a, --all", "Force stage all changes (default: auto-stage if nothing staged)").action(async (options) => {
|
|
@@ -2477,8 +2491,181 @@ var initCommand = new Command17("init").description("Initialize .gut/ templates
|
|
|
2477
2491
|
console.log(chalk18.gray("\nYou can now customize these templates for your project."));
|
|
2478
2492
|
});
|
|
2479
2493
|
|
|
2494
|
+
// src/commands/gitignore.ts
|
|
2495
|
+
import { Command as Command18 } from "commander";
|
|
2496
|
+
import chalk19 from "chalk";
|
|
2497
|
+
import ora15 from "ora";
|
|
2498
|
+
import { simpleGit as simpleGit15 } from "simple-git";
|
|
2499
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2500
|
+
import { join as join6 } from "path";
|
|
2501
|
+
var CONFIG_FILES = [
|
|
2502
|
+
// JavaScript/TypeScript
|
|
2503
|
+
"package.json",
|
|
2504
|
+
"tsconfig.json",
|
|
2505
|
+
"vite.config.ts",
|
|
2506
|
+
"vite.config.js",
|
|
2507
|
+
"next.config.js",
|
|
2508
|
+
"next.config.mjs",
|
|
2509
|
+
"nuxt.config.ts",
|
|
2510
|
+
"astro.config.mjs",
|
|
2511
|
+
// Python
|
|
2512
|
+
"pyproject.toml",
|
|
2513
|
+
"setup.py",
|
|
2514
|
+
"requirements.txt",
|
|
2515
|
+
"Pipfile",
|
|
2516
|
+
"poetry.lock",
|
|
2517
|
+
// Go
|
|
2518
|
+
"go.mod",
|
|
2519
|
+
"go.sum",
|
|
2520
|
+
// Rust
|
|
2521
|
+
"Cargo.toml",
|
|
2522
|
+
"Cargo.lock",
|
|
2523
|
+
// Java/Kotlin
|
|
2524
|
+
"pom.xml",
|
|
2525
|
+
"build.gradle",
|
|
2526
|
+
"build.gradle.kts",
|
|
2527
|
+
// Ruby
|
|
2528
|
+
"Gemfile",
|
|
2529
|
+
"Gemfile.lock",
|
|
2530
|
+
// PHP
|
|
2531
|
+
"composer.json",
|
|
2532
|
+
"composer.lock",
|
|
2533
|
+
// .NET
|
|
2534
|
+
"*.csproj",
|
|
2535
|
+
"*.fsproj",
|
|
2536
|
+
"*.sln",
|
|
2537
|
+
// Elixir
|
|
2538
|
+
"mix.exs",
|
|
2539
|
+
// Dart/Flutter
|
|
2540
|
+
"pubspec.yaml",
|
|
2541
|
+
// Swift
|
|
2542
|
+
"Package.swift"
|
|
2543
|
+
];
|
|
2544
|
+
function getFiles(dir, maxDepth = 3, currentDepth = 0) {
|
|
2545
|
+
if (currentDepth >= maxDepth) return [];
|
|
2546
|
+
const files = [];
|
|
2547
|
+
try {
|
|
2548
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2549
|
+
for (const entry of entries) {
|
|
2550
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "target" || entry.name === "__pycache__" || entry.name === "venv" || entry.name === ".venv") {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const fullPath = join6(dir, entry.name);
|
|
2554
|
+
if (entry.isDirectory()) {
|
|
2555
|
+
files.push(entry.name + "/");
|
|
2556
|
+
const subFiles = getFiles(fullPath, maxDepth, currentDepth + 1);
|
|
2557
|
+
files.push(...subFiles.map((f) => entry.name + "/" + f));
|
|
2558
|
+
} else {
|
|
2559
|
+
files.push(entry.name);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
} catch {
|
|
2563
|
+
}
|
|
2564
|
+
return files;
|
|
2565
|
+
}
|
|
2566
|
+
function findConfigFiles(repoRoot) {
|
|
2567
|
+
const found = /* @__PURE__ */ new Map();
|
|
2568
|
+
for (const configFile of CONFIG_FILES) {
|
|
2569
|
+
if (configFile.includes("*")) {
|
|
2570
|
+
const ext = configFile.replace("*", "");
|
|
2571
|
+
try {
|
|
2572
|
+
const entries = readdirSync2(repoRoot);
|
|
2573
|
+
for (const entry of entries) {
|
|
2574
|
+
if (entry.endsWith(ext)) {
|
|
2575
|
+
const content = readFileSync7(join6(repoRoot, entry), "utf-8");
|
|
2576
|
+
found.set(entry, content.slice(0, 2e3));
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
} catch {
|
|
2580
|
+
}
|
|
2581
|
+
} else {
|
|
2582
|
+
const filePath = join6(repoRoot, configFile);
|
|
2583
|
+
if (existsSync6(filePath)) {
|
|
2584
|
+
try {
|
|
2585
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
2586
|
+
found.set(configFile, content.slice(0, 2e3));
|
|
2587
|
+
} catch {
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return found;
|
|
2593
|
+
}
|
|
2594
|
+
var gitignoreCommand = new Command18("gitignore").description("Generate .gitignore from current codebase").option("-p, --provider <provider>", "AI provider (gemini, openai, anthropic)", "gemini").option("-m, --model <model>", "Model to use (provider-specific)").option("-o, --output <file>", "Output file (default: .gitignore)", ".gitignore").option("--stdout", "Print to stdout instead of file").option("-y, --yes", "Overwrite existing .gitignore without confirmation").action(async (options) => {
|
|
2595
|
+
const git = simpleGit15();
|
|
2596
|
+
const repoRoot = await git.revparse(["--show-toplevel"]).catch(() => process.cwd());
|
|
2597
|
+
const root = repoRoot.trim();
|
|
2598
|
+
const provider = options.provider.toLowerCase();
|
|
2599
|
+
const template = findTemplate(root, "gitignore");
|
|
2600
|
+
if (template) {
|
|
2601
|
+
console.log(chalk19.gray("Using template from project..."));
|
|
2602
|
+
}
|
|
2603
|
+
const spinner = ora15("Analyzing project structure...").start();
|
|
2604
|
+
const files = getFiles(root);
|
|
2605
|
+
const configFiles = findConfigFiles(root);
|
|
2606
|
+
const gitignorePath = join6(root, options.output);
|
|
2607
|
+
let existingGitignore;
|
|
2608
|
+
if (existsSync6(gitignorePath)) {
|
|
2609
|
+
existingGitignore = readFileSync7(gitignorePath, "utf-8");
|
|
2610
|
+
}
|
|
2611
|
+
let configFilesStr = "";
|
|
2612
|
+
if (configFiles.size > 0) {
|
|
2613
|
+
const entries = [];
|
|
2614
|
+
for (const [name, content] of configFiles) {
|
|
2615
|
+
entries.push(`### ${name}
|
|
2616
|
+
\`\`\`
|
|
2617
|
+
${content}
|
|
2618
|
+
\`\`\``);
|
|
2619
|
+
}
|
|
2620
|
+
configFilesStr = entries.join("\n\n");
|
|
2621
|
+
}
|
|
2622
|
+
spinner.text = "Generating .gitignore...";
|
|
2623
|
+
try {
|
|
2624
|
+
const gitignoreContent = await generateGitignore(
|
|
2625
|
+
{
|
|
2626
|
+
files: files.slice(0, 200).join("\n"),
|
|
2627
|
+
configFiles: configFilesStr,
|
|
2628
|
+
existingGitignore
|
|
2629
|
+
},
|
|
2630
|
+
{ provider, model: options.model },
|
|
2631
|
+
template || void 0
|
|
2632
|
+
);
|
|
2633
|
+
spinner.stop();
|
|
2634
|
+
if (options.stdout) {
|
|
2635
|
+
console.log(gitignoreContent);
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
console.log(chalk19.bold("\nGenerated .gitignore:\n"));
|
|
2639
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2640
|
+
console.log(gitignoreContent);
|
|
2641
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2642
|
+
console.log();
|
|
2643
|
+
if (existsSync6(gitignorePath) && !options.yes) {
|
|
2644
|
+
const readline = await import("readline");
|
|
2645
|
+
const rl = readline.createInterface({
|
|
2646
|
+
input: process.stdin,
|
|
2647
|
+
output: process.stdout
|
|
2648
|
+
});
|
|
2649
|
+
const answer = await new Promise((resolve) => {
|
|
2650
|
+
rl.question(chalk19.cyan(`${options.output} already exists. Overwrite? (y/N) `), resolve);
|
|
2651
|
+
});
|
|
2652
|
+
rl.close();
|
|
2653
|
+
if (answer.toLowerCase() !== "y") {
|
|
2654
|
+
console.log(chalk19.gray("Aborted."));
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
writeFileSync4(gitignorePath, gitignoreContent);
|
|
2659
|
+
console.log(chalk19.green(`\u2713 Wrote ${options.output}`));
|
|
2660
|
+
} catch (error) {
|
|
2661
|
+
spinner.fail("Failed to generate .gitignore");
|
|
2662
|
+
console.error(chalk19.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2663
|
+
process.exit(1);
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2480
2667
|
// src/index.ts
|
|
2481
|
-
var program = new
|
|
2668
|
+
var program = new Command19();
|
|
2482
2669
|
program.name("gut").description("Git Utility Tool - AI-powered git commands").version("0.1.0");
|
|
2483
2670
|
program.addCommand(cleanupCommand);
|
|
2484
2671
|
program.addCommand(authCommand);
|
|
@@ -2497,5 +2684,6 @@ program.addCommand(summaryCommand);
|
|
|
2497
2684
|
program.addCommand(configCommand);
|
|
2498
2685
|
program.addCommand(langCommand);
|
|
2499
2686
|
program.addCommand(initCommand);
|
|
2687
|
+
program.addCommand(gitignoreCommand);
|
|
2500
2688
|
program.parse();
|
|
2501
2689
|
//# sourceMappingURL=index.js.map
|