gut-cli 0.1.19 → 0.1.21
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 -11
- 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 -23
- package/.gut/review.md +1 -14
- package/.gut/stash.md +1 -7
- package/.gut/summary.md +1 -19
- package/dist/index.js +190 -185
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +7 -6
- package/dist/lib/index.js +163 -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,25 @@ 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, outputFormat) {
|
|
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
|
+
const outputSection = outputFormat ? `
|
|
351
|
+
|
|
352
|
+
<output-format>
|
|
353
|
+
${outputFormat}
|
|
354
|
+
</output-format>` : "";
|
|
355
|
+
return contextXml + "<instructions>\n" + template + langInstruction + "\n</instructions>" + outputSection;
|
|
452
356
|
}
|
|
453
357
|
var DEFAULT_MODELS = {
|
|
454
358
|
gemini: "gemini-2.0-flash",
|
|
@@ -496,9 +400,9 @@ async function getModel(options) {
|
|
|
496
400
|
}
|
|
497
401
|
async function generateCommitMessage(diff, options, template) {
|
|
498
402
|
const model = await getModel(options);
|
|
499
|
-
const prompt =
|
|
403
|
+
const prompt = buildPrompt(template, "commit", {
|
|
500
404
|
diff: diff.slice(0, 8e3)
|
|
501
|
-
});
|
|
405
|
+
}, options.language, "Respond with ONLY the commit message, nothing else.");
|
|
502
406
|
const result = await generateText({
|
|
503
407
|
model,
|
|
504
408
|
prompt,
|
|
@@ -508,12 +412,18 @@ async function generateCommitMessage(diff, options, template) {
|
|
|
508
412
|
}
|
|
509
413
|
async function generatePRDescription(context, options, template) {
|
|
510
414
|
const model = await getModel(options);
|
|
511
|
-
const prompt =
|
|
415
|
+
const prompt = buildPrompt(template, "pr", {
|
|
512
416
|
baseBranch: context.baseBranch,
|
|
513
417
|
currentBranch: context.currentBranch,
|
|
514
418
|
commits: context.commits.map((c) => `- ${c}`).join("\n"),
|
|
515
419
|
diff: context.diff.slice(0, 6e3)
|
|
516
|
-
}
|
|
420
|
+
}, options.language, `Respond in JSON format:
|
|
421
|
+
\`\`\`json
|
|
422
|
+
{
|
|
423
|
+
"title": "...",
|
|
424
|
+
"body": "..."
|
|
425
|
+
}
|
|
426
|
+
\`\`\``);
|
|
517
427
|
const result = await generateText({
|
|
518
428
|
model,
|
|
519
429
|
prompt,
|
|
@@ -544,9 +454,9 @@ var CodeReviewSchema = z.object({
|
|
|
544
454
|
});
|
|
545
455
|
async function generateCodeReview(diff, options, template) {
|
|
546
456
|
const model = await getModel(options);
|
|
547
|
-
const prompt =
|
|
457
|
+
const prompt = buildPrompt(template, "review", {
|
|
548
458
|
diff: diff.slice(0, 1e4)
|
|
549
|
-
});
|
|
459
|
+
}, options.language);
|
|
550
460
|
const result = await generateObject({
|
|
551
461
|
model,
|
|
552
462
|
schema: CodeReviewSchema,
|
|
@@ -568,13 +478,13 @@ var ChangelogSchema = z.object({
|
|
|
568
478
|
async function generateChangelog(context, options, template) {
|
|
569
479
|
const model = await getModel(options);
|
|
570
480
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
571
|
-
const prompt =
|
|
481
|
+
const prompt = buildPrompt(template, "changelog", {
|
|
572
482
|
fromRef: context.fromRef,
|
|
573
483
|
toRef: context.toRef,
|
|
574
484
|
commits: commitList,
|
|
575
485
|
diff: context.diff.slice(0, 8e3),
|
|
576
486
|
todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
577
|
-
});
|
|
487
|
+
}, options.language);
|
|
578
488
|
const result = await generateObject({
|
|
579
489
|
model,
|
|
580
490
|
schema: ChangelogSchema,
|
|
@@ -602,10 +512,10 @@ var ExplanationSchema = z.object({
|
|
|
602
512
|
async function generateExplanation(context, options, template) {
|
|
603
513
|
const model = await getModel(options);
|
|
604
514
|
if (context.type === "file-content") {
|
|
605
|
-
const prompt2 =
|
|
515
|
+
const prompt2 = buildPrompt(template, "explain-file", {
|
|
606
516
|
filePath: context.metadata.filePath || "",
|
|
607
517
|
content: context.content?.slice(0, 15e3) || ""
|
|
608
|
-
});
|
|
518
|
+
}, options.language);
|
|
609
519
|
const result2 = await generateObject({
|
|
610
520
|
model,
|
|
611
521
|
schema: ExplanationSchema,
|
|
@@ -639,11 +549,11 @@ Author: ${context.metadata.author}
|
|
|
639
549
|
Date: ${context.metadata.date}`;
|
|
640
550
|
targetType = "commit";
|
|
641
551
|
}
|
|
642
|
-
const prompt =
|
|
552
|
+
const prompt = buildPrompt(template, "explain", {
|
|
643
553
|
targetType,
|
|
644
|
-
|
|
554
|
+
contextInfo,
|
|
645
555
|
diff: context.diff?.slice(0, 12e3) || ""
|
|
646
|
-
});
|
|
556
|
+
}, options.language);
|
|
647
557
|
const result = await generateObject({
|
|
648
558
|
model,
|
|
649
559
|
schema: ExplanationSchema,
|
|
@@ -663,11 +573,11 @@ var CommitSearchSchema = z.object({
|
|
|
663
573
|
async function searchCommits(query, commits, options, maxResults = 5, template) {
|
|
664
574
|
const model = await getModel(options);
|
|
665
575
|
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 =
|
|
576
|
+
const prompt = buildPrompt(template, "find", {
|
|
667
577
|
query,
|
|
668
578
|
commits: commitList,
|
|
669
579
|
maxResults: String(maxResults)
|
|
670
|
-
});
|
|
580
|
+
}, options.language);
|
|
671
581
|
const result = await generateObject({
|
|
672
582
|
model,
|
|
673
583
|
schema: CommitSearchSchema,
|
|
@@ -696,11 +606,11 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
696
606
|
}
|
|
697
607
|
async function generateBranchName(description, options, context, template) {
|
|
698
608
|
const model = await getModel(options);
|
|
699
|
-
const prompt =
|
|
609
|
+
const prompt = buildPrompt(template, "branch", {
|
|
700
610
|
description,
|
|
701
611
|
type: context?.type,
|
|
702
612
|
issue: context?.issue
|
|
703
|
-
});
|
|
613
|
+
}, options.language, "Respond with ONLY the branch name, nothing else.");
|
|
704
614
|
const result = await generateText({
|
|
705
615
|
model,
|
|
706
616
|
prompt,
|
|
@@ -710,9 +620,9 @@ async function generateBranchName(description, options, context, template) {
|
|
|
710
620
|
}
|
|
711
621
|
async function generateBranchNameFromDiff(diff, options, template) {
|
|
712
622
|
const model = await getModel(options);
|
|
713
|
-
const prompt =
|
|
623
|
+
const prompt = buildPrompt(template, "checkout", {
|
|
714
624
|
diff: diff.slice(0, 8e3)
|
|
715
|
-
});
|
|
625
|
+
}, options.language, "Respond with ONLY the branch name, nothing else.");
|
|
716
626
|
const result = await generateText({
|
|
717
627
|
model,
|
|
718
628
|
prompt,
|
|
@@ -722,9 +632,9 @@ async function generateBranchNameFromDiff(diff, options, template) {
|
|
|
722
632
|
}
|
|
723
633
|
async function generateStashName(diff, options, template) {
|
|
724
634
|
const model = await getModel(options);
|
|
725
|
-
const prompt =
|
|
635
|
+
const prompt = buildPrompt(template, "stash", {
|
|
726
636
|
diff: diff.slice(0, 4e3)
|
|
727
|
-
});
|
|
637
|
+
}, options.language, "Respond with ONLY the stash name, nothing else.");
|
|
728
638
|
const result = await generateText({
|
|
729
639
|
model,
|
|
730
640
|
prompt,
|
|
@@ -754,13 +664,13 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
754
664
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
|
|
755
665
|
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
666
|
const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
|
|
757
|
-
const prompt =
|
|
667
|
+
const prompt = buildPrompt(template, "summary", {
|
|
758
668
|
author: context.author,
|
|
759
669
|
period,
|
|
760
670
|
format: formatHint,
|
|
761
671
|
commits: commitList,
|
|
762
672
|
diff: context.diff?.slice(0, 6e3)
|
|
763
|
-
});
|
|
673
|
+
}, options.language);
|
|
764
674
|
const result = await generateObject({
|
|
765
675
|
model,
|
|
766
676
|
schema: WorkSummarySchema,
|
|
@@ -776,12 +686,12 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
776
686
|
}
|
|
777
687
|
async function resolveConflict(conflictedContent, context, options, template) {
|
|
778
688
|
const model = await getModel(options);
|
|
779
|
-
const prompt =
|
|
689
|
+
const prompt = buildPrompt(template, "merge", {
|
|
780
690
|
filename: context.filename,
|
|
781
691
|
oursRef: context.oursRef,
|
|
782
692
|
theirsRef: context.theirsRef,
|
|
783
693
|
content: conflictedContent
|
|
784
|
-
});
|
|
694
|
+
}, options.language);
|
|
785
695
|
const result = await generateObject({
|
|
786
696
|
model,
|
|
787
697
|
schema: ConflictResolutionSchema,
|
|
@@ -791,11 +701,11 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
791
701
|
}
|
|
792
702
|
async function generateGitignore(context, options, template) {
|
|
793
703
|
const model = await getModel(options);
|
|
794
|
-
const prompt =
|
|
704
|
+
const prompt = buildPrompt(template, "gitignore", {
|
|
795
705
|
files: context.files,
|
|
796
706
|
configFiles: context.configFiles,
|
|
797
707
|
existingGitignore: context.existingGitignore
|
|
798
|
-
});
|
|
708
|
+
}, options.language, "Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks.");
|
|
799
709
|
const result = await generateText({
|
|
800
710
|
model,
|
|
801
711
|
prompt,
|
|
@@ -901,12 +811,12 @@ import { Command as Command4 } from "commander";
|
|
|
901
811
|
import chalk5 from "chalk";
|
|
902
812
|
import ora3 from "ora";
|
|
903
813
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
904
|
-
import { existsSync as
|
|
905
|
-
import { join as
|
|
906
|
-
import { execSync as
|
|
814
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
815
|
+
import { join as join2 } from "path";
|
|
816
|
+
import { execSync as execSync2 } from "child_process";
|
|
907
817
|
|
|
908
818
|
// src/lib/gh.ts
|
|
909
|
-
import { execSync
|
|
819
|
+
import { execSync } from "child_process";
|
|
910
820
|
import chalk4 from "chalk";
|
|
911
821
|
var ghInstalledCache = null;
|
|
912
822
|
function isGhCliInstalled() {
|
|
@@ -914,7 +824,7 @@ function isGhCliInstalled() {
|
|
|
914
824
|
return ghInstalledCache;
|
|
915
825
|
}
|
|
916
826
|
try {
|
|
917
|
-
|
|
827
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
918
828
|
ghInstalledCache = true;
|
|
919
829
|
return true;
|
|
920
830
|
} catch {
|
|
@@ -946,9 +856,9 @@ var GITHUB_PR_TEMPLATE_PATHS = [
|
|
|
946
856
|
];
|
|
947
857
|
function findPRTemplate(repoRoot) {
|
|
948
858
|
for (const templatePath of GITHUB_PR_TEMPLATE_PATHS) {
|
|
949
|
-
const fullPath =
|
|
950
|
-
if (
|
|
951
|
-
return
|
|
859
|
+
const fullPath = join2(repoRoot, templatePath);
|
|
860
|
+
if (existsSync2(fullPath)) {
|
|
861
|
+
return readFileSync2(fullPath, "utf-8");
|
|
952
862
|
}
|
|
953
863
|
}
|
|
954
864
|
return findTemplate(repoRoot, "pr");
|
|
@@ -1016,7 +926,7 @@ var prCommand = new Command4("pr").description("Generate a pull request title an
|
|
|
1016
926
|
const fullText = `${title}
|
|
1017
927
|
|
|
1018
928
|
${body}`;
|
|
1019
|
-
|
|
929
|
+
execSync2("pbcopy", { input: fullText });
|
|
1020
930
|
console.log(chalk5.green("\n\u2713 Copied to clipboard"));
|
|
1021
931
|
} catch {
|
|
1022
932
|
console.log(chalk5.yellow("\n\u26A0 Could not copy to clipboard"));
|
|
@@ -1047,7 +957,7 @@ ${body}`;
|
|
|
1047
957
|
try {
|
|
1048
958
|
const escapedTitle = title.replace(/"/g, '\\"');
|
|
1049
959
|
const escapedBody = body.replace(/"/g, '\\"');
|
|
1050
|
-
|
|
960
|
+
execSync2(
|
|
1051
961
|
`gh pr create --title "${escapedTitle}" --body "${escapedBody}" --base ${baseBranch}`,
|
|
1052
962
|
{ stdio: "pipe" }
|
|
1053
963
|
);
|
|
@@ -1072,11 +982,11 @@ import { Command as Command5 } from "commander";
|
|
|
1072
982
|
import chalk6 from "chalk";
|
|
1073
983
|
import ora4 from "ora";
|
|
1074
984
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
1075
|
-
import { execSync as
|
|
985
|
+
import { execSync as execSync3 } from "child_process";
|
|
1076
986
|
async function getPRDiff(prNumber) {
|
|
1077
987
|
try {
|
|
1078
|
-
const diff =
|
|
1079
|
-
const prJsonStr =
|
|
988
|
+
const diff = execSync3(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
989
|
+
const prJsonStr = execSync3(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
|
|
1080
990
|
const prJson = JSON.parse(prJsonStr);
|
|
1081
991
|
return {
|
|
1082
992
|
diff,
|
|
@@ -1407,8 +1317,8 @@ import { Command as Command8 } from "commander";
|
|
|
1407
1317
|
import chalk9 from "chalk";
|
|
1408
1318
|
import ora7 from "ora";
|
|
1409
1319
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
1410
|
-
import { execSync as
|
|
1411
|
-
import { existsSync as
|
|
1320
|
+
import { execSync as execSync4 } from "child_process";
|
|
1321
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
1412
1322
|
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
1323
|
const git = simpleGit7();
|
|
1414
1324
|
const isRepo = await git.checkIsRepo();
|
|
@@ -1429,7 +1339,7 @@ var explainCommand = new Command8("explain").description("Get an AI-powered expl
|
|
|
1429
1339
|
}
|
|
1430
1340
|
} else {
|
|
1431
1341
|
const isPR = target.match(/^#?\d+$/) || target.includes("/pull/");
|
|
1432
|
-
const isFile =
|
|
1342
|
+
const isFile = existsSync3(target);
|
|
1433
1343
|
if (isPR) {
|
|
1434
1344
|
spinner.stop();
|
|
1435
1345
|
if (!requireGhCli()) {
|
|
@@ -1520,7 +1430,7 @@ async function getCommitContext(hash, git, spinner) {
|
|
|
1520
1430
|
}
|
|
1521
1431
|
async function getFileContentContext(filePath, spinner) {
|
|
1522
1432
|
spinner.text = `Reading ${filePath}...`;
|
|
1523
|
-
const content =
|
|
1433
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
1524
1434
|
return {
|
|
1525
1435
|
type: "file-content",
|
|
1526
1436
|
title: filePath,
|
|
@@ -1571,7 +1481,7 @@ async function getPRContext(target, spinner) {
|
|
|
1571
1481
|
spinner.text = `Fetching PR #${prNumber}...`;
|
|
1572
1482
|
let prInfo;
|
|
1573
1483
|
try {
|
|
1574
|
-
const prJson =
|
|
1484
|
+
const prJson = execSync4(
|
|
1575
1485
|
`gh pr view ${prNumber} --json title,url,baseRefName,headRefName,commits`,
|
|
1576
1486
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1577
1487
|
);
|
|
@@ -1582,7 +1492,7 @@ async function getPRContext(target, spinner) {
|
|
|
1582
1492
|
spinner.text = `Getting diff for PR #${prNumber}...`;
|
|
1583
1493
|
let diff;
|
|
1584
1494
|
try {
|
|
1585
|
-
diff =
|
|
1495
|
+
diff = execSync4(`gh pr diff ${prNumber}`, {
|
|
1586
1496
|
encoding: "utf-8",
|
|
1587
1497
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1588
1498
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1744,10 +1654,10 @@ import { Command as Command10 } from "commander";
|
|
|
1744
1654
|
import chalk11 from "chalk";
|
|
1745
1655
|
import ora9 from "ora";
|
|
1746
1656
|
import { simpleGit as simpleGit9 } from "simple-git";
|
|
1747
|
-
import { execSync as
|
|
1657
|
+
import { execSync as execSync5 } from "child_process";
|
|
1748
1658
|
function getIssueInfo(issueNumber) {
|
|
1749
1659
|
try {
|
|
1750
|
-
const result =
|
|
1660
|
+
const result = execSync5(`gh issue view ${issueNumber} --json title,body`, {
|
|
1751
1661
|
encoding: "utf-8",
|
|
1752
1662
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1753
1663
|
});
|
|
@@ -1772,9 +1682,10 @@ var branchCommand = new Command10("branch").description("Generate a branch name
|
|
|
1772
1682
|
if (!requireGhCli()) {
|
|
1773
1683
|
process.exit(1);
|
|
1774
1684
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
const
|
|
1685
|
+
const cleanedIssue = issue.replace(/^#/, "");
|
|
1686
|
+
issueNumber = cleanedIssue;
|
|
1687
|
+
const spinner2 = ora9(`Fetching issue #${cleanedIssue}...`).start();
|
|
1688
|
+
const issueInfo = getIssueInfo(cleanedIssue);
|
|
1778
1689
|
if (!issueInfo) {
|
|
1779
1690
|
spinner2.fail(`Could not fetch issue #${issueNumber}`);
|
|
1780
1691
|
console.log(chalk11.gray("Make sure you are authenticated: gh auth login"));
|
|
@@ -2335,6 +2246,99 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
|
2335
2246
|
import { join as join5 } from "path";
|
|
2336
2247
|
import { homedir as homedir3 } from "os";
|
|
2337
2248
|
import { simpleGit as simpleGit14 } from "simple-git";
|
|
2249
|
+
|
|
2250
|
+
// src/lib/config.ts
|
|
2251
|
+
import { homedir as homedir2 } from "os";
|
|
2252
|
+
import { join as join4 } from "path";
|
|
2253
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
2254
|
+
import { execSync as execSync6 } from "child_process";
|
|
2255
|
+
var DEFAULT_CONFIG = {
|
|
2256
|
+
lang: "en"
|
|
2257
|
+
};
|
|
2258
|
+
function getGlobalConfigPath() {
|
|
2259
|
+
const configDir = join4(homedir2(), ".config", "gut");
|
|
2260
|
+
return join4(configDir, "config.json");
|
|
2261
|
+
}
|
|
2262
|
+
function getRepoRoot() {
|
|
2263
|
+
try {
|
|
2264
|
+
return execSync6("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
2265
|
+
} catch {
|
|
2266
|
+
return null;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
function getLocalConfigPath() {
|
|
2270
|
+
const repoRoot = getRepoRoot();
|
|
2271
|
+
if (!repoRoot) return null;
|
|
2272
|
+
return join4(repoRoot, ".gut", "config.json");
|
|
2273
|
+
}
|
|
2274
|
+
function ensureGlobalConfigDir() {
|
|
2275
|
+
const configDir = join4(homedir2(), ".config", "gut");
|
|
2276
|
+
if (!existsSync4(configDir)) {
|
|
2277
|
+
mkdirSync(configDir, { recursive: true });
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function ensureLocalConfigDir() {
|
|
2281
|
+
const repoRoot = getRepoRoot();
|
|
2282
|
+
if (!repoRoot) return;
|
|
2283
|
+
const gutDir = join4(repoRoot, ".gut");
|
|
2284
|
+
if (!existsSync4(gutDir)) {
|
|
2285
|
+
mkdirSync(gutDir, { recursive: true });
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function readConfigFile(path2) {
|
|
2289
|
+
if (!existsSync4(path2)) return {};
|
|
2290
|
+
try {
|
|
2291
|
+
return JSON.parse(readFileSync5(path2, "utf-8"));
|
|
2292
|
+
} catch {
|
|
2293
|
+
return {};
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
function getGlobalConfig() {
|
|
2297
|
+
const globalPath = getGlobalConfigPath();
|
|
2298
|
+
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
2299
|
+
}
|
|
2300
|
+
function getLocalConfig() {
|
|
2301
|
+
const localPath = getLocalConfigPath();
|
|
2302
|
+
if (!localPath) return {};
|
|
2303
|
+
return readConfigFile(localPath);
|
|
2304
|
+
}
|
|
2305
|
+
function getConfig() {
|
|
2306
|
+
const globalConfig = getGlobalConfig();
|
|
2307
|
+
const localConfig = getLocalConfig();
|
|
2308
|
+
return { ...globalConfig, ...localConfig };
|
|
2309
|
+
}
|
|
2310
|
+
function setGlobalConfig(key, value) {
|
|
2311
|
+
ensureGlobalConfigDir();
|
|
2312
|
+
const config = getGlobalConfig();
|
|
2313
|
+
config[key] = value;
|
|
2314
|
+
writeFileSync2(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
2315
|
+
}
|
|
2316
|
+
function setLocalConfig(key, value) {
|
|
2317
|
+
const localPath = getLocalConfigPath();
|
|
2318
|
+
if (!localPath) {
|
|
2319
|
+
throw new Error("Not in a git repository");
|
|
2320
|
+
}
|
|
2321
|
+
ensureLocalConfigDir();
|
|
2322
|
+
const config = getLocalConfig();
|
|
2323
|
+
config[key] = value;
|
|
2324
|
+
writeFileSync2(localPath, JSON.stringify(config, null, 2));
|
|
2325
|
+
}
|
|
2326
|
+
function getLanguage() {
|
|
2327
|
+
return getConfig().lang;
|
|
2328
|
+
}
|
|
2329
|
+
function setLanguage(lang, local = false) {
|
|
2330
|
+
if (local) {
|
|
2331
|
+
setLocalConfig("lang", lang);
|
|
2332
|
+
} else {
|
|
2333
|
+
setGlobalConfig("lang", lang);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
var VALID_LANGUAGES = ["en", "ja"];
|
|
2337
|
+
function isValidLanguage(lang) {
|
|
2338
|
+
return VALID_LANGUAGES.includes(lang);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// src/commands/config.ts
|
|
2338
2342
|
function openFolder(path2) {
|
|
2339
2343
|
const platform = process.platform;
|
|
2340
2344
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? 'start ""' : "xdg-open";
|
|
@@ -2372,7 +2376,6 @@ configCommand.command("get <key>").description("Get a configuration value").acti
|
|
|
2372
2376
|
}
|
|
2373
2377
|
});
|
|
2374
2378
|
configCommand.command("list").description("List all configuration values").action(() => {
|
|
2375
|
-
const globalConfig = getGlobalConfig();
|
|
2376
2379
|
const localConfig = getLocalConfig();
|
|
2377
2380
|
const effectiveConfig = getConfig();
|
|
2378
2381
|
console.log(chalk16.bold("Configuration:"));
|
|
@@ -2508,6 +2511,8 @@ async function translateTemplate(content, targetLang, provider) {
|
|
|
2508
2511
|
model = anthropic(modelName);
|
|
2509
2512
|
break;
|
|
2510
2513
|
}
|
|
2514
|
+
default:
|
|
2515
|
+
throw new Error(`Unsupported provider for translation: ${provider}`);
|
|
2511
2516
|
}
|
|
2512
2517
|
const langNames = {
|
|
2513
2518
|
ja: "Japanese",
|
|
@@ -2624,7 +2629,7 @@ import { Command as Command18 } from "commander";
|
|
|
2624
2629
|
import chalk19 from "chalk";
|
|
2625
2630
|
import ora15 from "ora";
|
|
2626
2631
|
import { simpleGit as simpleGit16 } from "simple-git";
|
|
2627
|
-
import { readdirSync
|
|
2632
|
+
import { readdirSync, readFileSync as readFileSync7, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2628
2633
|
import { join as join7 } from "path";
|
|
2629
2634
|
var CONFIG_FILES = [
|
|
2630
2635
|
// JavaScript/TypeScript
|
|
@@ -2673,7 +2678,7 @@ function getFiles(dir, maxDepth = 3, currentDepth = 0) {
|
|
|
2673
2678
|
if (currentDepth >= maxDepth) return [];
|
|
2674
2679
|
const files = [];
|
|
2675
2680
|
try {
|
|
2676
|
-
const entries =
|
|
2681
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2677
2682
|
for (const entry of entries) {
|
|
2678
2683
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "target" || entry.name === "__pycache__" || entry.name === "venv" || entry.name === ".venv") {
|
|
2679
2684
|
continue;
|
|
@@ -2697,7 +2702,7 @@ function findConfigFiles(repoRoot) {
|
|
|
2697
2702
|
if (configFile.includes("*")) {
|
|
2698
2703
|
const ext = configFile.replace("*", "");
|
|
2699
2704
|
try {
|
|
2700
|
-
const entries =
|
|
2705
|
+
const entries = readdirSync(repoRoot);
|
|
2701
2706
|
for (const entry of entries) {
|
|
2702
2707
|
if (entry.endsWith(ext)) {
|
|
2703
2708
|
const content = readFileSync7(join7(repoRoot, entry), "utf-8");
|