gut-cli 0.1.7 → 0.1.9
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/README.md +52 -7
- package/dist/index.js +259 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ All AI commands support short aliases (without `ai-` prefix):
|
|
|
24
24
|
| `gut changelog` | - | Generate changelogs |
|
|
25
25
|
| `gut sync` | - | Sync with remote (fetch + rebase/merge) |
|
|
26
26
|
| `gut stash` | - | Stash with AI-generated names |
|
|
27
|
+
| `gut config` | - | Manage configuration (language, etc.) |
|
|
28
|
+
| `gut lang` | - | Set or show output language |
|
|
27
29
|
|
|
28
30
|
### `gut commit`
|
|
29
31
|
|
|
@@ -193,20 +195,20 @@ gut branch -d "add user authentication"
|
|
|
193
195
|
|
|
194
196
|
### `gut sync`
|
|
195
197
|
|
|
196
|
-
Sync current branch with remote (fetch + rebase).
|
|
198
|
+
Sync current branch with remote (fetch + rebase + push).
|
|
197
199
|
|
|
198
200
|
```bash
|
|
199
|
-
# Sync current branch (rebase
|
|
201
|
+
# Sync current branch (fetch + rebase + push)
|
|
200
202
|
gut sync
|
|
201
203
|
|
|
202
|
-
# Use merge instead of rebase
|
|
203
|
-
gut sync --merge
|
|
204
|
-
|
|
205
204
|
# Auto-stash changes before sync
|
|
206
205
|
gut sync --stash
|
|
207
206
|
|
|
208
|
-
#
|
|
209
|
-
gut sync --
|
|
207
|
+
# Use merge instead of rebase
|
|
208
|
+
gut sync --merge
|
|
209
|
+
|
|
210
|
+
# Skip push
|
|
211
|
+
gut sync --no-push
|
|
210
212
|
```
|
|
211
213
|
|
|
212
214
|
### `gut stash`
|
|
@@ -259,6 +261,49 @@ gut changelog --json
|
|
|
259
261
|
|
|
260
262
|
**Changelog Template Support**: If `CHANGELOG.md` exists, gut will match its style.
|
|
261
263
|
|
|
264
|
+
### `gut lang`
|
|
265
|
+
|
|
266
|
+
Set or show AI output language (shortcut for `gut config set lang`).
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Show current language
|
|
270
|
+
gut lang
|
|
271
|
+
|
|
272
|
+
# Set to Japanese
|
|
273
|
+
gut lang ja
|
|
274
|
+
|
|
275
|
+
# Set to English
|
|
276
|
+
gut lang en
|
|
277
|
+
|
|
278
|
+
# Set for current repository only
|
|
279
|
+
gut lang en --local
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### `gut config`
|
|
283
|
+
|
|
284
|
+
Manage gut configuration settings.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# List all settings
|
|
288
|
+
gut config list
|
|
289
|
+
|
|
290
|
+
# Set language to Japanese (global)
|
|
291
|
+
gut config set lang ja
|
|
292
|
+
|
|
293
|
+
# Set language for current repository only
|
|
294
|
+
gut config set lang en --local
|
|
295
|
+
|
|
296
|
+
# Get current language
|
|
297
|
+
gut config get lang
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Available settings:**
|
|
301
|
+
- `lang` - Output language for AI responses (`en`, `ja`)
|
|
302
|
+
|
|
303
|
+
**Configuration precedence:**
|
|
304
|
+
1. Local: `.gut/config.json` (per-repository)
|
|
305
|
+
2. Global: `~/.config/gut/config.json`
|
|
306
|
+
|
|
262
307
|
### `gut cleanup`
|
|
263
308
|
|
|
264
309
|
Delete merged branches safely.
|
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 Command15 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/cleanup.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -264,8 +264,8 @@ import { Command as Command3 } from "commander";
|
|
|
264
264
|
import chalk3 from "chalk";
|
|
265
265
|
import ora2 from "ora";
|
|
266
266
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
267
|
-
import { existsSync, readFileSync } from "fs";
|
|
268
|
-
import { join } from "path";
|
|
267
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
268
|
+
import { join as join2 } from "path";
|
|
269
269
|
|
|
270
270
|
// src/lib/ai.ts
|
|
271
271
|
import { generateText, generateObject } from "ai";
|
|
@@ -273,6 +273,108 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
|
273
273
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
274
274
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
275
275
|
import { z } from "zod";
|
|
276
|
+
|
|
277
|
+
// src/lib/config.ts
|
|
278
|
+
import { homedir } from "os";
|
|
279
|
+
import { join } from "path";
|
|
280
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
281
|
+
import { execSync } from "child_process";
|
|
282
|
+
var DEFAULT_CONFIG = {
|
|
283
|
+
lang: "en"
|
|
284
|
+
};
|
|
285
|
+
function getGlobalConfigPath() {
|
|
286
|
+
const configDir = join(homedir(), ".config", "gut");
|
|
287
|
+
return join(configDir, "config.json");
|
|
288
|
+
}
|
|
289
|
+
function getRepoRoot() {
|
|
290
|
+
try {
|
|
291
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function getLocalConfigPath() {
|
|
297
|
+
const repoRoot = getRepoRoot();
|
|
298
|
+
if (!repoRoot) return null;
|
|
299
|
+
return join(repoRoot, ".gut", "config.json");
|
|
300
|
+
}
|
|
301
|
+
function ensureGlobalConfigDir() {
|
|
302
|
+
const configDir = join(homedir(), ".config", "gut");
|
|
303
|
+
if (!existsSync(configDir)) {
|
|
304
|
+
mkdirSync(configDir, { recursive: true });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function ensureLocalConfigDir() {
|
|
308
|
+
const repoRoot = getRepoRoot();
|
|
309
|
+
if (!repoRoot) return;
|
|
310
|
+
const gutDir = join(repoRoot, ".gut");
|
|
311
|
+
if (!existsSync(gutDir)) {
|
|
312
|
+
mkdirSync(gutDir, { recursive: true });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function readConfigFile(path2) {
|
|
316
|
+
if (!existsSync(path2)) return {};
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(readFileSync(path2, "utf-8"));
|
|
319
|
+
} catch {
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function getGlobalConfig() {
|
|
324
|
+
const globalPath = getGlobalConfigPath();
|
|
325
|
+
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
326
|
+
}
|
|
327
|
+
function getLocalConfig() {
|
|
328
|
+
const localPath = getLocalConfigPath();
|
|
329
|
+
if (!localPath) return {};
|
|
330
|
+
return readConfigFile(localPath);
|
|
331
|
+
}
|
|
332
|
+
function getConfig() {
|
|
333
|
+
const globalConfig = getGlobalConfig();
|
|
334
|
+
const localConfig = getLocalConfig();
|
|
335
|
+
return { ...globalConfig, ...localConfig };
|
|
336
|
+
}
|
|
337
|
+
function setGlobalConfig(key, value) {
|
|
338
|
+
ensureGlobalConfigDir();
|
|
339
|
+
const config = getGlobalConfig();
|
|
340
|
+
config[key] = value;
|
|
341
|
+
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
342
|
+
}
|
|
343
|
+
function setLocalConfig(key, value) {
|
|
344
|
+
const localPath = getLocalConfigPath();
|
|
345
|
+
if (!localPath) {
|
|
346
|
+
throw new Error("Not in a git repository");
|
|
347
|
+
}
|
|
348
|
+
ensureLocalConfigDir();
|
|
349
|
+
const config = getLocalConfig();
|
|
350
|
+
config[key] = value;
|
|
351
|
+
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
352
|
+
}
|
|
353
|
+
function getLanguage() {
|
|
354
|
+
return getConfig().lang;
|
|
355
|
+
}
|
|
356
|
+
function setLanguage(lang, local = false) {
|
|
357
|
+
if (local) {
|
|
358
|
+
setLocalConfig("lang", lang);
|
|
359
|
+
} else {
|
|
360
|
+
setGlobalConfig("lang", lang);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function getLanguageInstruction(lang) {
|
|
364
|
+
switch (lang) {
|
|
365
|
+
case "ja":
|
|
366
|
+
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
367
|
+
case "en":
|
|
368
|
+
default:
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
var VALID_LANGUAGES = ["en", "ja"];
|
|
373
|
+
function isValidLanguage(lang) {
|
|
374
|
+
return VALID_LANGUAGES.includes(lang);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/lib/ai.ts
|
|
276
378
|
var DEFAULT_MODELS = {
|
|
277
379
|
gemini: "gemini-2.0-flash",
|
|
278
380
|
openai: "gpt-4o-mini",
|
|
@@ -317,6 +419,7 @@ Rules:
|
|
|
317
419
|
- Description should be lowercase, imperative mood, no period at end
|
|
318
420
|
- Keep the first line under 72 characters
|
|
319
421
|
- If changes are complex, add a blank line and bullet points for details`;
|
|
422
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
320
423
|
const prompt = `You are an expert at writing git commit messages.
|
|
321
424
|
|
|
322
425
|
Analyze the following git diff and generate a concise, meaningful commit message.
|
|
@@ -327,7 +430,7 @@ Git diff:
|
|
|
327
430
|
${diff.slice(0, 8e3)}
|
|
328
431
|
\`\`\`
|
|
329
432
|
|
|
330
|
-
Respond with ONLY the commit message, nothing else
|
|
433
|
+
Respond with ONLY the commit message, nothing else.${langInstruction}`;
|
|
331
434
|
const result = await generateText({
|
|
332
435
|
model,
|
|
333
436
|
prompt,
|
|
@@ -351,6 +454,7 @@ Rules for description:
|
|
|
351
454
|
- ## Summary section with 2-3 bullet points
|
|
352
455
|
- ## Changes section listing key modifications
|
|
353
456
|
- ## Test Plan section (suggest what to test)`;
|
|
457
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
354
458
|
const prompt = `You are an expert at writing pull request descriptions.
|
|
355
459
|
|
|
356
460
|
Generate a clear and informative PR title and description based on the following information.
|
|
@@ -373,7 +477,7 @@ Respond in JSON format:
|
|
|
373
477
|
{
|
|
374
478
|
"title": "...",
|
|
375
479
|
"body": "..."
|
|
376
|
-
}`;
|
|
480
|
+
}${langInstruction}`;
|
|
377
481
|
const result = await generateText({
|
|
378
482
|
model,
|
|
379
483
|
prompt,
|
|
@@ -404,6 +508,7 @@ var CodeReviewSchema = z.object({
|
|
|
404
508
|
});
|
|
405
509
|
async function generateCodeReview(diff, options) {
|
|
406
510
|
const model = await getModel(options);
|
|
511
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
407
512
|
const result = await generateObject({
|
|
408
513
|
model,
|
|
409
514
|
schema: CodeReviewSchema,
|
|
@@ -421,7 +526,7 @@ Git diff:
|
|
|
421
526
|
${diff.slice(0, 1e4)}
|
|
422
527
|
\`\`\`
|
|
423
528
|
|
|
424
|
-
Be constructive and specific. Include line numbers when possible
|
|
529
|
+
Be constructive and specific. Include line numbers when possible.${langInstruction}`
|
|
425
530
|
});
|
|
426
531
|
return result.object;
|
|
427
532
|
}
|
|
@@ -451,6 +556,7 @@ Use Keep a Changelog format (https://keepachangelog.com/):
|
|
|
451
556
|
- Each item should be a concise description of the change
|
|
452
557
|
- Use past tense`;
|
|
453
558
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
559
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
454
560
|
const result = await generateObject({
|
|
455
561
|
model,
|
|
456
562
|
schema: ChangelogSchema,
|
|
@@ -472,7 +578,7 @@ Focus on:
|
|
|
472
578
|
- Bug fixes and their impact
|
|
473
579
|
- Breaking changes (highlight these)
|
|
474
580
|
- Group related changes together
|
|
475
|
-
- Write for end users, not developers (unless it's a library)`
|
|
581
|
+
- Write for end users, not developers (unless it's a library)${langInstruction}`
|
|
476
582
|
});
|
|
477
583
|
return result.object;
|
|
478
584
|
}
|
|
@@ -502,6 +608,7 @@ IMPORTANT: Use this project context to provide more accurate explanations:
|
|
|
502
608
|
${projectContext.slice(0, 4e3)}
|
|
503
609
|
--- PROJECT CONTEXT END ---
|
|
504
610
|
` : "";
|
|
611
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
505
612
|
if (context.type === "file-content") {
|
|
506
613
|
const result2 = await generateObject({
|
|
507
614
|
model,
|
|
@@ -524,7 +631,7 @@ Focus on:
|
|
|
524
631
|
- Dependencies and what it interacts with
|
|
525
632
|
- Any important patterns or architecture decisions
|
|
526
633
|
|
|
527
|
-
Explain in a way that helps someone quickly understand this file's purpose and how it fits into the larger codebase
|
|
634
|
+
Explain in a way that helps someone quickly understand this file's purpose and how it fits into the larger codebase.${langInstruction}`
|
|
528
635
|
});
|
|
529
636
|
return result2.object;
|
|
530
637
|
}
|
|
@@ -582,7 +689,7 @@ Focus on:
|
|
|
582
689
|
- The broader context and purpose
|
|
583
690
|
- Any important implications or side effects
|
|
584
691
|
|
|
585
|
-
Explain in a way that helps someone understand not just the "what" but the "why" behind these changes
|
|
692
|
+
Explain in a way that helps someone understand not just the "what" but the "why" behind these changes.${langInstruction}`
|
|
586
693
|
});
|
|
587
694
|
return result.object;
|
|
588
695
|
}
|
|
@@ -605,6 +712,7 @@ ${projectContext.slice(0, 3e3)}
|
|
|
605
712
|
--- PROJECT CONTEXT END ---
|
|
606
713
|
` : "";
|
|
607
714
|
const commitList = commits.map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`).join("\n");
|
|
715
|
+
const langInstruction = getLanguageInstruction(getLanguage());
|
|
608
716
|
const result = await generateObject({
|
|
609
717
|
model,
|
|
610
718
|
schema: CommitSearchSchema,
|
|
@@ -627,7 +735,7 @@ Only include commits that actually match the query - if none match well, return
|
|
|
627
735
|
|
|
628
736
|
For each match, provide:
|
|
629
737
|
- The commit hash (first 7 characters are fine)
|
|
630
|
-
- A brief reason explaining why this commit matches the query`
|
|
738
|
+
- A brief reason explaining why this commit matches the query${langInstruction}`
|
|
631
739
|
});
|
|
632
740
|
const enrichedMatches = result.object.matches.map((match) => {
|
|
633
741
|
const commit = commits.find((c) => c.hash.startsWith(match.hash));
|
|
@@ -765,9 +873,9 @@ var CONVENTION_PATHS = [
|
|
|
765
873
|
];
|
|
766
874
|
function findCommitConvention(repoRoot) {
|
|
767
875
|
for (const conventionPath of CONVENTION_PATHS) {
|
|
768
|
-
const fullPath =
|
|
769
|
-
if (
|
|
770
|
-
return
|
|
876
|
+
const fullPath = join2(repoRoot, conventionPath);
|
|
877
|
+
if (existsSync2(fullPath)) {
|
|
878
|
+
return readFileSync2(fullPath, "utf-8");
|
|
771
879
|
}
|
|
772
880
|
}
|
|
773
881
|
return null;
|
|
@@ -832,14 +940,14 @@ var aiCommitCommand = new Command3("ai-commit").alias("commit").description("Gen
|
|
|
832
940
|
console.log(chalk3.green("\u2713 Committed successfully"));
|
|
833
941
|
} else if (answer.toLowerCase() === "e") {
|
|
834
942
|
console.log(chalk3.gray("Opening editor..."));
|
|
835
|
-
const { execSync:
|
|
943
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
836
944
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
837
945
|
const fs2 = await import("fs");
|
|
838
946
|
const os = await import("os");
|
|
839
947
|
const path2 = await import("path");
|
|
840
948
|
const tmpFile = path2.join(os.tmpdir(), "gut-commit-msg.txt");
|
|
841
949
|
fs2.writeFileSync(tmpFile, message);
|
|
842
|
-
|
|
950
|
+
execSync5(`${editor} "${tmpFile}"`, { stdio: "inherit" });
|
|
843
951
|
const editedMessage = fs2.readFileSync(tmpFile, "utf-8").trim();
|
|
844
952
|
fs2.unlinkSync(tmpFile);
|
|
845
953
|
if (editedMessage) {
|
|
@@ -866,8 +974,8 @@ import { Command as Command4 } from "commander";
|
|
|
866
974
|
import chalk4 from "chalk";
|
|
867
975
|
import ora3 from "ora";
|
|
868
976
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
869
|
-
import { existsSync as
|
|
870
|
-
import { join as
|
|
977
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
978
|
+
import { join as join3 } from "path";
|
|
871
979
|
var PR_TEMPLATE_PATHS = [
|
|
872
980
|
".gut/pr-template.md",
|
|
873
981
|
".github/pull_request_template.md",
|
|
@@ -878,9 +986,9 @@ var PR_TEMPLATE_PATHS = [
|
|
|
878
986
|
];
|
|
879
987
|
function findPRTemplate(repoRoot) {
|
|
880
988
|
for (const templatePath of PR_TEMPLATE_PATHS) {
|
|
881
|
-
const fullPath =
|
|
882
|
-
if (
|
|
883
|
-
return
|
|
989
|
+
const fullPath = join3(repoRoot, templatePath);
|
|
990
|
+
if (existsSync3(fullPath)) {
|
|
991
|
+
return readFileSync3(fullPath, "utf-8");
|
|
884
992
|
}
|
|
885
993
|
}
|
|
886
994
|
return null;
|
|
@@ -948,11 +1056,11 @@ var aiPrCommand = new Command4("ai-pr").alias("pr").description("Generate a pull
|
|
|
948
1056
|
console.log(chalk4.gray("\u2500".repeat(50)));
|
|
949
1057
|
if (options.copy) {
|
|
950
1058
|
try {
|
|
951
|
-
const { execSync:
|
|
1059
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
952
1060
|
const fullText = `${title}
|
|
953
1061
|
|
|
954
1062
|
${body}`;
|
|
955
|
-
|
|
1063
|
+
execSync5("pbcopy", { input: fullText });
|
|
956
1064
|
console.log(chalk4.green("\n\u2713 Copied to clipboard"));
|
|
957
1065
|
} catch {
|
|
958
1066
|
console.log(chalk4.yellow("\n\u26A0 Could not copy to clipboard"));
|
|
@@ -971,10 +1079,10 @@ ${body}`;
|
|
|
971
1079
|
if (answer.toLowerCase() === "y") {
|
|
972
1080
|
const createSpinner = ora3("Creating PR...").start();
|
|
973
1081
|
try {
|
|
974
|
-
const { execSync:
|
|
1082
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
975
1083
|
const escapedTitle = title.replace(/"/g, '\\"');
|
|
976
1084
|
const escapedBody = body.replace(/"/g, '\\"');
|
|
977
|
-
|
|
1085
|
+
execSync5(
|
|
978
1086
|
`gh pr create --title "${escapedTitle}" --body "${escapedBody}" --base ${baseBranch}`,
|
|
979
1087
|
{ stdio: "pipe" }
|
|
980
1088
|
);
|
|
@@ -1002,11 +1110,11 @@ import { Command as Command5 } from "commander";
|
|
|
1002
1110
|
import chalk5 from "chalk";
|
|
1003
1111
|
import ora4 from "ora";
|
|
1004
1112
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
1005
|
-
import { execSync } from "child_process";
|
|
1113
|
+
import { execSync as execSync2 } from "child_process";
|
|
1006
1114
|
async function getPRDiff(prNumber) {
|
|
1007
1115
|
try {
|
|
1008
|
-
const diff =
|
|
1009
|
-
const prJsonStr =
|
|
1116
|
+
const diff = execSync2(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
1117
|
+
const prJsonStr = execSync2(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
|
|
1010
1118
|
const prJson = JSON.parse(prJsonStr);
|
|
1011
1119
|
return {
|
|
1012
1120
|
diff,
|
|
@@ -1258,8 +1366,8 @@ import { Command as Command7 } from "commander";
|
|
|
1258
1366
|
import chalk7 from "chalk";
|
|
1259
1367
|
import ora6 from "ora";
|
|
1260
1368
|
import { simpleGit as simpleGit6 } from "simple-git";
|
|
1261
|
-
import { existsSync as
|
|
1262
|
-
import { join as
|
|
1369
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
1370
|
+
import { join as join5 } from "path";
|
|
1263
1371
|
var CHANGELOG_PATHS = [
|
|
1264
1372
|
".gut/changelog-template.md",
|
|
1265
1373
|
".gut/CHANGELOG.md",
|
|
@@ -1271,9 +1379,9 @@ var CHANGELOG_PATHS = [
|
|
|
1271
1379
|
];
|
|
1272
1380
|
function findChangelog(repoRoot) {
|
|
1273
1381
|
for (const changelogPath of CHANGELOG_PATHS) {
|
|
1274
|
-
const fullPath =
|
|
1275
|
-
if (
|
|
1276
|
-
return
|
|
1382
|
+
const fullPath = join5(repoRoot, changelogPath);
|
|
1383
|
+
if (existsSync5(fullPath)) {
|
|
1384
|
+
return readFileSync5(fullPath, "utf-8");
|
|
1277
1385
|
}
|
|
1278
1386
|
}
|
|
1279
1387
|
return null;
|
|
@@ -1373,17 +1481,17 @@ import { Command as Command8 } from "commander";
|
|
|
1373
1481
|
import chalk8 from "chalk";
|
|
1374
1482
|
import ora7 from "ora";
|
|
1375
1483
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
1376
|
-
import { execSync as
|
|
1377
|
-
import { existsSync as
|
|
1378
|
-
import { join as
|
|
1484
|
+
import { execSync as execSync3 } from "child_process";
|
|
1485
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
1486
|
+
import { join as join6 } from "path";
|
|
1379
1487
|
var CONTEXT_PATHS = [
|
|
1380
1488
|
".gut/explain.md"
|
|
1381
1489
|
];
|
|
1382
1490
|
function findExplainContext(repoRoot) {
|
|
1383
1491
|
for (const contextPath of CONTEXT_PATHS) {
|
|
1384
|
-
const fullPath =
|
|
1385
|
-
if (
|
|
1386
|
-
return
|
|
1492
|
+
const fullPath = join6(repoRoot, contextPath);
|
|
1493
|
+
if (existsSync6(fullPath)) {
|
|
1494
|
+
return readFileSync6(fullPath, "utf-8");
|
|
1387
1495
|
}
|
|
1388
1496
|
}
|
|
1389
1497
|
return null;
|
|
@@ -1408,7 +1516,7 @@ var aiExplainCommand = new Command8("ai-explain").alias("explain").description("
|
|
|
1408
1516
|
}
|
|
1409
1517
|
} else {
|
|
1410
1518
|
const isPR = target.match(/^#?\d+$/) || target.includes("/pull/");
|
|
1411
|
-
const isFile =
|
|
1519
|
+
const isFile = existsSync6(target);
|
|
1412
1520
|
if (isPR) {
|
|
1413
1521
|
context = await getPRContext(target, spinner);
|
|
1414
1522
|
} else if (isFile) {
|
|
@@ -1491,7 +1599,7 @@ async function getCommitContext(hash, git, spinner) {
|
|
|
1491
1599
|
}
|
|
1492
1600
|
async function getFileContentContext(filePath, spinner) {
|
|
1493
1601
|
spinner.text = `Reading ${filePath}...`;
|
|
1494
|
-
const content =
|
|
1602
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
1495
1603
|
return {
|
|
1496
1604
|
type: "file-content",
|
|
1497
1605
|
title: filePath,
|
|
@@ -1542,7 +1650,7 @@ async function getPRContext(target, spinner) {
|
|
|
1542
1650
|
spinner.text = `Fetching PR #${prNumber}...`;
|
|
1543
1651
|
let prInfo;
|
|
1544
1652
|
try {
|
|
1545
|
-
const prJson =
|
|
1653
|
+
const prJson = execSync3(
|
|
1546
1654
|
`gh pr view ${prNumber} --json title,url,baseRefName,headRefName,commits`,
|
|
1547
1655
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1548
1656
|
);
|
|
@@ -1553,7 +1661,7 @@ async function getPRContext(target, spinner) {
|
|
|
1553
1661
|
spinner.text = `Getting diff for PR #${prNumber}...`;
|
|
1554
1662
|
let diff;
|
|
1555
1663
|
try {
|
|
1556
|
-
diff =
|
|
1664
|
+
diff = execSync3(`gh pr diff ${prNumber}`, {
|
|
1557
1665
|
encoding: "utf-8",
|
|
1558
1666
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1559
1667
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1620,14 +1728,14 @@ import { Command as Command9 } from "commander";
|
|
|
1620
1728
|
import chalk9 from "chalk";
|
|
1621
1729
|
import ora8 from "ora";
|
|
1622
1730
|
import { simpleGit as simpleGit8 } from "simple-git";
|
|
1623
|
-
import { existsSync as
|
|
1624
|
-
import { join as
|
|
1731
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1732
|
+
import { join as join7 } from "path";
|
|
1625
1733
|
var CONTEXT_PATHS2 = [".gut/find.md"];
|
|
1626
1734
|
function findProjectContext(repoRoot) {
|
|
1627
1735
|
for (const contextPath of CONTEXT_PATHS2) {
|
|
1628
|
-
const fullPath =
|
|
1629
|
-
if (
|
|
1630
|
-
return
|
|
1736
|
+
const fullPath = join7(repoRoot, contextPath);
|
|
1737
|
+
if (existsSync7(fullPath)) {
|
|
1738
|
+
return readFileSync7(fullPath, "utf-8");
|
|
1631
1739
|
}
|
|
1632
1740
|
}
|
|
1633
1741
|
return null;
|
|
@@ -1730,22 +1838,22 @@ import { Command as Command10 } from "commander";
|
|
|
1730
1838
|
import chalk10 from "chalk";
|
|
1731
1839
|
import ora9 from "ora";
|
|
1732
1840
|
import { simpleGit as simpleGit9 } from "simple-git";
|
|
1733
|
-
import { existsSync as
|
|
1734
|
-
import { join as
|
|
1735
|
-
import { execSync as
|
|
1841
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
1842
|
+
import { join as join8 } from "path";
|
|
1843
|
+
import { execSync as execSync4 } from "child_process";
|
|
1736
1844
|
var CONVENTION_PATHS2 = [".gut/branch-convention.md", ".github/branch-convention.md"];
|
|
1737
1845
|
function findBranchConvention(repoRoot) {
|
|
1738
1846
|
for (const conventionPath of CONVENTION_PATHS2) {
|
|
1739
|
-
const fullPath =
|
|
1740
|
-
if (
|
|
1741
|
-
return
|
|
1847
|
+
const fullPath = join8(repoRoot, conventionPath);
|
|
1848
|
+
if (existsSync8(fullPath)) {
|
|
1849
|
+
return readFileSync8(fullPath, "utf-8");
|
|
1742
1850
|
}
|
|
1743
1851
|
}
|
|
1744
1852
|
return null;
|
|
1745
1853
|
}
|
|
1746
1854
|
function getIssueInfo(issueNumber) {
|
|
1747
1855
|
try {
|
|
1748
|
-
const result =
|
|
1856
|
+
const result = execSync4(`gh issue view ${issueNumber} --json title,body`, {
|
|
1749
1857
|
encoding: "utf-8",
|
|
1750
1858
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1751
1859
|
});
|
|
@@ -1840,7 +1948,7 @@ import { Command as Command11 } from "commander";
|
|
|
1840
1948
|
import chalk11 from "chalk";
|
|
1841
1949
|
import ora10 from "ora";
|
|
1842
1950
|
import { simpleGit as simpleGit10 } from "simple-git";
|
|
1843
|
-
var syncCommand = new Command11("sync").description("Sync current branch with remote (fetch + rebase/merge)").option("-m, --merge", "Use merge instead of rebase").option("-
|
|
1951
|
+
var syncCommand = new Command11("sync").description("Sync current branch with remote (fetch + rebase/merge)").option("-m, --merge", "Use merge instead of rebase").option("--no-push", "Skip push after syncing").option("--stash", "Auto-stash changes before sync").option("-f, --force", "Force sync even with uncommitted changes").action(async (options) => {
|
|
1844
1952
|
const git = simpleGit10();
|
|
1845
1953
|
const isRepo = await git.checkIsRepo();
|
|
1846
1954
|
if (!isRepo) {
|
|
@@ -1913,12 +2021,23 @@ To set upstream: git push -u origin ${currentBranch}`));
|
|
|
1913
2021
|
const ahead = newStatus.ahead || 0;
|
|
1914
2022
|
const behind = newStatus.behind || 0;
|
|
1915
2023
|
spinner.succeed(chalk11.green("Synced successfully"));
|
|
1916
|
-
if (ahead > 0) {
|
|
1917
|
-
console.log(chalk11.cyan(` \u2191 ${ahead} commit(s) ahead - run 'git push' to publish`));
|
|
1918
|
-
}
|
|
1919
2024
|
if (behind > 0) {
|
|
1920
2025
|
console.log(chalk11.yellow(` \u2193 ${behind} commit(s) behind`));
|
|
1921
2026
|
}
|
|
2027
|
+
if (ahead > 0) {
|
|
2028
|
+
if (options.push !== false) {
|
|
2029
|
+
const pushSpinner = ora10("Pushing to remote...").start();
|
|
2030
|
+
try {
|
|
2031
|
+
await git.push();
|
|
2032
|
+
pushSpinner.succeed(chalk11.green(`Pushed ${ahead} commit(s)`));
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
pushSpinner.fail("Push failed");
|
|
2035
|
+
console.error(chalk11.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2036
|
+
}
|
|
2037
|
+
} else {
|
|
2038
|
+
console.log(chalk11.cyan(` \u2191 ${ahead} commit(s) ahead`));
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1922
2041
|
if (stashed) {
|
|
1923
2042
|
spinner.start("Restoring stashed changes...");
|
|
1924
2043
|
try {
|
|
@@ -2042,8 +2161,87 @@ var stashCommand = new Command12("stash").description("Stash changes with AI-gen
|
|
|
2042
2161
|
console.log(chalk12.green(`\u2713 Stashed: ${stashName}`));
|
|
2043
2162
|
});
|
|
2044
2163
|
|
|
2164
|
+
// src/commands/config.ts
|
|
2165
|
+
import { Command as Command13 } from "commander";
|
|
2166
|
+
import chalk13 from "chalk";
|
|
2167
|
+
var configCommand = new Command13("config").description("Manage gut configuration");
|
|
2168
|
+
configCommand.command("set <key> <value>").description("Set a configuration value").option("--local", "Set for current repository only").action((key, value, options) => {
|
|
2169
|
+
if (key === "lang") {
|
|
2170
|
+
if (!isValidLanguage(value)) {
|
|
2171
|
+
console.error(chalk13.red(`Invalid language: ${value}`));
|
|
2172
|
+
console.error(chalk13.gray(`Valid languages: ${VALID_LANGUAGES.join(", ")}`));
|
|
2173
|
+
process.exit(1);
|
|
2174
|
+
}
|
|
2175
|
+
try {
|
|
2176
|
+
setLanguage(value, options.local ?? false);
|
|
2177
|
+
const scope = options.local ? "(local)" : "(global)";
|
|
2178
|
+
console.log(chalk13.green(`\u2713 Language set to: ${value} ${scope}`));
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
console.error(chalk13.red(err.message));
|
|
2181
|
+
process.exit(1);
|
|
2182
|
+
}
|
|
2183
|
+
} else {
|
|
2184
|
+
console.error(chalk13.red(`Unknown config key: ${key}`));
|
|
2185
|
+
console.error(chalk13.gray("Available keys: lang"));
|
|
2186
|
+
process.exit(1);
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
configCommand.command("get <key>").description("Get a configuration value").action((key) => {
|
|
2190
|
+
const config = getConfig();
|
|
2191
|
+
if (key in config) {
|
|
2192
|
+
console.log(config[key]);
|
|
2193
|
+
} else {
|
|
2194
|
+
console.error(chalk13.red(`Unknown config key: ${key}`));
|
|
2195
|
+
process.exit(1);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
configCommand.command("list").description("List all configuration values").action(() => {
|
|
2199
|
+
const globalConfig = getGlobalConfig();
|
|
2200
|
+
const localConfig = getLocalConfig();
|
|
2201
|
+
const effectiveConfig = getConfig();
|
|
2202
|
+
console.log(chalk13.bold("Configuration:"));
|
|
2203
|
+
console.log();
|
|
2204
|
+
for (const key of Object.keys(effectiveConfig)) {
|
|
2205
|
+
const value = effectiveConfig[key];
|
|
2206
|
+
const isLocal = key in localConfig;
|
|
2207
|
+
const scope = isLocal ? chalk13.cyan(" (local)") : chalk13.gray(" (global)");
|
|
2208
|
+
console.log(` ${chalk13.cyan(key)}: ${value}${scope}`);
|
|
2209
|
+
}
|
|
2210
|
+
if (Object.keys(localConfig).length > 0) {
|
|
2211
|
+
console.log();
|
|
2212
|
+
console.log(chalk13.gray("Local config: .gut/config.json"));
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
|
|
2216
|
+
// src/commands/lang.ts
|
|
2217
|
+
import { Command as Command14 } from "commander";
|
|
2218
|
+
import chalk14 from "chalk";
|
|
2219
|
+
var langCommand = new Command14("lang").description("Set or show output language").argument("[language]", `Language to set (${VALID_LANGUAGES.join(", ")})`).option("--local", "Set for current repository only").action((language, options) => {
|
|
2220
|
+
if (!language) {
|
|
2221
|
+
const lang = getLanguage();
|
|
2222
|
+
const localConfig = getLocalConfig();
|
|
2223
|
+
const isLocal = "lang" in localConfig;
|
|
2224
|
+
const scope = isLocal ? chalk14.cyan("(local)") : chalk14.gray("(global)");
|
|
2225
|
+
console.log(`${lang} ${scope}`);
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
if (!isValidLanguage(language)) {
|
|
2229
|
+
console.error(chalk14.red(`Invalid language: ${language}`));
|
|
2230
|
+
console.error(chalk14.gray(`Valid languages: ${VALID_LANGUAGES.join(", ")}`));
|
|
2231
|
+
process.exit(1);
|
|
2232
|
+
}
|
|
2233
|
+
try {
|
|
2234
|
+
setLanguage(language, options.local ?? false);
|
|
2235
|
+
const scope = options.local ? "(local)" : "(global)";
|
|
2236
|
+
console.log(chalk14.green(`\u2713 Language set to: ${language} ${scope}`));
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
console.error(chalk14.red(err.message));
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
|
|
2045
2243
|
// src/index.ts
|
|
2046
|
-
var program = new
|
|
2244
|
+
var program = new Command15();
|
|
2047
2245
|
program.name("gut").description("Git Utility Tool - AI-powered git commands").version("0.1.0");
|
|
2048
2246
|
program.addCommand(cleanupCommand);
|
|
2049
2247
|
program.addCommand(authCommand);
|
|
@@ -2057,5 +2255,7 @@ program.addCommand(aiFindCommand);
|
|
|
2057
2255
|
program.addCommand(aiBranchCommand);
|
|
2058
2256
|
program.addCommand(syncCommand);
|
|
2059
2257
|
program.addCommand(stashCommand);
|
|
2258
|
+
program.addCommand(configCommand);
|
|
2259
|
+
program.addCommand(langCommand);
|
|
2060
2260
|
program.parse();
|
|
2061
2261
|
//# sourceMappingURL=index.js.map
|