gut-cli 0.1.13 → 0.1.15
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 +231 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +300 -0
- package/dist/lib/index.js +583 -0
- package/dist/lib/index.js.map +1 -0
- package/package.json +17 -2
|
@@ -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";
|
|
@@ -114,6 +114,9 @@ async function getKeytar() {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
async function saveApiKey(provider, apiKey) {
|
|
117
|
+
if (provider === "ollama") {
|
|
118
|
+
throw new Error("Ollama does not require an API key");
|
|
119
|
+
}
|
|
117
120
|
const keytar = await getKeytar();
|
|
118
121
|
if (!keytar) {
|
|
119
122
|
throw new Error("Keychain not available. Set environment variable instead.");
|
|
@@ -121,6 +124,9 @@ async function saveApiKey(provider, apiKey) {
|
|
|
121
124
|
await keytar.setPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider], apiKey);
|
|
122
125
|
}
|
|
123
126
|
async function getApiKey(provider) {
|
|
127
|
+
if (provider === "ollama") {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
124
130
|
const envKey = process.env[ENV_VAR_MAP[provider]];
|
|
125
131
|
if (envKey) return envKey;
|
|
126
132
|
const fallbackKey = process.env[FALLBACK_ENV_MAP[provider]];
|
|
@@ -130,6 +136,9 @@ async function getApiKey(provider) {
|
|
|
130
136
|
return keytar.getPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
|
|
131
137
|
}
|
|
132
138
|
async function deleteApiKey(provider) {
|
|
139
|
+
if (provider === "ollama") {
|
|
140
|
+
throw new Error("Ollama does not use an API key");
|
|
141
|
+
}
|
|
133
142
|
const keytar = await getKeytar();
|
|
134
143
|
if (!keytar) {
|
|
135
144
|
throw new Error("Keychain not available.");
|
|
@@ -137,20 +146,22 @@ async function deleteApiKey(provider) {
|
|
|
137
146
|
return keytar.deletePassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
|
|
138
147
|
}
|
|
139
148
|
async function listProviders() {
|
|
140
|
-
const
|
|
149
|
+
const apiKeyProviders = ["gemini", "openai", "anthropic"];
|
|
141
150
|
const results = await Promise.all(
|
|
142
|
-
|
|
151
|
+
apiKeyProviders.map(async (provider) => ({
|
|
143
152
|
provider,
|
|
144
153
|
hasKey: !!await getApiKey(provider)
|
|
145
154
|
}))
|
|
146
155
|
);
|
|
156
|
+
results.push({ provider: "ollama", hasKey: true });
|
|
147
157
|
return results;
|
|
148
158
|
}
|
|
149
159
|
function getProviderDisplayName(provider) {
|
|
150
160
|
const names = {
|
|
151
161
|
gemini: "Google Gemini",
|
|
152
162
|
openai: "OpenAI",
|
|
153
|
-
anthropic: "Anthropic Claude"
|
|
163
|
+
anthropic: "Anthropic Claude",
|
|
164
|
+
ollama: "Ollama (Local)"
|
|
154
165
|
};
|
|
155
166
|
return names[provider];
|
|
156
167
|
}
|
|
@@ -270,6 +281,7 @@ import { generateText, generateObject } from "ai";
|
|
|
270
281
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
271
282
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
272
283
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
284
|
+
import { createOllama } from "ollama-ai-provider";
|
|
273
285
|
import { z } from "zod";
|
|
274
286
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
275
287
|
import { join as join2, dirname } from "path";
|
|
@@ -410,29 +422,45 @@ function applyTemplate(userTemplate, templateName, variables) {
|
|
|
410
422
|
var DEFAULT_MODELS = {
|
|
411
423
|
gemini: "gemini-2.0-flash",
|
|
412
424
|
openai: "gpt-4o-mini",
|
|
413
|
-
anthropic: "claude-sonnet-4-20250514"
|
|
425
|
+
anthropic: "claude-sonnet-4-20250514",
|
|
426
|
+
ollama: "llama3.2"
|
|
414
427
|
};
|
|
415
428
|
async function getModel(options) {
|
|
416
|
-
const apiKey = await getApiKey(options.provider);
|
|
417
|
-
if (!apiKey) {
|
|
418
|
-
throw new Error(
|
|
419
|
-
`No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
429
|
const modelName = options.model || DEFAULT_MODELS[options.provider];
|
|
430
|
+
async function resolveApiKey() {
|
|
431
|
+
if (options.apiKey) return options.apiKey;
|
|
432
|
+
return getApiKey(options.provider);
|
|
433
|
+
}
|
|
434
|
+
if (options.provider !== "ollama") {
|
|
435
|
+
const apiKey = await resolveApiKey();
|
|
436
|
+
if (!apiKey) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
423
442
|
switch (options.provider) {
|
|
424
443
|
case "gemini": {
|
|
444
|
+
const apiKey = await resolveApiKey();
|
|
425
445
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
426
446
|
return google(modelName);
|
|
427
447
|
}
|
|
428
448
|
case "openai": {
|
|
449
|
+
const apiKey = await resolveApiKey();
|
|
429
450
|
const openai = createOpenAI({ apiKey });
|
|
430
451
|
return openai(modelName);
|
|
431
452
|
}
|
|
432
453
|
case "anthropic": {
|
|
454
|
+
const apiKey = await resolveApiKey();
|
|
433
455
|
const anthropic = createAnthropic({ apiKey });
|
|
434
456
|
return anthropic(modelName);
|
|
435
457
|
}
|
|
458
|
+
case "ollama": {
|
|
459
|
+
const ollama = createOllama({
|
|
460
|
+
baseURL: options.ollamaBaseUrl || "http://localhost:11434/api"
|
|
461
|
+
});
|
|
462
|
+
return ollama(modelName);
|
|
463
|
+
}
|
|
436
464
|
}
|
|
437
465
|
}
|
|
438
466
|
async function generateCommitMessage(diff, options, template) {
|
|
@@ -614,11 +642,12 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
614
642
|
schema: CommitSearchSchema,
|
|
615
643
|
prompt
|
|
616
644
|
});
|
|
617
|
-
const enrichedMatches = result.object.matches.map((match) => {
|
|
645
|
+
const enrichedMatches = result.object.matches.map((match, index) => {
|
|
618
646
|
const commit = commits.find((c) => c.hash.startsWith(match.hash));
|
|
619
647
|
if (!commit) {
|
|
620
648
|
return null;
|
|
621
649
|
}
|
|
650
|
+
const relevance = index === 0 ? "high" : index < 3 ? "medium" : "low";
|
|
622
651
|
return {
|
|
623
652
|
hash: commit.hash,
|
|
624
653
|
message: commit.message,
|
|
@@ -626,15 +655,9 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
626
655
|
email: commit.email,
|
|
627
656
|
date: commit.date,
|
|
628
657
|
reason: match.reason,
|
|
629
|
-
relevance
|
|
630
|
-
// First results are most relevant
|
|
658
|
+
relevance
|
|
631
659
|
};
|
|
632
660
|
}).filter((m) => m !== null);
|
|
633
|
-
enrichedMatches.forEach((match, index) => {
|
|
634
|
-
if (index === 0) match.relevance = "high";
|
|
635
|
-
else if (index < 3) match.relevance = "medium";
|
|
636
|
-
else match.relevance = "low";
|
|
637
|
-
});
|
|
638
661
|
return {
|
|
639
662
|
matches: enrichedMatches,
|
|
640
663
|
summary: result.object.summary
|
|
@@ -735,6 +758,20 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
735
758
|
});
|
|
736
759
|
return result.object;
|
|
737
760
|
}
|
|
761
|
+
async function generateGitignore(context, options, template) {
|
|
762
|
+
const model = await getModel(options);
|
|
763
|
+
const prompt = applyTemplate(template, "gitignore", {
|
|
764
|
+
files: context.files,
|
|
765
|
+
configFiles: context.configFiles,
|
|
766
|
+
existingGitignore: context.existingGitignore
|
|
767
|
+
});
|
|
768
|
+
const result = await generateText({
|
|
769
|
+
model,
|
|
770
|
+
prompt,
|
|
771
|
+
maxTokens: 2e3
|
|
772
|
+
});
|
|
773
|
+
return result.text.trim();
|
|
774
|
+
}
|
|
738
775
|
|
|
739
776
|
// src/commands/commit.ts
|
|
740
777
|
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 +2514,181 @@ var initCommand = new Command17("init").description("Initialize .gut/ templates
|
|
|
2477
2514
|
console.log(chalk18.gray("\nYou can now customize these templates for your project."));
|
|
2478
2515
|
});
|
|
2479
2516
|
|
|
2517
|
+
// src/commands/gitignore.ts
|
|
2518
|
+
import { Command as Command18 } from "commander";
|
|
2519
|
+
import chalk19 from "chalk";
|
|
2520
|
+
import ora15 from "ora";
|
|
2521
|
+
import { simpleGit as simpleGit15 } from "simple-git";
|
|
2522
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2523
|
+
import { join as join6 } from "path";
|
|
2524
|
+
var CONFIG_FILES = [
|
|
2525
|
+
// JavaScript/TypeScript
|
|
2526
|
+
"package.json",
|
|
2527
|
+
"tsconfig.json",
|
|
2528
|
+
"vite.config.ts",
|
|
2529
|
+
"vite.config.js",
|
|
2530
|
+
"next.config.js",
|
|
2531
|
+
"next.config.mjs",
|
|
2532
|
+
"nuxt.config.ts",
|
|
2533
|
+
"astro.config.mjs",
|
|
2534
|
+
// Python
|
|
2535
|
+
"pyproject.toml",
|
|
2536
|
+
"setup.py",
|
|
2537
|
+
"requirements.txt",
|
|
2538
|
+
"Pipfile",
|
|
2539
|
+
"poetry.lock",
|
|
2540
|
+
// Go
|
|
2541
|
+
"go.mod",
|
|
2542
|
+
"go.sum",
|
|
2543
|
+
// Rust
|
|
2544
|
+
"Cargo.toml",
|
|
2545
|
+
"Cargo.lock",
|
|
2546
|
+
// Java/Kotlin
|
|
2547
|
+
"pom.xml",
|
|
2548
|
+
"build.gradle",
|
|
2549
|
+
"build.gradle.kts",
|
|
2550
|
+
// Ruby
|
|
2551
|
+
"Gemfile",
|
|
2552
|
+
"Gemfile.lock",
|
|
2553
|
+
// PHP
|
|
2554
|
+
"composer.json",
|
|
2555
|
+
"composer.lock",
|
|
2556
|
+
// .NET
|
|
2557
|
+
"*.csproj",
|
|
2558
|
+
"*.fsproj",
|
|
2559
|
+
"*.sln",
|
|
2560
|
+
// Elixir
|
|
2561
|
+
"mix.exs",
|
|
2562
|
+
// Dart/Flutter
|
|
2563
|
+
"pubspec.yaml",
|
|
2564
|
+
// Swift
|
|
2565
|
+
"Package.swift"
|
|
2566
|
+
];
|
|
2567
|
+
function getFiles(dir, maxDepth = 3, currentDepth = 0) {
|
|
2568
|
+
if (currentDepth >= maxDepth) return [];
|
|
2569
|
+
const files = [];
|
|
2570
|
+
try {
|
|
2571
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2572
|
+
for (const entry of entries) {
|
|
2573
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "target" || entry.name === "__pycache__" || entry.name === "venv" || entry.name === ".venv") {
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
const fullPath = join6(dir, entry.name);
|
|
2577
|
+
if (entry.isDirectory()) {
|
|
2578
|
+
files.push(entry.name + "/");
|
|
2579
|
+
const subFiles = getFiles(fullPath, maxDepth, currentDepth + 1);
|
|
2580
|
+
files.push(...subFiles.map((f) => entry.name + "/" + f));
|
|
2581
|
+
} else {
|
|
2582
|
+
files.push(entry.name);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
} catch {
|
|
2586
|
+
}
|
|
2587
|
+
return files;
|
|
2588
|
+
}
|
|
2589
|
+
function findConfigFiles(repoRoot) {
|
|
2590
|
+
const found = /* @__PURE__ */ new Map();
|
|
2591
|
+
for (const configFile of CONFIG_FILES) {
|
|
2592
|
+
if (configFile.includes("*")) {
|
|
2593
|
+
const ext = configFile.replace("*", "");
|
|
2594
|
+
try {
|
|
2595
|
+
const entries = readdirSync2(repoRoot);
|
|
2596
|
+
for (const entry of entries) {
|
|
2597
|
+
if (entry.endsWith(ext)) {
|
|
2598
|
+
const content = readFileSync7(join6(repoRoot, entry), "utf-8");
|
|
2599
|
+
found.set(entry, content.slice(0, 2e3));
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
} catch {
|
|
2603
|
+
}
|
|
2604
|
+
} else {
|
|
2605
|
+
const filePath = join6(repoRoot, configFile);
|
|
2606
|
+
if (existsSync6(filePath)) {
|
|
2607
|
+
try {
|
|
2608
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
2609
|
+
found.set(configFile, content.slice(0, 2e3));
|
|
2610
|
+
} catch {
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
return found;
|
|
2616
|
+
}
|
|
2617
|
+
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) => {
|
|
2618
|
+
const git = simpleGit15();
|
|
2619
|
+
const repoRoot = await git.revparse(["--show-toplevel"]).catch(() => process.cwd());
|
|
2620
|
+
const root = repoRoot.trim();
|
|
2621
|
+
const provider = options.provider.toLowerCase();
|
|
2622
|
+
const template = findTemplate(root, "gitignore");
|
|
2623
|
+
if (template) {
|
|
2624
|
+
console.log(chalk19.gray("Using template from project..."));
|
|
2625
|
+
}
|
|
2626
|
+
const spinner = ora15("Analyzing project structure...").start();
|
|
2627
|
+
const files = getFiles(root);
|
|
2628
|
+
const configFiles = findConfigFiles(root);
|
|
2629
|
+
const gitignorePath = join6(root, options.output);
|
|
2630
|
+
let existingGitignore;
|
|
2631
|
+
if (existsSync6(gitignorePath)) {
|
|
2632
|
+
existingGitignore = readFileSync7(gitignorePath, "utf-8");
|
|
2633
|
+
}
|
|
2634
|
+
let configFilesStr = "";
|
|
2635
|
+
if (configFiles.size > 0) {
|
|
2636
|
+
const entries = [];
|
|
2637
|
+
for (const [name, content] of configFiles) {
|
|
2638
|
+
entries.push(`### ${name}
|
|
2639
|
+
\`\`\`
|
|
2640
|
+
${content}
|
|
2641
|
+
\`\`\``);
|
|
2642
|
+
}
|
|
2643
|
+
configFilesStr = entries.join("\n\n");
|
|
2644
|
+
}
|
|
2645
|
+
spinner.text = "Generating .gitignore...";
|
|
2646
|
+
try {
|
|
2647
|
+
const gitignoreContent = await generateGitignore(
|
|
2648
|
+
{
|
|
2649
|
+
files: files.slice(0, 200).join("\n"),
|
|
2650
|
+
configFiles: configFilesStr,
|
|
2651
|
+
existingGitignore
|
|
2652
|
+
},
|
|
2653
|
+
{ provider, model: options.model },
|
|
2654
|
+
template || void 0
|
|
2655
|
+
);
|
|
2656
|
+
spinner.stop();
|
|
2657
|
+
if (options.stdout) {
|
|
2658
|
+
console.log(gitignoreContent);
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
console.log(chalk19.bold("\nGenerated .gitignore:\n"));
|
|
2662
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2663
|
+
console.log(gitignoreContent);
|
|
2664
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2665
|
+
console.log();
|
|
2666
|
+
if (existsSync6(gitignorePath) && !options.yes) {
|
|
2667
|
+
const readline = await import("readline");
|
|
2668
|
+
const rl = readline.createInterface({
|
|
2669
|
+
input: process.stdin,
|
|
2670
|
+
output: process.stdout
|
|
2671
|
+
});
|
|
2672
|
+
const answer = await new Promise((resolve) => {
|
|
2673
|
+
rl.question(chalk19.cyan(`${options.output} already exists. Overwrite? (y/N) `), resolve);
|
|
2674
|
+
});
|
|
2675
|
+
rl.close();
|
|
2676
|
+
if (answer.toLowerCase() !== "y") {
|
|
2677
|
+
console.log(chalk19.gray("Aborted."));
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
writeFileSync4(gitignorePath, gitignoreContent);
|
|
2682
|
+
console.log(chalk19.green(`\u2713 Wrote ${options.output}`));
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
spinner.fail("Failed to generate .gitignore");
|
|
2685
|
+
console.error(chalk19.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2686
|
+
process.exit(1);
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2480
2690
|
// src/index.ts
|
|
2481
|
-
var program = new
|
|
2691
|
+
var program = new Command19();
|
|
2482
2692
|
program.name("gut").description("Git Utility Tool - AI-powered git commands").version("0.1.0");
|
|
2483
2693
|
program.addCommand(cleanupCommand);
|
|
2484
2694
|
program.addCommand(authCommand);
|
|
@@ -2497,5 +2707,6 @@ program.addCommand(summaryCommand);
|
|
|
2497
2707
|
program.addCommand(configCommand);
|
|
2498
2708
|
program.addCommand(langCommand);
|
|
2499
2709
|
program.addCommand(initCommand);
|
|
2710
|
+
program.addCommand(gitignoreCommand);
|
|
2500
2711
|
program.parse();
|
|
2501
2712
|
//# sourceMappingURL=index.js.map
|