gut-cli 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -24,6 +24,8 @@ All AI commands support short aliases (without `ai-` prefix):
24
24
  | `gut changelog` | - | Generate changelogs |
25
25
  | `gut sync` | - | Sync with remote (fetch + rebase/merge) |
26
26
  | `gut stash` | - | Stash with AI-generated names |
27
+ | `gut config` | - | Manage configuration (language, etc.) |
28
+ | `gut lang` | - | Set or show output language |
27
29
 
28
30
  ### `gut commit`
29
31
 
@@ -193,20 +195,20 @@ gut branch -d "add user authentication"
193
195
 
194
196
  ### `gut sync`
195
197
 
196
- Sync current branch with remote (fetch + rebase).
198
+ Sync current branch with remote (fetch + rebase + push).
197
199
 
198
200
  ```bash
199
- # Sync current branch (rebase by default)
201
+ # Sync current branch (fetch + rebase + push)
200
202
  gut sync
201
203
 
202
- # Use merge instead of rebase
203
- gut sync --merge
204
-
205
204
  # Auto-stash changes before sync
206
205
  gut sync --stash
207
206
 
208
- # Force sync with uncommitted changes
209
- gut sync --force
207
+ # Use merge instead of rebase
208
+ gut sync --merge
209
+
210
+ # Skip push
211
+ gut sync --no-push
210
212
  ```
211
213
 
212
214
  ### `gut stash`
@@ -259,6 +261,49 @@ gut changelog --json
259
261
 
260
262
  **Changelog Template Support**: If `CHANGELOG.md` exists, gut will match its style.
261
263
 
264
+ ### `gut lang`
265
+
266
+ Set or show AI output language (shortcut for `gut config set lang`).
267
+
268
+ ```bash
269
+ # Show current language
270
+ gut lang
271
+
272
+ # Set to Japanese
273
+ gut lang ja
274
+
275
+ # Set to English
276
+ gut lang en
277
+
278
+ # Set for current repository only
279
+ gut lang en --local
280
+ ```
281
+
282
+ ### `gut config`
283
+
284
+ Manage gut configuration settings.
285
+
286
+ ```bash
287
+ # List all settings
288
+ gut config list
289
+
290
+ # Set language to Japanese (global)
291
+ gut config set lang ja
292
+
293
+ # Set language for current repository only
294
+ gut config set lang en --local
295
+
296
+ # Get current language
297
+ gut config get lang
298
+ ```
299
+
300
+ **Available settings:**
301
+ - `lang` - Output language for AI responses (`en`, `ja`)
302
+
303
+ **Configuration precedence:**
304
+ 1. Local: `.gut/config.json` (per-repository)
305
+ 2. Global: `~/.config/gut/config.json`
306
+
262
307
  ### `gut cleanup`
263
308
 
264
309
  Delete merged branches safely.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command13 } from "commander";
4
+ import { Command as Command15 } from "commander";
5
5
 
6
6
  // src/commands/cleanup.ts
7
7
  import { Command } from "commander";
@@ -264,8 +264,8 @@ import { Command as Command3 } from "commander";
264
264
  import chalk3 from "chalk";
265
265
  import ora2 from "ora";
266
266
  import { simpleGit as simpleGit2 } from "simple-git";
267
- import { existsSync, readFileSync } from "fs";
268
- import { join } from "path";
267
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
268
+ import { join as join2 } from "path";
269
269
 
270
270
  // src/lib/ai.ts
271
271
  import { generateText, generateObject } from "ai";
@@ -273,6 +273,108 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
273
273
  import { createOpenAI } from "@ai-sdk/openai";
274
274
  import { createAnthropic } from "@ai-sdk/anthropic";
275
275
  import { z } from "zod";
276
+
277
+ // src/lib/config.ts
278
+ import { homedir } from "os";
279
+ import { join } from "path";
280
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
281
+ import { execSync } from "child_process";
282
+ var DEFAULT_CONFIG = {
283
+ lang: "en"
284
+ };
285
+ function getGlobalConfigPath() {
286
+ const configDir = join(homedir(), ".config", "gut");
287
+ return join(configDir, "config.json");
288
+ }
289
+ function getRepoRoot() {
290
+ try {
291
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
292
+ } catch {
293
+ return null;
294
+ }
295
+ }
296
+ function getLocalConfigPath() {
297
+ const repoRoot = getRepoRoot();
298
+ if (!repoRoot) return null;
299
+ return join(repoRoot, ".gut", "config.json");
300
+ }
301
+ function ensureGlobalConfigDir() {
302
+ const configDir = join(homedir(), ".config", "gut");
303
+ if (!existsSync(configDir)) {
304
+ mkdirSync(configDir, { recursive: true });
305
+ }
306
+ }
307
+ function ensureLocalConfigDir() {
308
+ const repoRoot = getRepoRoot();
309
+ if (!repoRoot) return;
310
+ const gutDir = join(repoRoot, ".gut");
311
+ if (!existsSync(gutDir)) {
312
+ mkdirSync(gutDir, { recursive: true });
313
+ }
314
+ }
315
+ function readConfigFile(path2) {
316
+ if (!existsSync(path2)) return {};
317
+ try {
318
+ return JSON.parse(readFileSync(path2, "utf-8"));
319
+ } catch {
320
+ return {};
321
+ }
322
+ }
323
+ function getGlobalConfig() {
324
+ const globalPath = getGlobalConfigPath();
325
+ return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
326
+ }
327
+ function getLocalConfig() {
328
+ const localPath = getLocalConfigPath();
329
+ if (!localPath) return {};
330
+ return readConfigFile(localPath);
331
+ }
332
+ function getConfig() {
333
+ const globalConfig = getGlobalConfig();
334
+ const localConfig = getLocalConfig();
335
+ return { ...globalConfig, ...localConfig };
336
+ }
337
+ function setGlobalConfig(key, value) {
338
+ ensureGlobalConfigDir();
339
+ const config = getGlobalConfig();
340
+ config[key] = value;
341
+ writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
342
+ }
343
+ function setLocalConfig(key, value) {
344
+ const localPath = getLocalConfigPath();
345
+ if (!localPath) {
346
+ throw new Error("Not in a git repository");
347
+ }
348
+ ensureLocalConfigDir();
349
+ const config = getLocalConfig();
350
+ config[key] = value;
351
+ writeFileSync(localPath, JSON.stringify(config, null, 2));
352
+ }
353
+ function getLanguage() {
354
+ return getConfig().lang;
355
+ }
356
+ function setLanguage(lang, local = false) {
357
+ if (local) {
358
+ setLocalConfig("lang", lang);
359
+ } else {
360
+ setGlobalConfig("lang", lang);
361
+ }
362
+ }
363
+ function getLanguageInstruction(lang) {
364
+ switch (lang) {
365
+ case "ja":
366
+ return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
367
+ case "en":
368
+ default:
369
+ return "";
370
+ }
371
+ }
372
+ var VALID_LANGUAGES = ["en", "ja"];
373
+ function isValidLanguage(lang) {
374
+ return VALID_LANGUAGES.includes(lang);
375
+ }
376
+
377
+ // src/lib/ai.ts
276
378
  var DEFAULT_MODELS = {
277
379
  gemini: "gemini-2.0-flash",
278
380
  openai: "gpt-4o-mini",
@@ -317,6 +419,7 @@ Rules:
317
419
  - Description should be lowercase, imperative mood, no period at end
318
420
  - Keep the first line under 72 characters
319
421
  - If changes are complex, add a blank line and bullet points for details`;
422
+ const langInstruction = getLanguageInstruction(getLanguage());
320
423
  const prompt = `You are an expert at writing git commit messages.
321
424
 
322
425
  Analyze the following git diff and generate a concise, meaningful commit message.
@@ -327,7 +430,7 @@ Git diff:
327
430
  ${diff.slice(0, 8e3)}
328
431
  \`\`\`
329
432
 
330
- Respond with ONLY the commit message, nothing else.`;
433
+ Respond with ONLY the commit message, nothing else.${langInstruction}`;
331
434
  const result = await generateText({
332
435
  model,
333
436
  prompt,
@@ -351,6 +454,7 @@ Rules for description:
351
454
  - ## Summary section with 2-3 bullet points
352
455
  - ## Changes section listing key modifications
353
456
  - ## Test Plan section (suggest what to test)`;
457
+ const langInstruction = getLanguageInstruction(getLanguage());
354
458
  const prompt = `You are an expert at writing pull request descriptions.
355
459
 
356
460
  Generate a clear and informative PR title and description based on the following information.
@@ -373,7 +477,7 @@ Respond in JSON format:
373
477
  {
374
478
  "title": "...",
375
479
  "body": "..."
376
- }`;
480
+ }${langInstruction}`;
377
481
  const result = await generateText({
378
482
  model,
379
483
  prompt,
@@ -404,6 +508,7 @@ var CodeReviewSchema = z.object({
404
508
  });
405
509
  async function generateCodeReview(diff, options) {
406
510
  const model = await getModel(options);
511
+ const langInstruction = getLanguageInstruction(getLanguage());
407
512
  const result = await generateObject({
408
513
  model,
409
514
  schema: CodeReviewSchema,
@@ -421,7 +526,7 @@ Git diff:
421
526
  ${diff.slice(0, 1e4)}
422
527
  \`\`\`
423
528
 
424
- Be constructive and specific. Include line numbers when possible.`
529
+ Be constructive and specific. Include line numbers when possible.${langInstruction}`
425
530
  });
426
531
  return result.object;
427
532
  }
@@ -451,6 +556,7 @@ Use Keep a Changelog format (https://keepachangelog.com/):
451
556
  - Each item should be a concise description of the change
452
557
  - Use past tense`;
453
558
  const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
559
+ const langInstruction = getLanguageInstruction(getLanguage());
454
560
  const result = await generateObject({
455
561
  model,
456
562
  schema: ChangelogSchema,
@@ -472,7 +578,7 @@ Focus on:
472
578
  - Bug fixes and their impact
473
579
  - Breaking changes (highlight these)
474
580
  - Group related changes together
475
- - Write for end users, not developers (unless it's a library)`
581
+ - Write for end users, not developers (unless it's a library)${langInstruction}`
476
582
  });
477
583
  return result.object;
478
584
  }
@@ -502,6 +608,7 @@ IMPORTANT: Use this project context to provide more accurate explanations:
502
608
  ${projectContext.slice(0, 4e3)}
503
609
  --- PROJECT CONTEXT END ---
504
610
  ` : "";
611
+ const langInstruction = getLanguageInstruction(getLanguage());
505
612
  if (context.type === "file-content") {
506
613
  const result2 = await generateObject({
507
614
  model,
@@ -524,7 +631,7 @@ Focus on:
524
631
  - Dependencies and what it interacts with
525
632
  - Any important patterns or architecture decisions
526
633
 
527
- Explain in a way that helps someone quickly understand this file's purpose and how it fits into the larger codebase.`
634
+ Explain in a way that helps someone quickly understand this file's purpose and how it fits into the larger codebase.${langInstruction}`
528
635
  });
529
636
  return result2.object;
530
637
  }
@@ -582,7 +689,7 @@ Focus on:
582
689
  - The broader context and purpose
583
690
  - Any important implications or side effects
584
691
 
585
- Explain in a way that helps someone understand not just the "what" but the "why" behind these changes.`
692
+ Explain in a way that helps someone understand not just the "what" but the "why" behind these changes.${langInstruction}`
586
693
  });
587
694
  return result.object;
588
695
  }
@@ -605,6 +712,7 @@ ${projectContext.slice(0, 3e3)}
605
712
  --- PROJECT CONTEXT END ---
606
713
  ` : "";
607
714
  const commitList = commits.map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`).join("\n");
715
+ const langInstruction = getLanguageInstruction(getLanguage());
608
716
  const result = await generateObject({
609
717
  model,
610
718
  schema: CommitSearchSchema,
@@ -627,7 +735,7 @@ Only include commits that actually match the query - if none match well, return
627
735
 
628
736
  For each match, provide:
629
737
  - The commit hash (first 7 characters are fine)
630
- - A brief reason explaining why this commit matches the query`
738
+ - A brief reason explaining why this commit matches the query${langInstruction}`
631
739
  });
632
740
  const enrichedMatches = result.object.matches.map((match) => {
633
741
  const commit = commits.find((c) => c.hash.startsWith(match.hash));
@@ -765,9 +873,9 @@ var CONVENTION_PATHS = [
765
873
  ];
766
874
  function findCommitConvention(repoRoot) {
767
875
  for (const conventionPath of CONVENTION_PATHS) {
768
- const fullPath = join(repoRoot, conventionPath);
769
- if (existsSync(fullPath)) {
770
- return readFileSync(fullPath, "utf-8");
876
+ const fullPath = join2(repoRoot, conventionPath);
877
+ if (existsSync2(fullPath)) {
878
+ return readFileSync2(fullPath, "utf-8");
771
879
  }
772
880
  }
773
881
  return null;
@@ -832,14 +940,14 @@ var aiCommitCommand = new Command3("ai-commit").alias("commit").description("Gen
832
940
  console.log(chalk3.green("\u2713 Committed successfully"));
833
941
  } else if (answer.toLowerCase() === "e") {
834
942
  console.log(chalk3.gray("Opening editor..."));
835
- const { execSync: execSync4 } = await import("child_process");
943
+ const { execSync: execSync5 } = await import("child_process");
836
944
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
837
945
  const fs2 = await import("fs");
838
946
  const os = await import("os");
839
947
  const path2 = await import("path");
840
948
  const tmpFile = path2.join(os.tmpdir(), "gut-commit-msg.txt");
841
949
  fs2.writeFileSync(tmpFile, message);
842
- execSync4(`${editor} "${tmpFile}"`, { stdio: "inherit" });
950
+ execSync5(`${editor} "${tmpFile}"`, { stdio: "inherit" });
843
951
  const editedMessage = fs2.readFileSync(tmpFile, "utf-8").trim();
844
952
  fs2.unlinkSync(tmpFile);
845
953
  if (editedMessage) {
@@ -866,8 +974,8 @@ import { Command as Command4 } from "commander";
866
974
  import chalk4 from "chalk";
867
975
  import ora3 from "ora";
868
976
  import { simpleGit as simpleGit3 } from "simple-git";
869
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
870
- import { join as join2 } from "path";
977
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
978
+ import { join as join3 } from "path";
871
979
  var PR_TEMPLATE_PATHS = [
872
980
  ".gut/pr-template.md",
873
981
  ".github/pull_request_template.md",
@@ -878,9 +986,9 @@ var PR_TEMPLATE_PATHS = [
878
986
  ];
879
987
  function findPRTemplate(repoRoot) {
880
988
  for (const templatePath of PR_TEMPLATE_PATHS) {
881
- const fullPath = join2(repoRoot, templatePath);
882
- if (existsSync2(fullPath)) {
883
- return readFileSync2(fullPath, "utf-8");
989
+ const fullPath = join3(repoRoot, templatePath);
990
+ if (existsSync3(fullPath)) {
991
+ return readFileSync3(fullPath, "utf-8");
884
992
  }
885
993
  }
886
994
  return null;
@@ -948,11 +1056,11 @@ var aiPrCommand = new Command4("ai-pr").alias("pr").description("Generate a pull
948
1056
  console.log(chalk4.gray("\u2500".repeat(50)));
949
1057
  if (options.copy) {
950
1058
  try {
951
- const { execSync: execSync4 } = await import("child_process");
1059
+ const { execSync: execSync5 } = await import("child_process");
952
1060
  const fullText = `${title}
953
1061
 
954
1062
  ${body}`;
955
- execSync4("pbcopy", { input: fullText });
1063
+ execSync5("pbcopy", { input: fullText });
956
1064
  console.log(chalk4.green("\n\u2713 Copied to clipboard"));
957
1065
  } catch {
958
1066
  console.log(chalk4.yellow("\n\u26A0 Could not copy to clipboard"));
@@ -971,10 +1079,10 @@ ${body}`;
971
1079
  if (answer.toLowerCase() === "y") {
972
1080
  const createSpinner = ora3("Creating PR...").start();
973
1081
  try {
974
- const { execSync: execSync4 } = await import("child_process");
1082
+ const { execSync: execSync5 } = await import("child_process");
975
1083
  const escapedTitle = title.replace(/"/g, '\\"');
976
1084
  const escapedBody = body.replace(/"/g, '\\"');
977
- execSync4(
1085
+ execSync5(
978
1086
  `gh pr create --title "${escapedTitle}" --body "${escapedBody}" --base ${baseBranch}`,
979
1087
  { stdio: "pipe" }
980
1088
  );
@@ -1002,11 +1110,11 @@ import { Command as Command5 } from "commander";
1002
1110
  import chalk5 from "chalk";
1003
1111
  import ora4 from "ora";
1004
1112
  import { simpleGit as simpleGit4 } from "simple-git";
1005
- import { execSync } from "child_process";
1113
+ import { execSync as execSync2 } from "child_process";
1006
1114
  async function getPRDiff(prNumber) {
1007
1115
  try {
1008
- const diff = execSync(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
1009
- const prJsonStr = execSync(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
1116
+ const diff = execSync2(`gh pr diff ${prNumber}`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
1117
+ const prJsonStr = execSync2(`gh pr view ${prNumber} --json number,title,author,url`, { encoding: "utf-8" });
1010
1118
  const prJson = JSON.parse(prJsonStr);
1011
1119
  return {
1012
1120
  diff,
@@ -1258,8 +1366,8 @@ import { Command as Command7 } from "commander";
1258
1366
  import chalk7 from "chalk";
1259
1367
  import ora6 from "ora";
1260
1368
  import { simpleGit as simpleGit6 } from "simple-git";
1261
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1262
- import { join as join4 } from "path";
1369
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1370
+ import { join as join5 } from "path";
1263
1371
  var CHANGELOG_PATHS = [
1264
1372
  ".gut/changelog-template.md",
1265
1373
  ".gut/CHANGELOG.md",
@@ -1271,9 +1379,9 @@ var CHANGELOG_PATHS = [
1271
1379
  ];
1272
1380
  function findChangelog(repoRoot) {
1273
1381
  for (const changelogPath of CHANGELOG_PATHS) {
1274
- const fullPath = join4(repoRoot, changelogPath);
1275
- if (existsSync4(fullPath)) {
1276
- return readFileSync4(fullPath, "utf-8");
1382
+ const fullPath = join5(repoRoot, changelogPath);
1383
+ if (existsSync5(fullPath)) {
1384
+ return readFileSync5(fullPath, "utf-8");
1277
1385
  }
1278
1386
  }
1279
1387
  return null;
@@ -1373,17 +1481,17 @@ import { Command as Command8 } from "commander";
1373
1481
  import chalk8 from "chalk";
1374
1482
  import ora7 from "ora";
1375
1483
  import { simpleGit as simpleGit7 } from "simple-git";
1376
- import { execSync as execSync2 } from "child_process";
1377
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1378
- import { join as join5 } from "path";
1484
+ import { execSync as execSync3 } from "child_process";
1485
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1486
+ import { join as join6 } from "path";
1379
1487
  var CONTEXT_PATHS = [
1380
1488
  ".gut/explain.md"
1381
1489
  ];
1382
1490
  function findExplainContext(repoRoot) {
1383
1491
  for (const contextPath of CONTEXT_PATHS) {
1384
- const fullPath = join5(repoRoot, contextPath);
1385
- if (existsSync5(fullPath)) {
1386
- return readFileSync5(fullPath, "utf-8");
1492
+ const fullPath = join6(repoRoot, contextPath);
1493
+ if (existsSync6(fullPath)) {
1494
+ return readFileSync6(fullPath, "utf-8");
1387
1495
  }
1388
1496
  }
1389
1497
  return null;
@@ -1408,7 +1516,7 @@ var aiExplainCommand = new Command8("ai-explain").alias("explain").description("
1408
1516
  }
1409
1517
  } else {
1410
1518
  const isPR = target.match(/^#?\d+$/) || target.includes("/pull/");
1411
- const isFile = existsSync5(target);
1519
+ const isFile = existsSync6(target);
1412
1520
  if (isPR) {
1413
1521
  context = await getPRContext(target, spinner);
1414
1522
  } else if (isFile) {
@@ -1491,7 +1599,7 @@ async function getCommitContext(hash, git, spinner) {
1491
1599
  }
1492
1600
  async function getFileContentContext(filePath, spinner) {
1493
1601
  spinner.text = `Reading ${filePath}...`;
1494
- const content = readFileSync5(filePath, "utf-8");
1602
+ const content = readFileSync6(filePath, "utf-8");
1495
1603
  return {
1496
1604
  type: "file-content",
1497
1605
  title: filePath,
@@ -1542,7 +1650,7 @@ async function getPRContext(target, spinner) {
1542
1650
  spinner.text = `Fetching PR #${prNumber}...`;
1543
1651
  let prInfo;
1544
1652
  try {
1545
- const prJson = execSync2(
1653
+ const prJson = execSync3(
1546
1654
  `gh pr view ${prNumber} --json title,url,baseRefName,headRefName,commits`,
1547
1655
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
1548
1656
  );
@@ -1553,7 +1661,7 @@ async function getPRContext(target, spinner) {
1553
1661
  spinner.text = `Getting diff for PR #${prNumber}...`;
1554
1662
  let diff;
1555
1663
  try {
1556
- diff = execSync2(`gh pr diff ${prNumber}`, {
1664
+ diff = execSync3(`gh pr diff ${prNumber}`, {
1557
1665
  encoding: "utf-8",
1558
1666
  stdio: ["pipe", "pipe", "pipe"],
1559
1667
  maxBuffer: 10 * 1024 * 1024
@@ -1620,14 +1728,14 @@ import { Command as Command9 } from "commander";
1620
1728
  import chalk9 from "chalk";
1621
1729
  import ora8 from "ora";
1622
1730
  import { simpleGit as simpleGit8 } from "simple-git";
1623
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1624
- import { join as join6 } from "path";
1731
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1732
+ import { join as join7 } from "path";
1625
1733
  var CONTEXT_PATHS2 = [".gut/find.md"];
1626
1734
  function findProjectContext(repoRoot) {
1627
1735
  for (const contextPath of CONTEXT_PATHS2) {
1628
- const fullPath = join6(repoRoot, contextPath);
1629
- if (existsSync6(fullPath)) {
1630
- return readFileSync6(fullPath, "utf-8");
1736
+ const fullPath = join7(repoRoot, contextPath);
1737
+ if (existsSync7(fullPath)) {
1738
+ return readFileSync7(fullPath, "utf-8");
1631
1739
  }
1632
1740
  }
1633
1741
  return null;
@@ -1730,22 +1838,22 @@ import { Command as Command10 } from "commander";
1730
1838
  import chalk10 from "chalk";
1731
1839
  import ora9 from "ora";
1732
1840
  import { simpleGit as simpleGit9 } from "simple-git";
1733
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1734
- import { join as join7 } from "path";
1735
- import { execSync as execSync3 } from "child_process";
1841
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1842
+ import { join as join8 } from "path";
1843
+ import { execSync as execSync4 } from "child_process";
1736
1844
  var CONVENTION_PATHS2 = [".gut/branch-convention.md", ".github/branch-convention.md"];
1737
1845
  function findBranchConvention(repoRoot) {
1738
1846
  for (const conventionPath of CONVENTION_PATHS2) {
1739
- const fullPath = join7(repoRoot, conventionPath);
1740
- if (existsSync7(fullPath)) {
1741
- return readFileSync7(fullPath, "utf-8");
1847
+ const fullPath = join8(repoRoot, conventionPath);
1848
+ if (existsSync8(fullPath)) {
1849
+ return readFileSync8(fullPath, "utf-8");
1742
1850
  }
1743
1851
  }
1744
1852
  return null;
1745
1853
  }
1746
1854
  function getIssueInfo(issueNumber) {
1747
1855
  try {
1748
- const result = execSync3(`gh issue view ${issueNumber} --json title,body`, {
1856
+ const result = execSync4(`gh issue view ${issueNumber} --json title,body`, {
1749
1857
  encoding: "utf-8",
1750
1858
  stdio: ["pipe", "pipe", "pipe"]
1751
1859
  });
@@ -1840,7 +1948,7 @@ import { Command as Command11 } from "commander";
1840
1948
  import chalk11 from "chalk";
1841
1949
  import ora10 from "ora";
1842
1950
  import { simpleGit as simpleGit10 } from "simple-git";
1843
- var syncCommand = new Command11("sync").description("Sync current branch with remote (fetch + rebase/merge)").option("-m, --merge", "Use merge instead of rebase").option("-a, --all", "Sync all branches").option("--stash", "Auto-stash changes before sync").option("-f, --force", "Force sync even with uncommitted changes").action(async (options) => {
1951
+ var syncCommand = new Command11("sync").description("Sync current branch with remote (fetch + rebase/merge)").option("-m, --merge", "Use merge instead of rebase").option("--no-push", "Skip push after syncing").option("--stash", "Auto-stash changes before sync").option("-f, --force", "Force sync even with uncommitted changes").action(async (options) => {
1844
1952
  const git = simpleGit10();
1845
1953
  const isRepo = await git.checkIsRepo();
1846
1954
  if (!isRepo) {
@@ -1913,12 +2021,23 @@ To set upstream: git push -u origin ${currentBranch}`));
1913
2021
  const ahead = newStatus.ahead || 0;
1914
2022
  const behind = newStatus.behind || 0;
1915
2023
  spinner.succeed(chalk11.green("Synced successfully"));
1916
- if (ahead > 0) {
1917
- console.log(chalk11.cyan(` \u2191 ${ahead} commit(s) ahead - run 'git push' to publish`));
1918
- }
1919
2024
  if (behind > 0) {
1920
2025
  console.log(chalk11.yellow(` \u2193 ${behind} commit(s) behind`));
1921
2026
  }
2027
+ if (ahead > 0) {
2028
+ if (options.push !== false) {
2029
+ const pushSpinner = ora10("Pushing to remote...").start();
2030
+ try {
2031
+ await git.push();
2032
+ pushSpinner.succeed(chalk11.green(`Pushed ${ahead} commit(s)`));
2033
+ } catch (error) {
2034
+ pushSpinner.fail("Push failed");
2035
+ console.error(chalk11.red(error instanceof Error ? error.message : "Unknown error"));
2036
+ }
2037
+ } else {
2038
+ console.log(chalk11.cyan(` \u2191 ${ahead} commit(s) ahead`));
2039
+ }
2040
+ }
1922
2041
  if (stashed) {
1923
2042
  spinner.start("Restoring stashed changes...");
1924
2043
  try {
@@ -2042,8 +2161,87 @@ var stashCommand = new Command12("stash").description("Stash changes with AI-gen
2042
2161
  console.log(chalk12.green(`\u2713 Stashed: ${stashName}`));
2043
2162
  });
2044
2163
 
2164
+ // src/commands/config.ts
2165
+ import { Command as Command13 } from "commander";
2166
+ import chalk13 from "chalk";
2167
+ var configCommand = new Command13("config").description("Manage gut configuration");
2168
+ configCommand.command("set <key> <value>").description("Set a configuration value").option("--local", "Set for current repository only").action((key, value, options) => {
2169
+ if (key === "lang") {
2170
+ if (!isValidLanguage(value)) {
2171
+ console.error(chalk13.red(`Invalid language: ${value}`));
2172
+ console.error(chalk13.gray(`Valid languages: ${VALID_LANGUAGES.join(", ")}`));
2173
+ process.exit(1);
2174
+ }
2175
+ try {
2176
+ setLanguage(value, options.local ?? false);
2177
+ const scope = options.local ? "(local)" : "(global)";
2178
+ console.log(chalk13.green(`\u2713 Language set to: ${value} ${scope}`));
2179
+ } catch (err) {
2180
+ console.error(chalk13.red(err.message));
2181
+ process.exit(1);
2182
+ }
2183
+ } else {
2184
+ console.error(chalk13.red(`Unknown config key: ${key}`));
2185
+ console.error(chalk13.gray("Available keys: lang"));
2186
+ process.exit(1);
2187
+ }
2188
+ });
2189
+ configCommand.command("get <key>").description("Get a configuration value").action((key) => {
2190
+ const config = getConfig();
2191
+ if (key in config) {
2192
+ console.log(config[key]);
2193
+ } else {
2194
+ console.error(chalk13.red(`Unknown config key: ${key}`));
2195
+ process.exit(1);
2196
+ }
2197
+ });
2198
+ configCommand.command("list").description("List all configuration values").action(() => {
2199
+ const globalConfig = getGlobalConfig();
2200
+ const localConfig = getLocalConfig();
2201
+ const effectiveConfig = getConfig();
2202
+ console.log(chalk13.bold("Configuration:"));
2203
+ console.log();
2204
+ for (const key of Object.keys(effectiveConfig)) {
2205
+ const value = effectiveConfig[key];
2206
+ const isLocal = key in localConfig;
2207
+ const scope = isLocal ? chalk13.cyan(" (local)") : chalk13.gray(" (global)");
2208
+ console.log(` ${chalk13.cyan(key)}: ${value}${scope}`);
2209
+ }
2210
+ if (Object.keys(localConfig).length > 0) {
2211
+ console.log();
2212
+ console.log(chalk13.gray("Local config: .gut/config.json"));
2213
+ }
2214
+ });
2215
+
2216
+ // src/commands/lang.ts
2217
+ import { Command as Command14 } from "commander";
2218
+ import chalk14 from "chalk";
2219
+ var langCommand = new Command14("lang").description("Set or show output language").argument("[language]", `Language to set (${VALID_LANGUAGES.join(", ")})`).option("--local", "Set for current repository only").action((language, options) => {
2220
+ if (!language) {
2221
+ const lang = getLanguage();
2222
+ const localConfig = getLocalConfig();
2223
+ const isLocal = "lang" in localConfig;
2224
+ const scope = isLocal ? chalk14.cyan("(local)") : chalk14.gray("(global)");
2225
+ console.log(`${lang} ${scope}`);
2226
+ return;
2227
+ }
2228
+ if (!isValidLanguage(language)) {
2229
+ console.error(chalk14.red(`Invalid language: ${language}`));
2230
+ console.error(chalk14.gray(`Valid languages: ${VALID_LANGUAGES.join(", ")}`));
2231
+ process.exit(1);
2232
+ }
2233
+ try {
2234
+ setLanguage(language, options.local ?? false);
2235
+ const scope = options.local ? "(local)" : "(global)";
2236
+ console.log(chalk14.green(`\u2713 Language set to: ${language} ${scope}`));
2237
+ } catch (err) {
2238
+ console.error(chalk14.red(err.message));
2239
+ process.exit(1);
2240
+ }
2241
+ });
2242
+
2045
2243
  // src/index.ts
2046
- var program = new Command13();
2244
+ var program = new Command15();
2047
2245
  program.name("gut").description("Git Utility Tool - AI-powered git commands").version("0.1.0");
2048
2246
  program.addCommand(cleanupCommand);
2049
2247
  program.addCommand(authCommand);
@@ -2057,5 +2255,7 @@ program.addCommand(aiFindCommand);
2057
2255
  program.addCommand(aiBranchCommand);
2058
2256
  program.addCommand(syncCommand);
2059
2257
  program.addCommand(stashCommand);
2258
+ program.addCommand(configCommand);
2259
+ program.addCommand(langCommand);
2060
2260
  program.parse();
2061
2261
  //# sourceMappingURL=index.js.map