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.
@@ -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 Command18 } from "commander";
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 Command18();
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