gut-cli 0.1.18 → 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/README.md +49 -17
- package/dist/index.js +298 -215
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +7 -6
- package/dist/lib/index.js +162 -146
- package/dist/lib/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -288,152 +288,66 @@ 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 { fileURLToPath } from "url";
|
|
294
|
-
|
|
295
|
-
// src/lib/config.ts
|
|
291
|
+
import { existsSync, readFileSync } from "fs";
|
|
292
|
+
import { join, dirname } from "path";
|
|
296
293
|
import { homedir } from "os";
|
|
297
|
-
import {
|
|
298
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
299
|
-
import { execSync } from "child_process";
|
|
300
|
-
var DEFAULT_CONFIG = {
|
|
301
|
-
lang: "en"
|
|
302
|
-
};
|
|
303
|
-
function getGlobalConfigPath() {
|
|
304
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
305
|
-
return join(configDir, "config.json");
|
|
306
|
-
}
|
|
307
|
-
function getRepoRoot() {
|
|
308
|
-
try {
|
|
309
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
310
|
-
} catch {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
function getLocalConfigPath() {
|
|
315
|
-
const repoRoot = getRepoRoot();
|
|
316
|
-
if (!repoRoot) return null;
|
|
317
|
-
return join(repoRoot, ".gut", "config.json");
|
|
318
|
-
}
|
|
319
|
-
function ensureGlobalConfigDir() {
|
|
320
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
321
|
-
if (!existsSync(configDir)) {
|
|
322
|
-
mkdirSync(configDir, { recursive: true });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function ensureLocalConfigDir() {
|
|
326
|
-
const repoRoot = getRepoRoot();
|
|
327
|
-
if (!repoRoot) return;
|
|
328
|
-
const gutDir = join(repoRoot, ".gut");
|
|
329
|
-
if (!existsSync(gutDir)) {
|
|
330
|
-
mkdirSync(gutDir, { recursive: true });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
function readConfigFile(path2) {
|
|
334
|
-
if (!existsSync(path2)) return {};
|
|
335
|
-
try {
|
|
336
|
-
return JSON.parse(readFileSync(path2, "utf-8"));
|
|
337
|
-
} catch {
|
|
338
|
-
return {};
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function getGlobalConfig() {
|
|
342
|
-
const globalPath = getGlobalConfigPath();
|
|
343
|
-
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
344
|
-
}
|
|
345
|
-
function getLocalConfig() {
|
|
346
|
-
const localPath = getLocalConfigPath();
|
|
347
|
-
if (!localPath) return {};
|
|
348
|
-
return readConfigFile(localPath);
|
|
349
|
-
}
|
|
350
|
-
function getConfig() {
|
|
351
|
-
const globalConfig = getGlobalConfig();
|
|
352
|
-
const localConfig = getLocalConfig();
|
|
353
|
-
return { ...globalConfig, ...localConfig };
|
|
354
|
-
}
|
|
355
|
-
function setGlobalConfig(key, value) {
|
|
356
|
-
ensureGlobalConfigDir();
|
|
357
|
-
const config = getGlobalConfig();
|
|
358
|
-
config[key] = value;
|
|
359
|
-
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
360
|
-
}
|
|
361
|
-
function setLocalConfig(key, value) {
|
|
362
|
-
const localPath = getLocalConfigPath();
|
|
363
|
-
if (!localPath) {
|
|
364
|
-
throw new Error("Not in a git repository");
|
|
365
|
-
}
|
|
366
|
-
ensureLocalConfigDir();
|
|
367
|
-
const config = getLocalConfig();
|
|
368
|
-
config[key] = value;
|
|
369
|
-
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
370
|
-
}
|
|
371
|
-
function getLanguage() {
|
|
372
|
-
return getConfig().lang;
|
|
373
|
-
}
|
|
374
|
-
function setLanguage(lang, local = false) {
|
|
375
|
-
if (local) {
|
|
376
|
-
setLocalConfig("lang", lang);
|
|
377
|
-
} else {
|
|
378
|
-
setGlobalConfig("lang", lang);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
function getLanguageInstruction(lang) {
|
|
382
|
-
switch (lang) {
|
|
383
|
-
case "ja":
|
|
384
|
-
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
385
|
-
case "en":
|
|
386
|
-
default:
|
|
387
|
-
return "";
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
var VALID_LANGUAGES = ["en", "ja"];
|
|
391
|
-
function isValidLanguage(lang) {
|
|
392
|
-
return VALID_LANGUAGES.includes(lang);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// src/lib/ai.ts
|
|
294
|
+
import { fileURLToPath } from "url";
|
|
396
295
|
var __filename = fileURLToPath(import.meta.url);
|
|
397
296
|
var __dirname = dirname(__filename);
|
|
398
297
|
function findGutRoot() {
|
|
399
298
|
let current = __dirname;
|
|
400
299
|
for (let i = 0; i < 5; i++) {
|
|
401
|
-
const gutPath =
|
|
402
|
-
if (
|
|
300
|
+
const gutPath = join(current, ".gut");
|
|
301
|
+
if (existsSync(gutPath)) {
|
|
403
302
|
return current;
|
|
404
303
|
}
|
|
405
304
|
current = dirname(current);
|
|
406
305
|
}
|
|
407
|
-
return
|
|
306
|
+
return join(__dirname, "..");
|
|
408
307
|
}
|
|
409
308
|
var GUT_ROOT = findGutRoot();
|
|
410
309
|
function loadTemplate(name) {
|
|
411
|
-
const templatePath =
|
|
412
|
-
if (
|
|
413
|
-
return
|
|
310
|
+
const templatePath = join(GUT_ROOT, ".gut", `${name}.md`);
|
|
311
|
+
if (existsSync(templatePath)) {
|
|
312
|
+
return readFileSync(templatePath, "utf-8");
|
|
414
313
|
}
|
|
415
314
|
throw new Error(`Template not found: ${templatePath}`);
|
|
416
315
|
}
|
|
417
|
-
function
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
316
|
+
function getGlobalTemplatesDir() {
|
|
317
|
+
return join(homedir(), ".config", "gut", "templates");
|
|
318
|
+
}
|
|
319
|
+
function findGlobalTemplate(templateName) {
|
|
320
|
+
const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`);
|
|
321
|
+
if (existsSync(templatePath)) {
|
|
322
|
+
return readFileSync(templatePath, "utf-8");
|
|
421
323
|
}
|
|
422
324
|
return null;
|
|
423
325
|
}
|
|
424
|
-
function
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return variables[key] ? content : "";
|
|
429
|
-
});
|
|
430
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
431
|
-
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value || "");
|
|
326
|
+
function findTemplate(repoRoot, templateName) {
|
|
327
|
+
const projectTemplatePath = join(repoRoot, ".gut", `${templateName}.md`);
|
|
328
|
+
if (existsSync(projectTemplatePath)) {
|
|
329
|
+
return readFileSync(projectTemplatePath, "utf-8");
|
|
432
330
|
}
|
|
433
|
-
|
|
434
|
-
|
|
331
|
+
const globalTemplate = findGlobalTemplate(templateName);
|
|
332
|
+
if (globalTemplate) {
|
|
333
|
+
return globalTemplate;
|
|
435
334
|
}
|
|
436
|
-
return
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
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
|
+
}
|
|
346
|
+
}
|
|
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>";
|
|
437
351
|
}
|
|
438
352
|
var DEFAULT_MODELS = {
|
|
439
353
|
gemini: "gemini-2.0-flash",
|
|
@@ -481,9 +395,9 @@ async function getModel(options) {
|
|
|
481
395
|
}
|
|
482
396
|
async function generateCommitMessage(diff, options, template) {
|
|
483
397
|
const model = await getModel(options);
|
|
484
|
-
const prompt =
|
|
398
|
+
const prompt = buildPrompt(template, "commit", {
|
|
485
399
|
diff: diff.slice(0, 8e3)
|
|
486
|
-
});
|
|
400
|
+
}, options.language);
|
|
487
401
|
const result = await generateText({
|
|
488
402
|
model,
|
|
489
403
|
prompt,
|
|
@@ -493,12 +407,12 @@ async function generateCommitMessage(diff, options, template) {
|
|
|
493
407
|
}
|
|
494
408
|
async function generatePRDescription(context, options, template) {
|
|
495
409
|
const model = await getModel(options);
|
|
496
|
-
const prompt =
|
|
410
|
+
const prompt = buildPrompt(template, "pr", {
|
|
497
411
|
baseBranch: context.baseBranch,
|
|
498
412
|
currentBranch: context.currentBranch,
|
|
499
413
|
commits: context.commits.map((c) => `- ${c}`).join("\n"),
|
|
500
414
|
diff: context.diff.slice(0, 6e3)
|
|
501
|
-
});
|
|
415
|
+
}, options.language);
|
|
502
416
|
const result = await generateText({
|
|
503
417
|
model,
|
|
504
418
|
prompt,
|
|
@@ -529,9 +443,9 @@ var CodeReviewSchema = z.object({
|
|
|
529
443
|
});
|
|
530
444
|
async function generateCodeReview(diff, options, template) {
|
|
531
445
|
const model = await getModel(options);
|
|
532
|
-
const prompt =
|
|
446
|
+
const prompt = buildPrompt(template, "review", {
|
|
533
447
|
diff: diff.slice(0, 1e4)
|
|
534
|
-
});
|
|
448
|
+
}, options.language);
|
|
535
449
|
const result = await generateObject({
|
|
536
450
|
model,
|
|
537
451
|
schema: CodeReviewSchema,
|
|
@@ -553,13 +467,13 @@ var ChangelogSchema = z.object({
|
|
|
553
467
|
async function generateChangelog(context, options, template) {
|
|
554
468
|
const model = await getModel(options);
|
|
555
469
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
556
|
-
const prompt =
|
|
470
|
+
const prompt = buildPrompt(template, "changelog", {
|
|
557
471
|
fromRef: context.fromRef,
|
|
558
472
|
toRef: context.toRef,
|
|
559
473
|
commits: commitList,
|
|
560
474
|
diff: context.diff.slice(0, 8e3),
|
|
561
475
|
todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
562
|
-
});
|
|
476
|
+
}, options.language);
|
|
563
477
|
const result = await generateObject({
|
|
564
478
|
model,
|
|
565
479
|
schema: ChangelogSchema,
|
|
@@ -587,10 +501,10 @@ var ExplanationSchema = z.object({
|
|
|
587
501
|
async function generateExplanation(context, options, template) {
|
|
588
502
|
const model = await getModel(options);
|
|
589
503
|
if (context.type === "file-content") {
|
|
590
|
-
const prompt2 =
|
|
504
|
+
const prompt2 = buildPrompt(template, "explain-file", {
|
|
591
505
|
filePath: context.metadata.filePath || "",
|
|
592
506
|
content: context.content?.slice(0, 15e3) || ""
|
|
593
|
-
});
|
|
507
|
+
}, options.language);
|
|
594
508
|
const result2 = await generateObject({
|
|
595
509
|
model,
|
|
596
510
|
schema: ExplanationSchema,
|
|
@@ -624,11 +538,11 @@ Author: ${context.metadata.author}
|
|
|
624
538
|
Date: ${context.metadata.date}`;
|
|
625
539
|
targetType = "commit";
|
|
626
540
|
}
|
|
627
|
-
const prompt =
|
|
541
|
+
const prompt = buildPrompt(template, "explain", {
|
|
628
542
|
targetType,
|
|
629
|
-
|
|
543
|
+
contextInfo,
|
|
630
544
|
diff: context.diff?.slice(0, 12e3) || ""
|
|
631
|
-
});
|
|
545
|
+
}, options.language);
|
|
632
546
|
const result = await generateObject({
|
|
633
547
|
model,
|
|
634
548
|
schema: ExplanationSchema,
|
|
@@ -648,11 +562,11 @@ var CommitSearchSchema = z.object({
|
|
|
648
562
|
async function searchCommits(query, commits, options, maxResults = 5, template) {
|
|
649
563
|
const model = await getModel(options);
|
|
650
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");
|
|
651
|
-
const prompt =
|
|
565
|
+
const prompt = buildPrompt(template, "find", {
|
|
652
566
|
query,
|
|
653
567
|
commits: commitList,
|
|
654
568
|
maxResults: String(maxResults)
|
|
655
|
-
});
|
|
569
|
+
}, options.language);
|
|
656
570
|
const result = await generateObject({
|
|
657
571
|
model,
|
|
658
572
|
schema: CommitSearchSchema,
|
|
@@ -681,11 +595,11 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
681
595
|
}
|
|
682
596
|
async function generateBranchName(description, options, context, template) {
|
|
683
597
|
const model = await getModel(options);
|
|
684
|
-
const prompt =
|
|
598
|
+
const prompt = buildPrompt(template, "branch", {
|
|
685
599
|
description,
|
|
686
600
|
type: context?.type,
|
|
687
601
|
issue: context?.issue
|
|
688
|
-
});
|
|
602
|
+
}, options.language);
|
|
689
603
|
const result = await generateText({
|
|
690
604
|
model,
|
|
691
605
|
prompt,
|
|
@@ -695,9 +609,9 @@ async function generateBranchName(description, options, context, template) {
|
|
|
695
609
|
}
|
|
696
610
|
async function generateBranchNameFromDiff(diff, options, template) {
|
|
697
611
|
const model = await getModel(options);
|
|
698
|
-
const prompt =
|
|
612
|
+
const prompt = buildPrompt(template, "checkout", {
|
|
699
613
|
diff: diff.slice(0, 8e3)
|
|
700
|
-
});
|
|
614
|
+
}, options.language);
|
|
701
615
|
const result = await generateText({
|
|
702
616
|
model,
|
|
703
617
|
prompt,
|
|
@@ -707,9 +621,9 @@ async function generateBranchNameFromDiff(diff, options, template) {
|
|
|
707
621
|
}
|
|
708
622
|
async function generateStashName(diff, options, template) {
|
|
709
623
|
const model = await getModel(options);
|
|
710
|
-
const prompt =
|
|
624
|
+
const prompt = buildPrompt(template, "stash", {
|
|
711
625
|
diff: diff.slice(0, 4e3)
|
|
712
|
-
});
|
|
626
|
+
}, options.language);
|
|
713
627
|
const result = await generateText({
|
|
714
628
|
model,
|
|
715
629
|
prompt,
|
|
@@ -739,13 +653,13 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
739
653
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
|
|
740
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}` : ""}.`;
|
|
741
655
|
const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
|
|
742
|
-
const prompt =
|
|
656
|
+
const prompt = buildPrompt(template, "summary", {
|
|
743
657
|
author: context.author,
|
|
744
658
|
period,
|
|
745
659
|
format: formatHint,
|
|
746
660
|
commits: commitList,
|
|
747
661
|
diff: context.diff?.slice(0, 6e3)
|
|
748
|
-
});
|
|
662
|
+
}, options.language);
|
|
749
663
|
const result = await generateObject({
|
|
750
664
|
model,
|
|
751
665
|
schema: WorkSummarySchema,
|
|
@@ -761,12 +675,12 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
761
675
|
}
|
|
762
676
|
async function resolveConflict(conflictedContent, context, options, template) {
|
|
763
677
|
const model = await getModel(options);
|
|
764
|
-
const prompt =
|
|
678
|
+
const prompt = buildPrompt(template, "merge", {
|
|
765
679
|
filename: context.filename,
|
|
766
680
|
oursRef: context.oursRef,
|
|
767
681
|
theirsRef: context.theirsRef,
|
|
768
682
|
content: conflictedContent
|
|
769
|
-
});
|
|
683
|
+
}, options.language);
|
|
770
684
|
const result = await generateObject({
|
|
771
685
|
model,
|
|
772
686
|
schema: ConflictResolutionSchema,
|
|
@@ -776,11 +690,11 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
776
690
|
}
|
|
777
691
|
async function generateGitignore(context, options, template) {
|
|
778
692
|
const model = await getModel(options);
|
|
779
|
-
const prompt =
|
|
693
|
+
const prompt = buildPrompt(template, "gitignore", {
|
|
780
694
|
files: context.files,
|
|
781
695
|
configFiles: context.configFiles,
|
|
782
696
|
existingGitignore: context.existingGitignore
|
|
783
|
-
});
|
|
697
|
+
}, options.language);
|
|
784
698
|
const result = await generateText({
|
|
785
699
|
model,
|
|
786
700
|
prompt,
|
|
@@ -852,14 +766,14 @@ var commitCommand = new Command3("commit").description("Generate a commit messag
|
|
|
852
766
|
console.log(chalk3.green("\u2713 Committed successfully"));
|
|
853
767
|
} else if (answer.toLowerCase() === "e") {
|
|
854
768
|
console.log(chalk3.gray("Opening editor..."));
|
|
855
|
-
const { execSync:
|
|
769
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
856
770
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
857
771
|
const fs2 = await import("fs");
|
|
858
772
|
const os = await import("os");
|
|
859
773
|
const path2 = await import("path");
|
|
860
774
|
const tmpFile = path2.join(os.tmpdir(), "gut-commit-msg.txt");
|
|
861
775
|
fs2.writeFileSync(tmpFile, message);
|
|
862
|
-
|
|
776
|
+
execSync9(`${editor} "${tmpFile}"`, { stdio: "inherit" });
|
|
863
777
|
const editedMessage = fs2.readFileSync(tmpFile, "utf-8").trim();
|
|
864
778
|
fs2.unlinkSync(tmpFile);
|
|
865
779
|
if (editedMessage) {
|
|
@@ -886,12 +800,12 @@ import { Command as Command4 } from "commander";
|
|
|
886
800
|
import chalk5 from "chalk";
|
|
887
801
|
import ora3 from "ora";
|
|
888
802
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
889
|
-
import { existsSync as
|
|
890
|
-
import { join as
|
|
891
|
-
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";
|
|
892
806
|
|
|
893
807
|
// src/lib/gh.ts
|
|
894
|
-
import { execSync
|
|
808
|
+
import { execSync } from "child_process";
|
|
895
809
|
import chalk4 from "chalk";
|
|
896
810
|
var ghInstalledCache = null;
|
|
897
811
|
function isGhCliInstalled() {
|
|
@@ -899,7 +813,7 @@ function isGhCliInstalled() {
|
|
|
899
813
|
return ghInstalledCache;
|
|
900
814
|
}
|
|
901
815
|
try {
|
|
902
|
-
|
|
816
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
903
817
|
ghInstalledCache = true;
|
|
904
818
|
return true;
|
|
905
819
|
} catch {
|
|
@@ -931,9 +845,9 @@ var GITHUB_PR_TEMPLATE_PATHS = [
|
|
|
931
845
|
];
|
|
932
846
|
function findPRTemplate(repoRoot) {
|
|
933
847
|
for (const templatePath of GITHUB_PR_TEMPLATE_PATHS) {
|
|
934
|
-
const fullPath =
|
|
935
|
-
if (
|
|
936
|
-
return
|
|
848
|
+
const fullPath = join2(repoRoot, templatePath);
|
|
849
|
+
if (existsSync2(fullPath)) {
|
|
850
|
+
return readFileSync2(fullPath, "utf-8");
|
|
937
851
|
}
|
|
938
852
|
}
|
|
939
853
|
return findTemplate(repoRoot, "pr");
|
|
@@ -1001,7 +915,7 @@ var prCommand = new Command4("pr").description("Generate a pull request title an
|
|
|
1001
915
|
const fullText = `${title}
|
|
1002
916
|
|
|
1003
917
|
${body}`;
|
|
1004
|
-
|
|
918
|
+
execSync2("pbcopy", { input: fullText });
|
|
1005
919
|
console.log(chalk5.green("\n\u2713 Copied to clipboard"));
|
|
1006
920
|
} catch {
|
|
1007
921
|
console.log(chalk5.yellow("\n\u26A0 Could not copy to clipboard"));
|
|
@@ -1032,7 +946,7 @@ ${body}`;
|
|
|
1032
946
|
try {
|
|
1033
947
|
const escapedTitle = title.replace(/"/g, '\\"');
|
|
1034
948
|
const escapedBody = body.replace(/"/g, '\\"');
|
|
1035
|
-
|
|
949
|
+
execSync2(
|
|
1036
950
|
`gh pr create --title "${escapedTitle}" --body "${escapedBody}" --base ${baseBranch}`,
|
|
1037
951
|
{ stdio: "pipe" }
|
|
1038
952
|
);
|
|
@@ -1057,11 +971,11 @@ import { Command as Command5 } from "commander";
|
|
|
1057
971
|
import chalk6 from "chalk";
|
|
1058
972
|
import ora4 from "ora";
|
|
1059
973
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
1060
|
-
import { execSync as
|
|
974
|
+
import { execSync as execSync3 } from "child_process";
|
|
1061
975
|
async function getPRDiff(prNumber) {
|
|
1062
976
|
try {
|
|
1063
|
-
const diff =
|
|
1064
|
-
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" });
|
|
1065
979
|
const prJson = JSON.parse(prJsonStr);
|
|
1066
980
|
return {
|
|
1067
981
|
diff,
|
|
@@ -1392,8 +1306,8 @@ import { Command as Command8 } from "commander";
|
|
|
1392
1306
|
import chalk9 from "chalk";
|
|
1393
1307
|
import ora7 from "ora";
|
|
1394
1308
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
1395
|
-
import { execSync as
|
|
1396
|
-
import { existsSync as
|
|
1309
|
+
import { execSync as execSync4 } from "child_process";
|
|
1310
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
1397
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) => {
|
|
1398
1312
|
const git = simpleGit7();
|
|
1399
1313
|
const isRepo = await git.checkIsRepo();
|
|
@@ -1414,7 +1328,7 @@ var explainCommand = new Command8("explain").description("Get an AI-powered expl
|
|
|
1414
1328
|
}
|
|
1415
1329
|
} else {
|
|
1416
1330
|
const isPR = target.match(/^#?\d+$/) || target.includes("/pull/");
|
|
1417
|
-
const isFile =
|
|
1331
|
+
const isFile = existsSync3(target);
|
|
1418
1332
|
if (isPR) {
|
|
1419
1333
|
spinner.stop();
|
|
1420
1334
|
if (!requireGhCli()) {
|
|
@@ -1505,7 +1419,7 @@ async function getCommitContext(hash, git, spinner) {
|
|
|
1505
1419
|
}
|
|
1506
1420
|
async function getFileContentContext(filePath, spinner) {
|
|
1507
1421
|
spinner.text = `Reading ${filePath}...`;
|
|
1508
|
-
const content =
|
|
1422
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
1509
1423
|
return {
|
|
1510
1424
|
type: "file-content",
|
|
1511
1425
|
title: filePath,
|
|
@@ -1556,7 +1470,7 @@ async function getPRContext(target, spinner) {
|
|
|
1556
1470
|
spinner.text = `Fetching PR #${prNumber}...`;
|
|
1557
1471
|
let prInfo;
|
|
1558
1472
|
try {
|
|
1559
|
-
const prJson =
|
|
1473
|
+
const prJson = execSync4(
|
|
1560
1474
|
`gh pr view ${prNumber} --json title,url,baseRefName,headRefName,commits`,
|
|
1561
1475
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1562
1476
|
);
|
|
@@ -1567,7 +1481,7 @@ async function getPRContext(target, spinner) {
|
|
|
1567
1481
|
spinner.text = `Getting diff for PR #${prNumber}...`;
|
|
1568
1482
|
let diff;
|
|
1569
1483
|
try {
|
|
1570
|
-
diff =
|
|
1484
|
+
diff = execSync4(`gh pr diff ${prNumber}`, {
|
|
1571
1485
|
encoding: "utf-8",
|
|
1572
1486
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1573
1487
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1729,10 +1643,10 @@ import { Command as Command10 } from "commander";
|
|
|
1729
1643
|
import chalk11 from "chalk";
|
|
1730
1644
|
import ora9 from "ora";
|
|
1731
1645
|
import { simpleGit as simpleGit9 } from "simple-git";
|
|
1732
|
-
import { execSync as
|
|
1646
|
+
import { execSync as execSync5 } from "child_process";
|
|
1733
1647
|
function getIssueInfo(issueNumber) {
|
|
1734
1648
|
try {
|
|
1735
|
-
const result =
|
|
1649
|
+
const result = execSync5(`gh issue view ${issueNumber} --json title,body`, {
|
|
1736
1650
|
encoding: "utf-8",
|
|
1737
1651
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1738
1652
|
});
|
|
@@ -1757,9 +1671,10 @@ var branchCommand = new Command10("branch").description("Generate a branch name
|
|
|
1757
1671
|
if (!requireGhCli()) {
|
|
1758
1672
|
process.exit(1);
|
|
1759
1673
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
const
|
|
1674
|
+
const cleanedIssue = issue.replace(/^#/, "");
|
|
1675
|
+
issueNumber = cleanedIssue;
|
|
1676
|
+
const spinner2 = ora9(`Fetching issue #${cleanedIssue}...`).start();
|
|
1677
|
+
const issueInfo = getIssueInfo(cleanedIssue);
|
|
1763
1678
|
if (!issueInfo) {
|
|
1764
1679
|
spinner2.fail(`Could not fetch issue #${issueNumber}`);
|
|
1765
1680
|
console.log(chalk11.gray("Make sure you are authenticated: gh auth login"));
|
|
@@ -2197,9 +2112,9 @@ var summaryCommand = new Command14("summary").description("Generate a work summa
|
|
|
2197
2112
|
const output = options.markdown ? formatMarkdown(summary, author, since, options.until) : null;
|
|
2198
2113
|
if (options.copy) {
|
|
2199
2114
|
const textToCopy = output || formatMarkdown(summary, author, since, options.until);
|
|
2200
|
-
const { execSync:
|
|
2115
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
2201
2116
|
try {
|
|
2202
|
-
|
|
2117
|
+
execSync9("pbcopy", { input: textToCopy });
|
|
2203
2118
|
console.log(chalk15.green("Summary copied to clipboard!"));
|
|
2204
2119
|
console.log();
|
|
2205
2120
|
} catch {
|
|
@@ -2315,6 +2230,109 @@ function printSummary(summary, author, since, until) {
|
|
|
2315
2230
|
// src/commands/config.ts
|
|
2316
2231
|
import { Command as Command15 } from "commander";
|
|
2317
2232
|
import chalk16 from "chalk";
|
|
2233
|
+
import { execSync as execSync7 } from "child_process";
|
|
2234
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
2235
|
+
import { join as join5 } from "path";
|
|
2236
|
+
import { homedir as homedir3 } from "os";
|
|
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
|
|
2331
|
+
function openFolder(path2) {
|
|
2332
|
+
const platform = process.platform;
|
|
2333
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? 'start ""' : "xdg-open";
|
|
2334
|
+
execSync7(`${cmd} "${path2}"`);
|
|
2335
|
+
}
|
|
2318
2336
|
var configCommand = new Command15("config").description("Manage gut configuration");
|
|
2319
2337
|
configCommand.command("set <key> <value>").description("Set a configuration value").option("--local", "Set for current repository only").action((key, value, options) => {
|
|
2320
2338
|
if (key === "lang") {
|
|
@@ -2347,7 +2365,6 @@ configCommand.command("get <key>").description("Get a configuration value").acti
|
|
|
2347
2365
|
}
|
|
2348
2366
|
});
|
|
2349
2367
|
configCommand.command("list").description("List all configuration values").action(() => {
|
|
2350
|
-
const globalConfig = getGlobalConfig();
|
|
2351
2368
|
const localConfig = getLocalConfig();
|
|
2352
2369
|
const effectiveConfig = getConfig();
|
|
2353
2370
|
console.log(chalk16.bold("Configuration:"));
|
|
@@ -2363,6 +2380,39 @@ configCommand.command("list").description("List all configuration values").actio
|
|
|
2363
2380
|
console.log(chalk16.gray("Local config: .gut/config.json"));
|
|
2364
2381
|
}
|
|
2365
2382
|
});
|
|
2383
|
+
configCommand.command("open").description("Open configuration or templates folder").option("-t, --templates", "Open templates folder instead of config").option("-g, --global", "Open global folder (default)").option("-l, --local", "Open local/project folder").action(async (options) => {
|
|
2384
|
+
const git = simpleGit14();
|
|
2385
|
+
const isLocal = options.local === true;
|
|
2386
|
+
let targetPath;
|
|
2387
|
+
if (isLocal) {
|
|
2388
|
+
const isRepo = await git.checkIsRepo();
|
|
2389
|
+
if (!isRepo) {
|
|
2390
|
+
console.error(chalk16.red("Error: Not a git repository"));
|
|
2391
|
+
console.error(chalk16.gray("Use --global to open global config folder"));
|
|
2392
|
+
process.exit(1);
|
|
2393
|
+
}
|
|
2394
|
+
const repoRoot = await git.revparse(["--show-toplevel"]).catch(() => process.cwd());
|
|
2395
|
+
targetPath = join5(repoRoot.trim(), ".gut");
|
|
2396
|
+
} else {
|
|
2397
|
+
if (options.templates) {
|
|
2398
|
+
targetPath = join5(homedir3(), ".config", "gut", "templates");
|
|
2399
|
+
} else {
|
|
2400
|
+
targetPath = join5(homedir3(), ".config", "gut");
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (!existsSync5(targetPath)) {
|
|
2404
|
+
mkdirSync2(targetPath, { recursive: true });
|
|
2405
|
+
console.log(chalk16.green(`Created ${targetPath}`));
|
|
2406
|
+
}
|
|
2407
|
+
try {
|
|
2408
|
+
openFolder(targetPath);
|
|
2409
|
+
console.log(chalk16.green(`Opened: ${targetPath}`));
|
|
2410
|
+
} catch (error) {
|
|
2411
|
+
console.error(chalk16.red(`Failed to open folder: ${targetPath}`));
|
|
2412
|
+
console.error(chalk16.gray(error.message));
|
|
2413
|
+
process.exit(1);
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2366
2416
|
|
|
2367
2417
|
// src/commands/lang.ts
|
|
2368
2418
|
import { Command as Command16 } from "commander";
|
|
@@ -2395,17 +2445,24 @@ var langCommand = new Command16("lang").description("Set or show output language
|
|
|
2395
2445
|
import { Command as Command17 } from "commander";
|
|
2396
2446
|
import chalk18 from "chalk";
|
|
2397
2447
|
import ora14 from "ora";
|
|
2398
|
-
import { simpleGit as
|
|
2399
|
-
import { existsSync as
|
|
2400
|
-
import { join as
|
|
2448
|
+
import { simpleGit as simpleGit15 } from "simple-git";
|
|
2449
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2450
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
2451
|
+
import { homedir as homedir4 } from "os";
|
|
2401
2452
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2453
|
+
import { execSync as execSync8 } from "child_process";
|
|
2402
2454
|
import { generateText as generateText2 } from "ai";
|
|
2403
2455
|
import { createGoogleGenerativeAI as createGoogleGenerativeAI2 } from "@ai-sdk/google";
|
|
2404
2456
|
import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
2405
2457
|
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
2458
|
+
function openFolder2(path2) {
|
|
2459
|
+
const platform = process.platform;
|
|
2460
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? 'start ""' : "xdg-open";
|
|
2461
|
+
execSync8(`${cmd} "${path2}"`);
|
|
2462
|
+
}
|
|
2406
2463
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2407
2464
|
var __dirname2 = dirname2(__filename2);
|
|
2408
|
-
var GUT_ROOT2 =
|
|
2465
|
+
var GUT_ROOT2 = join6(__dirname2, "..");
|
|
2409
2466
|
var TEMPLATE_FILES = [
|
|
2410
2467
|
"branch.md",
|
|
2411
2468
|
"changelog.md",
|
|
@@ -2443,6 +2500,8 @@ async function translateTemplate(content, targetLang, provider) {
|
|
|
2443
2500
|
model = anthropic(modelName);
|
|
2444
2501
|
break;
|
|
2445
2502
|
}
|
|
2503
|
+
default:
|
|
2504
|
+
throw new Error(`Unsupported provider for translation: ${provider}`);
|
|
2446
2505
|
}
|
|
2447
2506
|
const langNames = {
|
|
2448
2507
|
ja: "Japanese",
|
|
@@ -2468,20 +2527,28 @@ Translated template:`
|
|
|
2468
2527
|
});
|
|
2469
2528
|
return text.trim();
|
|
2470
2529
|
}
|
|
2471
|
-
var initCommand = new Command17("init").description("Initialize .gut/ templates in your project").option("-p, --provider <provider>", "AI provider for translation (gemini, openai, anthropic)", "gemini").option("-f, --force", "Overwrite existing templates").option("--no-translate", "Skip translation even if language is not English").action(async (options) => {
|
|
2472
|
-
const
|
|
2473
|
-
const
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2530
|
+
var initCommand = new Command17("init").description("Initialize .gut/ templates in your project or globally").option("-p, --provider <provider>", "AI provider for translation (gemini, openai, anthropic)", "gemini").option("-f, --force", "Overwrite existing templates").option("-g, --global", "Initialize templates globally (~/.config/gut/templates/)").option("-o, --open", "Open the templates folder (can be used alone)").option("--no-translate", "Skip translation even if language is not English").action(async (options) => {
|
|
2531
|
+
const isGlobal = options.global === true;
|
|
2532
|
+
const git = simpleGit15();
|
|
2533
|
+
let targetDir;
|
|
2534
|
+
if (isGlobal) {
|
|
2535
|
+
targetDir = join6(homedir4(), ".config", "gut", "templates");
|
|
2536
|
+
} else {
|
|
2537
|
+
const isRepo = await git.checkIsRepo();
|
|
2538
|
+
if (!isRepo) {
|
|
2539
|
+
console.error(chalk18.red("Error: Not a git repository"));
|
|
2540
|
+
console.error(chalk18.gray("Use --global to initialize templates globally"));
|
|
2541
|
+
process.exit(1);
|
|
2542
|
+
}
|
|
2543
|
+
const repoRoot = await git.revparse(["--show-toplevel"]).catch(() => process.cwd());
|
|
2544
|
+
targetDir = join6(repoRoot.trim(), ".gut");
|
|
2477
2545
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
const sourceDir = join5(GUT_ROOT2, ".gut");
|
|
2481
|
-
if (!existsSync5(targetDir)) {
|
|
2482
|
-
mkdirSync2(targetDir, { recursive: true });
|
|
2546
|
+
if (!existsSync6(targetDir)) {
|
|
2547
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
2483
2548
|
console.log(chalk18.green(`Created ${targetDir}`));
|
|
2484
2549
|
}
|
|
2550
|
+
console.log(chalk18.blue(isGlobal ? "Initializing global templates...\n" : "Initializing project templates...\n"));
|
|
2551
|
+
const sourceDir = join6(GUT_ROOT2, ".gut");
|
|
2485
2552
|
const lang = getLanguage();
|
|
2486
2553
|
const needsTranslation = options.translate !== false && lang !== "en";
|
|
2487
2554
|
const provider = options.provider.toLowerCase();
|
|
@@ -2493,12 +2560,12 @@ var initCommand = new Command17("init").description("Initialize .gut/ templates
|
|
|
2493
2560
|
let copied = 0;
|
|
2494
2561
|
let skipped = 0;
|
|
2495
2562
|
for (const filename of TEMPLATE_FILES) {
|
|
2496
|
-
const sourcePath =
|
|
2497
|
-
const targetPath =
|
|
2498
|
-
if (!
|
|
2563
|
+
const sourcePath = join6(sourceDir, filename);
|
|
2564
|
+
const targetPath = join6(targetDir, filename);
|
|
2565
|
+
if (!existsSync6(sourcePath)) {
|
|
2499
2566
|
continue;
|
|
2500
2567
|
}
|
|
2501
|
-
if (
|
|
2568
|
+
if (existsSync6(targetPath) && !options.force) {
|
|
2502
2569
|
console.log(chalk18.gray(` Skipped: ${filename} (already exists)`));
|
|
2503
2570
|
skipped++;
|
|
2504
2571
|
continue;
|
|
@@ -2522,21 +2589,37 @@ var initCommand = new Command17("init").description("Initialize .gut/ templates
|
|
|
2522
2589
|
}
|
|
2523
2590
|
console.log();
|
|
2524
2591
|
if (copied > 0) {
|
|
2525
|
-
|
|
2592
|
+
const location = isGlobal ? "~/.config/gut/templates/" : ".gut/";
|
|
2593
|
+
console.log(chalk18.green(`\u2713 ${copied} template(s) initialized in ${location}`));
|
|
2526
2594
|
}
|
|
2527
2595
|
if (skipped > 0) {
|
|
2528
2596
|
console.log(chalk18.gray(` ${skipped} template(s) skipped (use --force to overwrite)`));
|
|
2529
2597
|
}
|
|
2530
|
-
|
|
2598
|
+
if (isGlobal) {
|
|
2599
|
+
console.log(chalk18.gray("\nGlobal templates will be used as fallback for all projects."));
|
|
2600
|
+
console.log(chalk18.gray("Project-level templates (.gut/) take priority over global templates."));
|
|
2601
|
+
} else {
|
|
2602
|
+
console.log(chalk18.gray("\nYou can now customize these templates for your project."));
|
|
2603
|
+
}
|
|
2604
|
+
if (options.open) {
|
|
2605
|
+
try {
|
|
2606
|
+
openFolder2(targetDir);
|
|
2607
|
+
console.log(chalk18.green(`
|
|
2608
|
+
Opened: ${targetDir}`));
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
console.error(chalk18.red(`
|
|
2611
|
+
Failed to open folder: ${targetDir}`));
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2531
2614
|
});
|
|
2532
2615
|
|
|
2533
2616
|
// src/commands/gitignore.ts
|
|
2534
2617
|
import { Command as Command18 } from "commander";
|
|
2535
2618
|
import chalk19 from "chalk";
|
|
2536
2619
|
import ora15 from "ora";
|
|
2537
|
-
import { simpleGit as
|
|
2538
|
-
import { readdirSync
|
|
2539
|
-
import { join as
|
|
2620
|
+
import { simpleGit as simpleGit16 } from "simple-git";
|
|
2621
|
+
import { readdirSync, readFileSync as readFileSync7, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2622
|
+
import { join as join7 } from "path";
|
|
2540
2623
|
var CONFIG_FILES = [
|
|
2541
2624
|
// JavaScript/TypeScript
|
|
2542
2625
|
"package.json",
|
|
@@ -2584,12 +2667,12 @@ function getFiles(dir, maxDepth = 3, currentDepth = 0) {
|
|
|
2584
2667
|
if (currentDepth >= maxDepth) return [];
|
|
2585
2668
|
const files = [];
|
|
2586
2669
|
try {
|
|
2587
|
-
const entries =
|
|
2670
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2588
2671
|
for (const entry of entries) {
|
|
2589
2672
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "target" || entry.name === "__pycache__" || entry.name === "venv" || entry.name === ".venv") {
|
|
2590
2673
|
continue;
|
|
2591
2674
|
}
|
|
2592
|
-
const fullPath =
|
|
2675
|
+
const fullPath = join7(dir, entry.name);
|
|
2593
2676
|
if (entry.isDirectory()) {
|
|
2594
2677
|
files.push(entry.name + "/");
|
|
2595
2678
|
const subFiles = getFiles(fullPath, maxDepth, currentDepth + 1);
|
|
@@ -2608,18 +2691,18 @@ function findConfigFiles(repoRoot) {
|
|
|
2608
2691
|
if (configFile.includes("*")) {
|
|
2609
2692
|
const ext = configFile.replace("*", "");
|
|
2610
2693
|
try {
|
|
2611
|
-
const entries =
|
|
2694
|
+
const entries = readdirSync(repoRoot);
|
|
2612
2695
|
for (const entry of entries) {
|
|
2613
2696
|
if (entry.endsWith(ext)) {
|
|
2614
|
-
const content = readFileSync7(
|
|
2697
|
+
const content = readFileSync7(join7(repoRoot, entry), "utf-8");
|
|
2615
2698
|
found.set(entry, content.slice(0, 2e3));
|
|
2616
2699
|
}
|
|
2617
2700
|
}
|
|
2618
2701
|
} catch {
|
|
2619
2702
|
}
|
|
2620
2703
|
} else {
|
|
2621
|
-
const filePath =
|
|
2622
|
-
if (
|
|
2704
|
+
const filePath = join7(repoRoot, configFile);
|
|
2705
|
+
if (existsSync7(filePath)) {
|
|
2623
2706
|
try {
|
|
2624
2707
|
const content = readFileSync7(filePath, "utf-8");
|
|
2625
2708
|
found.set(configFile, content.slice(0, 2e3));
|
|
@@ -2631,7 +2714,7 @@ function findConfigFiles(repoRoot) {
|
|
|
2631
2714
|
return found;
|
|
2632
2715
|
}
|
|
2633
2716
|
var gitignoreCommand = new Command18("gitignore").description("Generate .gitignore from current codebase").option("-p, --provider <provider>", "AI provider (gemini, openai, anthropic)", "gemini").option("-m, --model <model>", "Model to use (provider-specific)").option("-o, --output <file>", "Output file (default: .gitignore)", ".gitignore").option("--stdout", "Print to stdout instead of file").option("-y, --yes", "Overwrite existing .gitignore without confirmation").action(async (options) => {
|
|
2634
|
-
const git =
|
|
2717
|
+
const git = simpleGit16();
|
|
2635
2718
|
const repoRoot = await git.revparse(["--show-toplevel"]).catch(() => process.cwd());
|
|
2636
2719
|
const root = repoRoot.trim();
|
|
2637
2720
|
const provider = options.provider.toLowerCase();
|
|
@@ -2642,9 +2725,9 @@ var gitignoreCommand = new Command18("gitignore").description("Generate .gitigno
|
|
|
2642
2725
|
const spinner = ora15("Analyzing project structure...").start();
|
|
2643
2726
|
const files = getFiles(root);
|
|
2644
2727
|
const configFiles = findConfigFiles(root);
|
|
2645
|
-
const gitignorePath =
|
|
2728
|
+
const gitignorePath = join7(root, options.output);
|
|
2646
2729
|
let existingGitignore;
|
|
2647
|
-
if (
|
|
2730
|
+
if (existsSync7(gitignorePath)) {
|
|
2648
2731
|
existingGitignore = readFileSync7(gitignorePath, "utf-8");
|
|
2649
2732
|
}
|
|
2650
2733
|
let configFilesStr = "";
|
|
@@ -2679,7 +2762,7 @@ ${content}
|
|
|
2679
2762
|
console.log(gitignoreContent);
|
|
2680
2763
|
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2681
2764
|
console.log();
|
|
2682
|
-
if (
|
|
2765
|
+
if (existsSync7(gitignorePath) && !options.yes) {
|
|
2683
2766
|
const readline = await import("readline");
|
|
2684
2767
|
const rl = readline.createInterface({
|
|
2685
2768
|
input: process.stdin,
|