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/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,25 @@ 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, 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
- 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
+ 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 = applyTemplate(template, "commit", {
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 = applyTemplate(template, "pr", {
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 = applyTemplate(template, "review", {
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 = applyTemplate(template, "changelog", {
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 = applyTemplate(template, "explain-file", {
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 = applyTemplate(template, "explain", {
552
+ const prompt = buildPrompt(template, "explain", {
643
553
  targetType,
644
- context: contextInfo,
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 = applyTemplate(template, "find", {
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 = applyTemplate(template, "branch", {
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 = applyTemplate(template, "checkout", {
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 = applyTemplate(template, "stash", {
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 = applyTemplate(template, "summary", {
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 = applyTemplate(template, "merge", {
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 = applyTemplate(template, "gitignore", {
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 existsSync3, readFileSync as readFileSync3 } from "fs";
905
- import { join as join3 } from "path";
906
- import { execSync as execSync3 } from "child_process";
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 as execSync2 } from "child_process";
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
- execSync2("gh --version", { stdio: "pipe" });
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 = join3(repoRoot, templatePath);
950
- if (existsSync3(fullPath)) {
951
- return readFileSync3(fullPath, "utf-8");
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
- execSync3("pbcopy", { input: fullText });
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
- execSync3(
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 execSync4 } from "child_process";
985
+ import { execSync as execSync3 } from "child_process";
1076
986
  async function getPRDiff(prNumber) {
1077
987
  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" });
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 execSync5 } from "child_process";
1411
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
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 = existsSync4(target);
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 = readFileSync5(filePath, "utf-8");
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 = execSync5(
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 = execSync5(`gh pr diff ${prNumber}`, {
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 execSync6 } from "child_process";
1657
+ import { execSync as execSync5 } from "child_process";
1748
1658
  function getIssueInfo(issueNumber) {
1749
1659
  try {
1750
- const result = execSync6(`gh issue view ${issueNumber} --json title,body`, {
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
- issueNumber = issue.replace(/^#/, "");
1776
- const spinner2 = ora9(`Fetching issue #${issueNumber}...`).start();
1777
- const issueInfo = getIssueInfo(issueNumber);
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 as readdirSync2, readFileSync as readFileSync7, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
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 = readdirSync2(dir, { withFileTypes: true });
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 = readdirSync2(repoRoot);
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");