gut-cli 0.1.19 → 0.1.20
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/branch.md +1 -13
- package/.gut/changelog.md +1 -13
- package/.gut/checkout.md +1 -9
- package/.gut/commit.md +1 -7
- package/.gut/explain-file.md +1 -9
- package/.gut/explain.md +1 -11
- package/.gut/find.md +2 -8
- package/.gut/gitignore.md +2 -24
- package/.gut/merge.md +1 -12
- package/.gut/pr.md +1 -13
- package/.gut/review.md +1 -7
- package/.gut/stash.md +1 -7
- package/.gut/summary.md +1 -19
- package/dist/index.js +179 -185
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +7 -6
- package/dist/lib/index.js +152 -151
- package/dist/lib/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -288,147 +288,45 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
288
288
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
289
289
|
import { createOllama } from "ollama-ai-provider";
|
|
290
290
|
import { z } from "zod";
|
|
291
|
-
import { existsSync
|
|
292
|
-
import { join
|
|
293
|
-
import { homedir as homedir2 } from "os";
|
|
294
|
-
import { fileURLToPath } from "url";
|
|
295
|
-
|
|
296
|
-
// src/lib/config.ts
|
|
291
|
+
import { existsSync, readFileSync } from "fs";
|
|
292
|
+
import { join, dirname } from "path";
|
|
297
293
|
import { homedir } from "os";
|
|
298
|
-
import {
|
|
299
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
300
|
-
import { execSync } from "child_process";
|
|
301
|
-
var DEFAULT_CONFIG = {
|
|
302
|
-
lang: "en"
|
|
303
|
-
};
|
|
304
|
-
function getGlobalConfigPath() {
|
|
305
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
306
|
-
return join(configDir, "config.json");
|
|
307
|
-
}
|
|
308
|
-
function getRepoRoot() {
|
|
309
|
-
try {
|
|
310
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
311
|
-
} catch {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
function getLocalConfigPath() {
|
|
316
|
-
const repoRoot = getRepoRoot();
|
|
317
|
-
if (!repoRoot) return null;
|
|
318
|
-
return join(repoRoot, ".gut", "config.json");
|
|
319
|
-
}
|
|
320
|
-
function ensureGlobalConfigDir() {
|
|
321
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
322
|
-
if (!existsSync(configDir)) {
|
|
323
|
-
mkdirSync(configDir, { recursive: true });
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
function ensureLocalConfigDir() {
|
|
327
|
-
const repoRoot = getRepoRoot();
|
|
328
|
-
if (!repoRoot) return;
|
|
329
|
-
const gutDir = join(repoRoot, ".gut");
|
|
330
|
-
if (!existsSync(gutDir)) {
|
|
331
|
-
mkdirSync(gutDir, { recursive: true });
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
function readConfigFile(path2) {
|
|
335
|
-
if (!existsSync(path2)) return {};
|
|
336
|
-
try {
|
|
337
|
-
return JSON.parse(readFileSync(path2, "utf-8"));
|
|
338
|
-
} catch {
|
|
339
|
-
return {};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function getGlobalConfig() {
|
|
343
|
-
const globalPath = getGlobalConfigPath();
|
|
344
|
-
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
345
|
-
}
|
|
346
|
-
function getLocalConfig() {
|
|
347
|
-
const localPath = getLocalConfigPath();
|
|
348
|
-
if (!localPath) return {};
|
|
349
|
-
return readConfigFile(localPath);
|
|
350
|
-
}
|
|
351
|
-
function getConfig() {
|
|
352
|
-
const globalConfig = getGlobalConfig();
|
|
353
|
-
const localConfig = getLocalConfig();
|
|
354
|
-
return { ...globalConfig, ...localConfig };
|
|
355
|
-
}
|
|
356
|
-
function setGlobalConfig(key, value) {
|
|
357
|
-
ensureGlobalConfigDir();
|
|
358
|
-
const config = getGlobalConfig();
|
|
359
|
-
config[key] = value;
|
|
360
|
-
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
361
|
-
}
|
|
362
|
-
function setLocalConfig(key, value) {
|
|
363
|
-
const localPath = getLocalConfigPath();
|
|
364
|
-
if (!localPath) {
|
|
365
|
-
throw new Error("Not in a git repository");
|
|
366
|
-
}
|
|
367
|
-
ensureLocalConfigDir();
|
|
368
|
-
const config = getLocalConfig();
|
|
369
|
-
config[key] = value;
|
|
370
|
-
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
371
|
-
}
|
|
372
|
-
function getLanguage() {
|
|
373
|
-
return getConfig().lang;
|
|
374
|
-
}
|
|
375
|
-
function setLanguage(lang, local = false) {
|
|
376
|
-
if (local) {
|
|
377
|
-
setLocalConfig("lang", lang);
|
|
378
|
-
} else {
|
|
379
|
-
setGlobalConfig("lang", lang);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
function getLanguageInstruction(lang) {
|
|
383
|
-
switch (lang) {
|
|
384
|
-
case "ja":
|
|
385
|
-
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
386
|
-
case "en":
|
|
387
|
-
default:
|
|
388
|
-
return "";
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
var VALID_LANGUAGES = ["en", "ja"];
|
|
392
|
-
function isValidLanguage(lang) {
|
|
393
|
-
return VALID_LANGUAGES.includes(lang);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// src/lib/ai.ts
|
|
294
|
+
import { fileURLToPath } from "url";
|
|
397
295
|
var __filename = fileURLToPath(import.meta.url);
|
|
398
296
|
var __dirname = dirname(__filename);
|
|
399
297
|
function findGutRoot() {
|
|
400
298
|
let current = __dirname;
|
|
401
299
|
for (let i = 0; i < 5; i++) {
|
|
402
|
-
const gutPath =
|
|
403
|
-
if (
|
|
300
|
+
const gutPath = join(current, ".gut");
|
|
301
|
+
if (existsSync(gutPath)) {
|
|
404
302
|
return current;
|
|
405
303
|
}
|
|
406
304
|
current = dirname(current);
|
|
407
305
|
}
|
|
408
|
-
return
|
|
306
|
+
return join(__dirname, "..");
|
|
409
307
|
}
|
|
410
308
|
var GUT_ROOT = findGutRoot();
|
|
411
309
|
function loadTemplate(name) {
|
|
412
|
-
const templatePath =
|
|
413
|
-
if (
|
|
414
|
-
return
|
|
310
|
+
const templatePath = join(GUT_ROOT, ".gut", `${name}.md`);
|
|
311
|
+
if (existsSync(templatePath)) {
|
|
312
|
+
return readFileSync(templatePath, "utf-8");
|
|
415
313
|
}
|
|
416
314
|
throw new Error(`Template not found: ${templatePath}`);
|
|
417
315
|
}
|
|
418
316
|
function getGlobalTemplatesDir() {
|
|
419
|
-
return
|
|
317
|
+
return join(homedir(), ".config", "gut", "templates");
|
|
420
318
|
}
|
|
421
319
|
function findGlobalTemplate(templateName) {
|
|
422
|
-
const templatePath =
|
|
423
|
-
if (
|
|
424
|
-
return
|
|
320
|
+
const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`);
|
|
321
|
+
if (existsSync(templatePath)) {
|
|
322
|
+
return readFileSync(templatePath, "utf-8");
|
|
425
323
|
}
|
|
426
324
|
return null;
|
|
427
325
|
}
|
|
428
326
|
function findTemplate(repoRoot, templateName) {
|
|
429
|
-
const projectTemplatePath =
|
|
430
|
-
if (
|
|
431
|
-
return
|
|
327
|
+
const projectTemplatePath = join(repoRoot, ".gut", `${templateName}.md`);
|
|
328
|
+
if (existsSync(projectTemplatePath)) {
|
|
329
|
+
return readFileSync(projectTemplatePath, "utf-8");
|
|
432
330
|
}
|
|
433
331
|
const globalTemplate = findGlobalTemplate(templateName);
|
|
434
332
|
if (globalTemplate) {
|
|
@@ -436,19 +334,20 @@ function findTemplate(repoRoot, templateName) {
|
|
|
436
334
|
}
|
|
437
335
|
return null;
|
|
438
336
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (langInstruction) {
|
|
449
|
-
result += langInstruction;
|
|
337
|
+
function buildPrompt(userTemplate, templateName, context, language) {
|
|
338
|
+
let contextXml = "<context>\n";
|
|
339
|
+
for (const [key, value] of Object.entries(context)) {
|
|
340
|
+
if (value) {
|
|
341
|
+
contextXml += `<${key}>
|
|
342
|
+
${value}
|
|
343
|
+
</${key}>
|
|
344
|
+
`;
|
|
345
|
+
}
|
|
450
346
|
}
|
|
451
|
-
|
|
347
|
+
contextXml += "</context>\n\n";
|
|
348
|
+
const template = userTemplate || loadTemplate(templateName);
|
|
349
|
+
const langInstruction = language === "ja" ? "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044)." : "";
|
|
350
|
+
return contextXml + "<instructions>\n" + template + langInstruction + "\n</instructions>";
|
|
452
351
|
}
|
|
453
352
|
var DEFAULT_MODELS = {
|
|
454
353
|
gemini: "gemini-2.0-flash",
|
|
@@ -496,9 +395,9 @@ async function getModel(options) {
|
|
|
496
395
|
}
|
|
497
396
|
async function generateCommitMessage(diff, options, template) {
|
|
498
397
|
const model = await getModel(options);
|
|
499
|
-
const prompt =
|
|
398
|
+
const prompt = buildPrompt(template, "commit", {
|
|
500
399
|
diff: diff.slice(0, 8e3)
|
|
501
|
-
});
|
|
400
|
+
}, options.language);
|
|
502
401
|
const result = await generateText({
|
|
503
402
|
model,
|
|
504
403
|
prompt,
|
|
@@ -508,12 +407,12 @@ async function generateCommitMessage(diff, options, template) {
|
|
|
508
407
|
}
|
|
509
408
|
async function generatePRDescription(context, options, template) {
|
|
510
409
|
const model = await getModel(options);
|
|
511
|
-
const prompt =
|
|
410
|
+
const prompt = buildPrompt(template, "pr", {
|
|
512
411
|
baseBranch: context.baseBranch,
|
|
513
412
|
currentBranch: context.currentBranch,
|
|
514
413
|
commits: context.commits.map((c) => `- ${c}`).join("\n"),
|
|
515
414
|
diff: context.diff.slice(0, 6e3)
|
|
516
|
-
});
|
|
415
|
+
}, options.language);
|
|
517
416
|
const result = await generateText({
|
|
518
417
|
model,
|
|
519
418
|
prompt,
|
|
@@ -544,9 +443,9 @@ var CodeReviewSchema = z.object({
|
|
|
544
443
|
});
|
|
545
444
|
async function generateCodeReview(diff, options, template) {
|
|
546
445
|
const model = await getModel(options);
|
|
547
|
-
const prompt =
|
|
446
|
+
const prompt = buildPrompt(template, "review", {
|
|
548
447
|
diff: diff.slice(0, 1e4)
|
|
549
|
-
});
|
|
448
|
+
}, options.language);
|
|
550
449
|
const result = await generateObject({
|
|
551
450
|
model,
|
|
552
451
|
schema: CodeReviewSchema,
|
|
@@ -568,13 +467,13 @@ var ChangelogSchema = z.object({
|
|
|
568
467
|
async function generateChangelog(context, options, template) {
|
|
569
468
|
const model = await getModel(options);
|
|
570
469
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
571
|
-
const prompt =
|
|
470
|
+
const prompt = buildPrompt(template, "changelog", {
|
|
572
471
|
fromRef: context.fromRef,
|
|
573
472
|
toRef: context.toRef,
|
|
574
473
|
commits: commitList,
|
|
575
474
|
diff: context.diff.slice(0, 8e3),
|
|
576
475
|
todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
577
|
-
});
|
|
476
|
+
}, options.language);
|
|
578
477
|
const result = await generateObject({
|
|
579
478
|
model,
|
|
580
479
|
schema: ChangelogSchema,
|
|
@@ -602,10 +501,10 @@ var ExplanationSchema = z.object({
|
|
|
602
501
|
async function generateExplanation(context, options, template) {
|
|
603
502
|
const model = await getModel(options);
|
|
604
503
|
if (context.type === "file-content") {
|
|
605
|
-
const prompt2 =
|
|
504
|
+
const prompt2 = buildPrompt(template, "explain-file", {
|
|
606
505
|
filePath: context.metadata.filePath || "",
|
|
607
506
|
content: context.content?.slice(0, 15e3) || ""
|
|
608
|
-
});
|
|
507
|
+
}, options.language);
|
|
609
508
|
const result2 = await generateObject({
|
|
610
509
|
model,
|
|
611
510
|
schema: ExplanationSchema,
|
|
@@ -639,11 +538,11 @@ Author: ${context.metadata.author}
|
|
|
639
538
|
Date: ${context.metadata.date}`;
|
|
640
539
|
targetType = "commit";
|
|
641
540
|
}
|
|
642
|
-
const prompt =
|
|
541
|
+
const prompt = buildPrompt(template, "explain", {
|
|
643
542
|
targetType,
|
|
644
|
-
|
|
543
|
+
contextInfo,
|
|
645
544
|
diff: context.diff?.slice(0, 12e3) || ""
|
|
646
|
-
});
|
|
545
|
+
}, options.language);
|
|
647
546
|
const result = await generateObject({
|
|
648
547
|
model,
|
|
649
548
|
schema: ExplanationSchema,
|
|
@@ -663,11 +562,11 @@ var CommitSearchSchema = z.object({
|
|
|
663
562
|
async function searchCommits(query, commits, options, maxResults = 5, template) {
|
|
664
563
|
const model = await getModel(options);
|
|
665
564
|
const commitList = commits.map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`).join("\n");
|
|
666
|
-
const prompt =
|
|
565
|
+
const prompt = buildPrompt(template, "find", {
|
|
667
566
|
query,
|
|
668
567
|
commits: commitList,
|
|
669
568
|
maxResults: String(maxResults)
|
|
670
|
-
});
|
|
569
|
+
}, options.language);
|
|
671
570
|
const result = await generateObject({
|
|
672
571
|
model,
|
|
673
572
|
schema: CommitSearchSchema,
|
|
@@ -696,11 +595,11 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
696
595
|
}
|
|
697
596
|
async function generateBranchName(description, options, context, template) {
|
|
698
597
|
const model = await getModel(options);
|
|
699
|
-
const prompt =
|
|
598
|
+
const prompt = buildPrompt(template, "branch", {
|
|
700
599
|
description,
|
|
701
600
|
type: context?.type,
|
|
702
601
|
issue: context?.issue
|
|
703
|
-
});
|
|
602
|
+
}, options.language);
|
|
704
603
|
const result = await generateText({
|
|
705
604
|
model,
|
|
706
605
|
prompt,
|
|
@@ -710,9 +609,9 @@ async function generateBranchName(description, options, context, template) {
|
|
|
710
609
|
}
|
|
711
610
|
async function generateBranchNameFromDiff(diff, options, template) {
|
|
712
611
|
const model = await getModel(options);
|
|
713
|
-
const prompt =
|
|
612
|
+
const prompt = buildPrompt(template, "checkout", {
|
|
714
613
|
diff: diff.slice(0, 8e3)
|
|
715
|
-
});
|
|
614
|
+
}, options.language);
|
|
716
615
|
const result = await generateText({
|
|
717
616
|
model,
|
|
718
617
|
prompt,
|
|
@@ -722,9 +621,9 @@ async function generateBranchNameFromDiff(diff, options, template) {
|
|
|
722
621
|
}
|
|
723
622
|
async function generateStashName(diff, options, template) {
|
|
724
623
|
const model = await getModel(options);
|
|
725
|
-
const prompt =
|
|
624
|
+
const prompt = buildPrompt(template, "stash", {
|
|
726
625
|
diff: diff.slice(0, 4e3)
|
|
727
|
-
});
|
|
626
|
+
}, options.language);
|
|
728
627
|
const result = await generateText({
|
|
729
628
|
model,
|
|
730
629
|
prompt,
|
|
@@ -754,13 +653,13 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
754
653
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
|
|
755
654
|
const formatHint = format === "daily" ? "This is a daily report. Focus on today's accomplishments." : format === "weekly" ? "This is a weekly report. Summarize the week's work at a higher level." : `This is a summary from ${context.since}${context.until ? ` to ${context.until}` : ""}.`;
|
|
756
655
|
const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
|
|
757
|
-
const prompt =
|
|
656
|
+
const prompt = buildPrompt(template, "summary", {
|
|
758
657
|
author: context.author,
|
|
759
658
|
period,
|
|
760
659
|
format: formatHint,
|
|
761
660
|
commits: commitList,
|
|
762
661
|
diff: context.diff?.slice(0, 6e3)
|
|
763
|
-
});
|
|
662
|
+
}, options.language);
|
|
764
663
|
const result = await generateObject({
|
|
765
664
|
model,
|
|
766
665
|
schema: WorkSummarySchema,
|
|
@@ -776,12 +675,12 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
776
675
|
}
|
|
777
676
|
async function resolveConflict(conflictedContent, context, options, template) {
|
|
778
677
|
const model = await getModel(options);
|
|
779
|
-
const prompt =
|
|
678
|
+
const prompt = buildPrompt(template, "merge", {
|
|
780
679
|
filename: context.filename,
|
|
781
680
|
oursRef: context.oursRef,
|
|
782
681
|
theirsRef: context.theirsRef,
|
|
783
682
|
content: conflictedContent
|
|
784
|
-
});
|
|
683
|
+
}, options.language);
|
|
785
684
|
const result = await generateObject({
|
|
786
685
|
model,
|
|
787
686
|
schema: ConflictResolutionSchema,
|
|
@@ -791,11 +690,11 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
791
690
|
}
|
|
792
691
|
async function generateGitignore(context, options, template) {
|
|
793
692
|
const model = await getModel(options);
|
|
794
|
-
const prompt =
|
|
693
|
+
const prompt = buildPrompt(template, "gitignore", {
|
|
795
694
|
files: context.files,
|
|
796
695
|
configFiles: context.configFiles,
|
|
797
696
|
existingGitignore: context.existingGitignore
|
|
798
|
-
});
|
|
697
|
+
}, options.language);
|
|
799
698
|
const result = await generateText({
|
|
800
699
|
model,
|
|
801
700
|
prompt,
|
|
@@ -901,12 +800,12 @@ import { Command as Command4 } from "commander";
|
|
|
901
800
|
import chalk5 from "chalk";
|
|
902
801
|
import ora3 from "ora";
|
|
903
802
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
904
|
-
import { existsSync as
|
|
905
|
-
import { join as
|
|
906
|
-
import { execSync as
|
|
803
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
804
|
+
import { join as join2 } from "path";
|
|
805
|
+
import { execSync as execSync2 } from "child_process";
|
|
907
806
|
|
|
908
807
|
// src/lib/gh.ts
|
|
909
|
-
import { execSync
|
|
808
|
+
import { execSync } from "child_process";
|
|
910
809
|
import chalk4 from "chalk";
|
|
911
810
|
var ghInstalledCache = null;
|
|
912
811
|
function isGhCliInstalled() {
|
|
@@ -914,7 +813,7 @@ function isGhCliInstalled() {
|
|
|
914
813
|
return ghInstalledCache;
|
|
915
814
|
}
|
|
916
815
|
try {
|
|
917
|
-
|
|
816
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
918
817
|
ghInstalledCache = true;
|
|
919
818
|
return true;
|
|
920
819
|
} catch {
|
|
@@ -946,9 +845,9 @@ var GITHUB_PR_TEMPLATE_PATHS = [
|
|
|
946
845
|
];
|
|
947
846
|
function findPRTemplate(repoRoot) {
|
|
948
847
|
for (const templatePath of GITHUB_PR_TEMPLATE_PATHS) {
|
|
949
|
-
const fullPath =
|
|
950
|
-
if (
|
|
951
|
-
return
|
|
848
|
+
const fullPath = join2(repoRoot, templatePath);
|
|
849
|
+
if (existsSync2(fullPath)) {
|
|
850
|
+
return readFileSync2(fullPath, "utf-8");
|
|
952
851
|
}
|
|
953
852
|
}
|
|
954
853
|
return findTemplate(repoRoot, "pr");
|
|
@@ -1016,7 +915,7 @@ var prCommand = new Command4("pr").description("Generate a pull request title an
|
|
|
1016
915
|
const fullText = `${title}
|
|
1017
916
|
|
|
1018
917
|
${body}`;
|
|
1019
|
-
|
|
918
|
+
execSync2("pbcopy", { input: fullText });
|
|
1020
919
|
console.log(chalk5.green("\n\u2713 Copied to clipboard"));
|
|
1021
920
|
} catch {
|
|
1022
921
|
console.log(chalk5.yellow("\n\u26A0 Could not copy to clipboard"));
|
|
@@ -1047,7 +946,7 @@ ${body}`;
|
|
|
1047
946
|
try {
|
|
1048
947
|
const escapedTitle = title.replace(/"/g, '\\"');
|
|
1049
948
|
const escapedBody = body.replace(/"/g, '\\"');
|
|
1050
|
-
|
|
949
|
+
execSync2(
|
|
1051
950
|
`gh pr create --title "${escapedTitle}" --body "${escapedBody}" --base ${baseBranch}`,
|
|
1052
951
|
{ stdio: "pipe" }
|
|
1053
952
|
);
|
|
@@ -1072,11 +971,11 @@ import { Command as Command5 } from "commander";
|
|
|
1072
971
|
import chalk6 from "chalk";
|
|
1073
972
|
import ora4 from "ora";
|
|
1074
973
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
1075
|
-
import { execSync as
|
|
974
|
+
import { execSync as execSync3 } from "child_process";
|
|
1076
975
|
async function getPRDiff(prNumber) {
|
|
1077
976
|
try {
|
|
1078
|
-
const diff =
|
|
1079
|
-
const prJsonStr =
|
|
977
|
+
const diff = execSync3(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
978
|
+
const prJsonStr = execSync3(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
|
|
1080
979
|
const prJson = JSON.parse(prJsonStr);
|
|
1081
980
|
return {
|
|
1082
981
|
diff,
|
|
@@ -1407,8 +1306,8 @@ import { Command as Command8 } from "commander";
|
|
|
1407
1306
|
import chalk9 from "chalk";
|
|
1408
1307
|
import ora7 from "ora";
|
|
1409
1308
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
1410
|
-
import { execSync as
|
|
1411
|
-
import { existsSync as
|
|
1309
|
+
import { execSync as execSync4 } from "child_process";
|
|
1310
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
1412
1311
|
var explainCommand = new Command8("explain").description("Get an AI-powered explanation of changes, commits, PRs, or files").argument("[target]", "Commit hash, PR number, PR URL, or file path (default: uncommitted changes)").option("-p, --provider <provider>", "AI provider (gemini, openai, anthropic)", "gemini").option("-m, --model <model>", "Model to use (provider-specific)").option("-s, --staged", "Explain only staged changes").option("-n, --commits <n>", "Number of commits to analyze for file history (default: 1)", "1").option("--history", "Explain file change history instead of content").option("--json", "Output as JSON").action(async (target, options) => {
|
|
1413
1312
|
const git = simpleGit7();
|
|
1414
1313
|
const isRepo = await git.checkIsRepo();
|
|
@@ -1429,7 +1328,7 @@ var explainCommand = new Command8("explain").description("Get an AI-powered expl
|
|
|
1429
1328
|
}
|
|
1430
1329
|
} else {
|
|
1431
1330
|
const isPR = target.match(/^#?\d+$/) || target.includes("/pull/");
|
|
1432
|
-
const isFile =
|
|
1331
|
+
const isFile = existsSync3(target);
|
|
1433
1332
|
if (isPR) {
|
|
1434
1333
|
spinner.stop();
|
|
1435
1334
|
if (!requireGhCli()) {
|
|
@@ -1520,7 +1419,7 @@ async function getCommitContext(hash, git, spinner) {
|
|
|
1520
1419
|
}
|
|
1521
1420
|
async function getFileContentContext(filePath, spinner) {
|
|
1522
1421
|
spinner.text = `Reading ${filePath}...`;
|
|
1523
|
-
const content =
|
|
1422
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
1524
1423
|
return {
|
|
1525
1424
|
type: "file-content",
|
|
1526
1425
|
title: filePath,
|
|
@@ -1571,7 +1470,7 @@ async function getPRContext(target, spinner) {
|
|
|
1571
1470
|
spinner.text = `Fetching PR #${prNumber}...`;
|
|
1572
1471
|
let prInfo;
|
|
1573
1472
|
try {
|
|
1574
|
-
const prJson =
|
|
1473
|
+
const prJson = execSync4(
|
|
1575
1474
|
`gh pr view ${prNumber} --json title,url,baseRefName,headRefName,commits`,
|
|
1576
1475
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1577
1476
|
);
|
|
@@ -1582,7 +1481,7 @@ async function getPRContext(target, spinner) {
|
|
|
1582
1481
|
spinner.text = `Getting diff for PR #${prNumber}...`;
|
|
1583
1482
|
let diff;
|
|
1584
1483
|
try {
|
|
1585
|
-
diff =
|
|
1484
|
+
diff = execSync4(`gh pr diff ${prNumber}`, {
|
|
1586
1485
|
encoding: "utf-8",
|
|
1587
1486
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1588
1487
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1744,10 +1643,10 @@ import { Command as Command10 } from "commander";
|
|
|
1744
1643
|
import chalk11 from "chalk";
|
|
1745
1644
|
import ora9 from "ora";
|
|
1746
1645
|
import { simpleGit as simpleGit9 } from "simple-git";
|
|
1747
|
-
import { execSync as
|
|
1646
|
+
import { execSync as execSync5 } from "child_process";
|
|
1748
1647
|
function getIssueInfo(issueNumber) {
|
|
1749
1648
|
try {
|
|
1750
|
-
const result =
|
|
1649
|
+
const result = execSync5(`gh issue view ${issueNumber} --json title,body`, {
|
|
1751
1650
|
encoding: "utf-8",
|
|
1752
1651
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1753
1652
|
});
|
|
@@ -1772,9 +1671,10 @@ var branchCommand = new Command10("branch").description("Generate a branch name
|
|
|
1772
1671
|
if (!requireGhCli()) {
|
|
1773
1672
|
process.exit(1);
|
|
1774
1673
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
const
|
|
1674
|
+
const cleanedIssue = issue.replace(/^#/, "");
|
|
1675
|
+
issueNumber = cleanedIssue;
|
|
1676
|
+
const spinner2 = ora9(`Fetching issue #${cleanedIssue}...`).start();
|
|
1677
|
+
const issueInfo = getIssueInfo(cleanedIssue);
|
|
1778
1678
|
if (!issueInfo) {
|
|
1779
1679
|
spinner2.fail(`Could not fetch issue #${issueNumber}`);
|
|
1780
1680
|
console.log(chalk11.gray("Make sure you are authenticated: gh auth login"));
|
|
@@ -2335,6 +2235,99 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
|
2335
2235
|
import { join as join5 } from "path";
|
|
2336
2236
|
import { homedir as homedir3 } from "os";
|
|
2337
2237
|
import { simpleGit as simpleGit14 } from "simple-git";
|
|
2238
|
+
|
|
2239
|
+
// src/lib/config.ts
|
|
2240
|
+
import { homedir as homedir2 } from "os";
|
|
2241
|
+
import { join as join4 } from "path";
|
|
2242
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
2243
|
+
import { execSync as execSync6 } from "child_process";
|
|
2244
|
+
var DEFAULT_CONFIG = {
|
|
2245
|
+
lang: "en"
|
|
2246
|
+
};
|
|
2247
|
+
function getGlobalConfigPath() {
|
|
2248
|
+
const configDir = join4(homedir2(), ".config", "gut");
|
|
2249
|
+
return join4(configDir, "config.json");
|
|
2250
|
+
}
|
|
2251
|
+
function getRepoRoot() {
|
|
2252
|
+
try {
|
|
2253
|
+
return execSync6("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2254
|
+
} catch {
|
|
2255
|
+
return null;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function getLocalConfigPath() {
|
|
2259
|
+
const repoRoot = getRepoRoot();
|
|
2260
|
+
if (!repoRoot) return null;
|
|
2261
|
+
return join4(repoRoot, ".gut", "config.json");
|
|
2262
|
+
}
|
|
2263
|
+
function ensureGlobalConfigDir() {
|
|
2264
|
+
const configDir = join4(homedir2(), ".config", "gut");
|
|
2265
|
+
if (!existsSync4(configDir)) {
|
|
2266
|
+
mkdirSync(configDir, { recursive: true });
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
function ensureLocalConfigDir() {
|
|
2270
|
+
const repoRoot = getRepoRoot();
|
|
2271
|
+
if (!repoRoot) return;
|
|
2272
|
+
const gutDir = join4(repoRoot, ".gut");
|
|
2273
|
+
if (!existsSync4(gutDir)) {
|
|
2274
|
+
mkdirSync(gutDir, { recursive: true });
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
function readConfigFile(path2) {
|
|
2278
|
+
if (!existsSync4(path2)) return {};
|
|
2279
|
+
try {
|
|
2280
|
+
return JSON.parse(readFileSync5(path2, "utf-8"));
|
|
2281
|
+
} catch {
|
|
2282
|
+
return {};
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function getGlobalConfig() {
|
|
2286
|
+
const globalPath = getGlobalConfigPath();
|
|
2287
|
+
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
2288
|
+
}
|
|
2289
|
+
function getLocalConfig() {
|
|
2290
|
+
const localPath = getLocalConfigPath();
|
|
2291
|
+
if (!localPath) return {};
|
|
2292
|
+
return readConfigFile(localPath);
|
|
2293
|
+
}
|
|
2294
|
+
function getConfig() {
|
|
2295
|
+
const globalConfig = getGlobalConfig();
|
|
2296
|
+
const localConfig = getLocalConfig();
|
|
2297
|
+
return { ...globalConfig, ...localConfig };
|
|
2298
|
+
}
|
|
2299
|
+
function setGlobalConfig(key, value) {
|
|
2300
|
+
ensureGlobalConfigDir();
|
|
2301
|
+
const config = getGlobalConfig();
|
|
2302
|
+
config[key] = value;
|
|
2303
|
+
writeFileSync2(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
2304
|
+
}
|
|
2305
|
+
function setLocalConfig(key, value) {
|
|
2306
|
+
const localPath = getLocalConfigPath();
|
|
2307
|
+
if (!localPath) {
|
|
2308
|
+
throw new Error("Not in a git repository");
|
|
2309
|
+
}
|
|
2310
|
+
ensureLocalConfigDir();
|
|
2311
|
+
const config = getLocalConfig();
|
|
2312
|
+
config[key] = value;
|
|
2313
|
+
writeFileSync2(localPath, JSON.stringify(config, null, 2));
|
|
2314
|
+
}
|
|
2315
|
+
function getLanguage() {
|
|
2316
|
+
return getConfig().lang;
|
|
2317
|
+
}
|
|
2318
|
+
function setLanguage(lang, local = false) {
|
|
2319
|
+
if (local) {
|
|
2320
|
+
setLocalConfig("lang", lang);
|
|
2321
|
+
} else {
|
|
2322
|
+
setGlobalConfig("lang", lang);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
var VALID_LANGUAGES = ["en", "ja"];
|
|
2326
|
+
function isValidLanguage(lang) {
|
|
2327
|
+
return VALID_LANGUAGES.includes(lang);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// src/commands/config.ts
|
|
2338
2331
|
function openFolder(path2) {
|
|
2339
2332
|
const platform = process.platform;
|
|
2340
2333
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? 'start ""' : "xdg-open";
|
|
@@ -2372,7 +2365,6 @@ configCommand.command("get <key>").description("Get a configuration value").acti
|
|
|
2372
2365
|
}
|
|
2373
2366
|
});
|
|
2374
2367
|
configCommand.command("list").description("List all configuration values").action(() => {
|
|
2375
|
-
const globalConfig = getGlobalConfig();
|
|
2376
2368
|
const localConfig = getLocalConfig();
|
|
2377
2369
|
const effectiveConfig = getConfig();
|
|
2378
2370
|
console.log(chalk16.bold("Configuration:"));
|
|
@@ -2508,6 +2500,8 @@ async function translateTemplate(content, targetLang, provider) {
|
|
|
2508
2500
|
model = anthropic(modelName);
|
|
2509
2501
|
break;
|
|
2510
2502
|
}
|
|
2503
|
+
default:
|
|
2504
|
+
throw new Error(`Unsupported provider for translation: ${provider}`);
|
|
2511
2505
|
}
|
|
2512
2506
|
const langNames = {
|
|
2513
2507
|
ja: "Japanese",
|
|
@@ -2624,7 +2618,7 @@ import { Command as Command18 } from "commander";
|
|
|
2624
2618
|
import chalk19 from "chalk";
|
|
2625
2619
|
import ora15 from "ora";
|
|
2626
2620
|
import { simpleGit as simpleGit16 } from "simple-git";
|
|
2627
|
-
import { readdirSync
|
|
2621
|
+
import { readdirSync, readFileSync as readFileSync7, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2628
2622
|
import { join as join7 } from "path";
|
|
2629
2623
|
var CONFIG_FILES = [
|
|
2630
2624
|
// JavaScript/TypeScript
|
|
@@ -2673,7 +2667,7 @@ function getFiles(dir, maxDepth = 3, currentDepth = 0) {
|
|
|
2673
2667
|
if (currentDepth >= maxDepth) return [];
|
|
2674
2668
|
const files = [];
|
|
2675
2669
|
try {
|
|
2676
|
-
const entries =
|
|
2670
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2677
2671
|
for (const entry of entries) {
|
|
2678
2672
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "target" || entry.name === "__pycache__" || entry.name === "venv" || entry.name === ".venv") {
|
|
2679
2673
|
continue;
|
|
@@ -2697,7 +2691,7 @@ function findConfigFiles(repoRoot) {
|
|
|
2697
2691
|
if (configFile.includes("*")) {
|
|
2698
2692
|
const ext = configFile.replace("*", "");
|
|
2699
2693
|
try {
|
|
2700
|
-
const entries =
|
|
2694
|
+
const entries = readdirSync(repoRoot);
|
|
2701
2695
|
for (const entry of entries) {
|
|
2702
2696
|
if (entry.endsWith(ext)) {
|
|
2703
2697
|
const content = readFileSync7(join7(repoRoot, entry), "utf-8");
|