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/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 as existsSync2, readFileSync as readFileSync2 } from "fs";
292
- import { join as join2, dirname } from "path";
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 { join } from "path";
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 = join2(current, ".gut");
403
- if (existsSync2(gutPath)) {
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 join2(__dirname, "..");
306
+ return join(__dirname, "..");
409
307
  }
410
308
  var GUT_ROOT = findGutRoot();
411
309
  function loadTemplate(name) {
412
- const templatePath = join2(GUT_ROOT, ".gut", `${name}.md`);
413
- if (existsSync2(templatePath)) {
414
- return readFileSync2(templatePath, "utf-8");
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 join2(homedir2(), ".config", "gut", "templates");
317
+ return join(homedir(), ".config", "gut", "templates");
420
318
  }
421
319
  function findGlobalTemplate(templateName) {
422
- const templatePath = join2(getGlobalTemplatesDir(), `${templateName}.md`);
423
- if (existsSync2(templatePath)) {
424
- return readFileSync2(templatePath, "utf-8");
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 = join2(repoRoot, ".gut", `${templateName}.md`);
430
- if (existsSync2(projectTemplatePath)) {
431
- return readFileSync2(projectTemplatePath, "utf-8");
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 applyTemplate(userTemplate, templateName, variables) {
440
- const langInstruction = getLanguageInstruction(getLanguage());
441
- let result = userTemplate || loadTemplate(templateName);
442
- result = result.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_, key, content) => {
443
- return variables[key] ? content : "";
444
- });
445
- for (const [key, value] of Object.entries(variables)) {
446
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value || "");
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
- return result;
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 = applyTemplate(template, "commit", {
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 = applyTemplate(template, "pr", {
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 = applyTemplate(template, "review", {
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 = applyTemplate(template, "changelog", {
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 = applyTemplate(template, "explain-file", {
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 = applyTemplate(template, "explain", {
541
+ const prompt = buildPrompt(template, "explain", {
643
542
  targetType,
644
- context: contextInfo,
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 = applyTemplate(template, "find", {
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 = applyTemplate(template, "branch", {
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 = applyTemplate(template, "checkout", {
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 = applyTemplate(template, "stash", {
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 = applyTemplate(template, "summary", {
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 = applyTemplate(template, "merge", {
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 = applyTemplate(template, "gitignore", {
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 existsSync3, readFileSync as readFileSync3 } from "fs";
905
- import { join as join3 } from "path";
906
- import { execSync as execSync3 } from "child_process";
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 as execSync2 } from "child_process";
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
- execSync2("gh --version", { stdio: "pipe" });
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 = join3(repoRoot, templatePath);
950
- if (existsSync3(fullPath)) {
951
- return readFileSync3(fullPath, "utf-8");
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
- execSync3("pbcopy", { input: fullText });
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
- execSync3(
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 execSync4 } from "child_process";
974
+ import { execSync as execSync3 } from "child_process";
1076
975
  async function getPRDiff(prNumber) {
1077
976
  try {
1078
- const diff = execSync4(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
1079
- const prJsonStr = execSync4(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
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 execSync5 } from "child_process";
1411
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
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 = existsSync4(target);
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 = readFileSync5(filePath, "utf-8");
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 = execSync5(
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 = execSync5(`gh pr diff ${prNumber}`, {
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 execSync6 } from "child_process";
1646
+ import { execSync as execSync5 } from "child_process";
1748
1647
  function getIssueInfo(issueNumber) {
1749
1648
  try {
1750
- const result = execSync6(`gh issue view ${issueNumber} --json title,body`, {
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
- issueNumber = issue.replace(/^#/, "");
1776
- const spinner2 = ora9(`Fetching issue #${issueNumber}...`).start();
1777
- const issueInfo = getIssueInfo(issueNumber);
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 as readdirSync2, readFileSync as readFileSync7, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
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 = readdirSync2(dir, { withFileTypes: true });
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 = readdirSync2(repoRoot);
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");