gut-cli 0.1.22 → 0.1.24

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.
@@ -1,5 +1,10 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ type Language = 'en' | 'ja';
4
+ declare function getLanguage(): Language;
5
+ declare function setLanguage(lang: Language, local?: boolean): void;
6
+ declare function getLanguageInstruction(lang: Language): string;
7
+
3
8
  type Provider = 'gemini' | 'openai' | 'anthropic' | 'ollama';
4
9
  declare function saveApiKey(provider: Provider, apiKey: string): Promise<void>;
5
10
  declare function getApiKey(provider: Provider): Promise<string | null>;
@@ -9,11 +14,6 @@ declare function listProviders(): Promise<{
9
14
  hasKey: boolean;
10
15
  }[]>;
11
16
 
12
- type Language = 'en' | 'ja';
13
- declare function getLanguage(): Language;
14
- declare function setLanguage(lang: Language, local?: boolean): void;
15
- declare function getLanguageInstruction(lang: Language): string;
16
-
17
17
  interface AIOptions {
18
18
  provider: Provider;
19
19
  model?: string;
@@ -61,7 +61,6 @@ declare const CodeReviewSchema: z.ZodObject<{
61
61
  }>, "many">;
62
62
  positives: z.ZodArray<z.ZodString, "many">;
63
63
  }, "strip", z.ZodTypeAny, {
64
- summary: string;
65
64
  issues: {
66
65
  message: string;
67
66
  severity: "critical" | "warning" | "suggestion";
@@ -69,9 +68,9 @@ declare const CodeReviewSchema: z.ZodObject<{
69
68
  suggestion?: string | undefined;
70
69
  line?: number | undefined;
71
70
  }[];
71
+ summary: string;
72
72
  positives: string[];
73
73
  }, {
74
- summary: string;
75
74
  issues: {
76
75
  message: string;
77
76
  severity: "critical" | "warning" | "suggestion";
@@ -79,6 +78,7 @@ declare const CodeReviewSchema: z.ZodObject<{
79
78
  suggestion?: string | undefined;
80
79
  line?: number | undefined;
81
80
  }[];
81
+ summary: string;
82
82
  positives: string[];
83
83
  }>;
84
84
  type CodeReview = z.infer<typeof CodeReviewSchema>;
package/dist/lib/index.js CHANGED
@@ -1,14 +1,121 @@
1
1
  // src/lib/ai.ts
2
- import { generateText, generateObject } from "ai";
2
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3
+ import { homedir as homedir2 } from "os";
4
+ import { dirname, join as join2 } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { createAnthropic } from "@ai-sdk/anthropic";
3
7
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
4
8
  import { createOpenAI } from "@ai-sdk/openai";
5
- import { createAnthropic } from "@ai-sdk/anthropic";
9
+ import { generateObject, generateText } from "ai";
6
10
  import { createOllama } from "ollama-ai-provider";
7
11
  import { z } from "zod";
8
- import { existsSync, readFileSync } from "fs";
9
- import { join, dirname } from "path";
12
+
13
+ // src/lib/config.ts
14
+ import { execSync } from "child_process";
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
16
  import { homedir } from "os";
11
- import { fileURLToPath } from "url";
17
+ import { join } from "path";
18
+ var DEFAULT_CONFIG = {
19
+ lang: "en"
20
+ };
21
+ var DEFAULT_MODELS = {
22
+ gemini: "gemini-2.5-flash",
23
+ openai: "gpt-4.1-mini",
24
+ anthropic: "claude-sonnet-4-5",
25
+ ollama: "llama3.3"
26
+ };
27
+ function getGlobalConfigPath() {
28
+ const configDir = join(homedir(), ".config", "gut");
29
+ return join(configDir, "config.json");
30
+ }
31
+ function getRepoRoot() {
32
+ try {
33
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ function getLocalConfigPath() {
39
+ const repoRoot = getRepoRoot();
40
+ if (!repoRoot) return null;
41
+ return join(repoRoot, ".gut", "config.json");
42
+ }
43
+ function ensureGlobalConfigDir() {
44
+ const configDir = join(homedir(), ".config", "gut");
45
+ if (!existsSync(configDir)) {
46
+ mkdirSync(configDir, { recursive: true });
47
+ }
48
+ }
49
+ function ensureLocalConfigDir() {
50
+ const repoRoot = getRepoRoot();
51
+ if (!repoRoot) return;
52
+ const gutDir = join(repoRoot, ".gut");
53
+ if (!existsSync(gutDir)) {
54
+ mkdirSync(gutDir, { recursive: true });
55
+ }
56
+ }
57
+ function readConfigFile(path) {
58
+ if (!existsSync(path)) return {};
59
+ try {
60
+ return JSON.parse(readFileSync(path, "utf-8"));
61
+ } catch {
62
+ return {};
63
+ }
64
+ }
65
+ function getGlobalConfig() {
66
+ const globalPath = getGlobalConfigPath();
67
+ return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
68
+ }
69
+ function getLocalConfig() {
70
+ const localPath = getLocalConfigPath();
71
+ if (!localPath) return {};
72
+ return readConfigFile(localPath);
73
+ }
74
+ function getConfig() {
75
+ const globalConfig = getGlobalConfig();
76
+ const localConfig = getLocalConfig();
77
+ return { ...globalConfig, ...localConfig };
78
+ }
79
+ function setGlobalConfig(key, value) {
80
+ ensureGlobalConfigDir();
81
+ const config = getGlobalConfig();
82
+ config[key] = value;
83
+ writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
84
+ }
85
+ function setLocalConfig(key, value) {
86
+ const localPath = getLocalConfigPath();
87
+ if (!localPath) {
88
+ throw new Error("Not in a git repository");
89
+ }
90
+ ensureLocalConfigDir();
91
+ const config = getLocalConfig();
92
+ config[key] = value;
93
+ writeFileSync(localPath, JSON.stringify(config, null, 2));
94
+ }
95
+ function getLanguage() {
96
+ return getConfig().lang;
97
+ }
98
+ function setLanguage(lang, local = false) {
99
+ if (local) {
100
+ setLocalConfig("lang", lang);
101
+ } else {
102
+ setGlobalConfig("lang", lang);
103
+ }
104
+ }
105
+ function getLanguageInstruction(lang) {
106
+ switch (lang) {
107
+ case "ja":
108
+ return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
109
+ default:
110
+ return "";
111
+ }
112
+ }
113
+ function getConfiguredModel() {
114
+ return getConfig().model;
115
+ }
116
+ function getDefaultModel(provider) {
117
+ return DEFAULT_MODELS[provider] || DEFAULT_MODELS.gemini;
118
+ }
12
119
 
13
120
  // src/lib/credentials.ts
14
121
  import { createRequire } from "module";
@@ -86,36 +193,36 @@ var __dirname = dirname(__filename);
86
193
  function findGutRoot() {
87
194
  let current = __dirname;
88
195
  for (let i = 0; i < 5; i++) {
89
- const gutPath = join(current, ".gut");
90
- if (existsSync(gutPath)) {
196
+ const gutPath = join2(current, ".gut");
197
+ if (existsSync2(gutPath)) {
91
198
  return current;
92
199
  }
93
200
  current = dirname(current);
94
201
  }
95
- return join(__dirname, "..");
202
+ return join2(__dirname, "..");
96
203
  }
97
204
  var GUT_ROOT = findGutRoot();
98
205
  function loadTemplate(name) {
99
- const templatePath = join(GUT_ROOT, ".gut", `${name}.md`);
100
- if (existsSync(templatePath)) {
101
- return readFileSync(templatePath, "utf-8");
206
+ const templatePath = join2(GUT_ROOT, ".gut", `${name}.md`);
207
+ if (existsSync2(templatePath)) {
208
+ return readFileSync2(templatePath, "utf-8");
102
209
  }
103
210
  throw new Error(`Template not found: ${templatePath}`);
104
211
  }
105
212
  function getGlobalTemplatesDir() {
106
- return join(homedir(), ".config", "gut", "templates");
213
+ return join2(homedir2(), ".config", "gut", "templates");
107
214
  }
108
215
  function findGlobalTemplate(templateName) {
109
- const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`);
110
- if (existsSync(templatePath)) {
111
- return readFileSync(templatePath, "utf-8");
216
+ const templatePath = join2(getGlobalTemplatesDir(), `${templateName}.md`);
217
+ if (existsSync2(templatePath)) {
218
+ return readFileSync2(templatePath, "utf-8");
112
219
  }
113
220
  return null;
114
221
  }
115
222
  function findTemplate(repoRoot, templateName) {
116
- const projectTemplatePath = join(repoRoot, ".gut", `${templateName}.md`);
117
- if (existsSync(projectTemplatePath)) {
118
- return readFileSync(projectTemplatePath, "utf-8");
223
+ const projectTemplatePath = join2(repoRoot, ".gut", `${templateName}.md`);
224
+ if (existsSync2(projectTemplatePath)) {
225
+ return readFileSync2(projectTemplatePath, "utf-8");
119
226
  }
120
227
  const globalTemplate = findGlobalTemplate(templateName);
121
228
  if (globalTemplate) {
@@ -141,16 +248,12 @@ ${value}
141
248
  <output-format>
142
249
  ${outputFormat}
143
250
  </output-format>` : "";
144
- return contextXml + "<instructions>\n" + template + langInstruction + "\n</instructions>" + outputSection;
251
+ return `${contextXml}<instructions>
252
+ ${template}${langInstruction}
253
+ </instructions>${outputSection}`;
145
254
  }
146
- var DEFAULT_MODELS = {
147
- gemini: "gemini-2.0-flash",
148
- openai: "gpt-4o-mini",
149
- anthropic: "claude-sonnet-4-20250514",
150
- ollama: "llama3.2"
151
- };
152
255
  async function getModel(options) {
153
- const modelName = options.model || DEFAULT_MODELS[options.provider];
256
+ const modelName = options.model || getConfiguredModel() || getDefaultModel(options.provider);
154
257
  async function resolveApiKey() {
155
258
  if (options.apiKey) return options.apiKey;
156
259
  return getApiKey(options.provider);
@@ -189,9 +292,15 @@ async function getModel(options) {
189
292
  }
190
293
  async function generateCommitMessage(diff, options, template) {
191
294
  const model = await getModel(options);
192
- const prompt = buildPrompt(template, "commit", {
193
- diff: diff.slice(0, 8e3)
194
- }, options.language, "Respond with ONLY the commit message, nothing else.");
295
+ const prompt = buildPrompt(
296
+ template,
297
+ "commit",
298
+ {
299
+ diff: diff.slice(0, 8e3)
300
+ },
301
+ options.language,
302
+ "Respond with ONLY the commit message, nothing else."
303
+ );
195
304
  const result = await generateText({
196
305
  model,
197
306
  prompt,
@@ -199,34 +308,29 @@ async function generateCommitMessage(diff, options, template) {
199
308
  });
200
309
  return result.text.trim();
201
310
  }
311
+ var PRDescriptionSchema = z.object({
312
+ title: z.string().describe("Concise PR title (50-72 chars)"),
313
+ body: z.string().describe("PR description in markdown format")
314
+ });
202
315
  async function generatePRDescription(context, options, template) {
203
316
  const model = await getModel(options);
204
- const prompt = buildPrompt(template, "pr", {
205
- baseBranch: context.baseBranch,
206
- currentBranch: context.currentBranch,
207
- commits: context.commits.map((c) => `- ${c}`).join("\n"),
208
- diff: context.diff.slice(0, 6e3)
209
- }, options.language, `Respond in JSON format:
210
- \`\`\`json
211
- {
212
- "title": "...",
213
- "body": "..."
214
- }
215
- \`\`\``);
216
- const result = await generateText({
317
+ const prompt = buildPrompt(
318
+ template,
319
+ "pr",
320
+ {
321
+ baseBranch: context.baseBranch,
322
+ currentBranch: context.currentBranch,
323
+ commits: context.commits.map((c) => `- ${c}`).join("\n"),
324
+ diff: context.diff.slice(0, 6e3)
325
+ },
326
+ options.language
327
+ );
328
+ const result = await generateObject({
217
329
  model,
218
- prompt,
219
- maxTokens: 2e3
330
+ schema: PRDescriptionSchema,
331
+ prompt
220
332
  });
221
- try {
222
- const cleaned = result.text.replace(/```json\n?|\n?```/g, "").trim();
223
- return JSON.parse(cleaned);
224
- } catch {
225
- return {
226
- title: context.currentBranch.replace(/[-_]/g, " "),
227
- body: result.text
228
- };
229
- }
333
+ return result.object;
230
334
  }
231
335
  var CodeReviewSchema = z.object({
232
336
  summary: z.string().describe("Brief overall assessment"),
@@ -243,9 +347,14 @@ var CodeReviewSchema = z.object({
243
347
  });
244
348
  async function generateCodeReview(diff, options, template) {
245
349
  const model = await getModel(options);
246
- const prompt = buildPrompt(template, "review", {
247
- diff: diff.slice(0, 1e4)
248
- }, options.language);
350
+ const prompt = buildPrompt(
351
+ template,
352
+ "review",
353
+ {
354
+ diff: diff.slice(0, 1e4)
355
+ },
356
+ options.language
357
+ );
249
358
  const result = await generateObject({
250
359
  model,
251
360
  schema: CodeReviewSchema,
@@ -267,13 +376,18 @@ var ChangelogSchema = z.object({
267
376
  async function generateChangelog(context, options, template) {
268
377
  const model = await getModel(options);
269
378
  const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
270
- const prompt = buildPrompt(template, "changelog", {
271
- fromRef: context.fromRef,
272
- toRef: context.toRef,
273
- commits: commitList,
274
- diff: context.diff.slice(0, 8e3),
275
- todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
276
- }, options.language);
379
+ const prompt = buildPrompt(
380
+ template,
381
+ "changelog",
382
+ {
383
+ fromRef: context.fromRef,
384
+ toRef: context.toRef,
385
+ commits: commitList,
386
+ diff: context.diff.slice(0, 8e3),
387
+ todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
388
+ },
389
+ options.language
390
+ );
277
391
  const result = await generateObject({
278
392
  model,
279
393
  schema: ChangelogSchema,
@@ -301,10 +415,15 @@ var ExplanationSchema = z.object({
301
415
  async function generateExplanation(context, options, template) {
302
416
  const model = await getModel(options);
303
417
  if (context.type === "file-content") {
304
- const prompt2 = buildPrompt(template, "explain-file", {
305
- filePath: context.metadata.filePath || "",
306
- content: context.content?.slice(0, 15e3) || ""
307
- }, options.language);
418
+ const prompt2 = buildPrompt(
419
+ template,
420
+ "explain-file",
421
+ {
422
+ filePath: context.metadata.filePath || "",
423
+ content: context.content?.slice(0, 15e3) || ""
424
+ },
425
+ options.language
426
+ );
308
427
  const result2 = await generateObject({
309
428
  model,
310
429
  schema: ExplanationSchema,
@@ -338,11 +457,16 @@ Author: ${context.metadata.author}
338
457
  Date: ${context.metadata.date}`;
339
458
  targetType = "commit";
340
459
  }
341
- const prompt = buildPrompt(template, "explain", {
342
- targetType,
343
- contextInfo,
344
- diff: context.diff?.slice(0, 12e3) || ""
345
- }, options.language);
460
+ const prompt = buildPrompt(
461
+ template,
462
+ "explain",
463
+ {
464
+ targetType,
465
+ contextInfo,
466
+ diff: context.diff?.slice(0, 12e3) || ""
467
+ },
468
+ options.language
469
+ );
346
470
  const result = await generateObject({
347
471
  model,
348
472
  schema: ExplanationSchema,
@@ -361,12 +485,19 @@ var CommitSearchSchema = z.object({
361
485
  });
362
486
  async function searchCommits(query, commits, options, maxResults = 5, template) {
363
487
  const model = await getModel(options);
364
- const commitList = commits.map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`).join("\n");
365
- const prompt = buildPrompt(template, "find", {
366
- query,
367
- commits: commitList,
368
- maxResults: String(maxResults)
369
- }, options.language);
488
+ const commitList = commits.map(
489
+ (c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`
490
+ ).join("\n");
491
+ const prompt = buildPrompt(
492
+ template,
493
+ "find",
494
+ {
495
+ query,
496
+ commits: commitList,
497
+ maxResults: String(maxResults)
498
+ },
499
+ options.language
500
+ );
370
501
  const result = await generateObject({
371
502
  model,
372
503
  schema: CommitSearchSchema,
@@ -395,11 +526,17 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
395
526
  }
396
527
  async function generateBranchName(description, options, context, template) {
397
528
  const model = await getModel(options);
398
- const prompt = buildPrompt(template, "branch", {
399
- description,
400
- type: context?.type,
401
- issue: context?.issue
402
- }, options.language, "Respond with ONLY the branch name, nothing else.");
529
+ const prompt = buildPrompt(
530
+ template,
531
+ "branch",
532
+ {
533
+ description,
534
+ type: context?.type,
535
+ issue: context?.issue
536
+ },
537
+ options.language,
538
+ "Respond with ONLY the branch name, nothing else."
539
+ );
403
540
  const result = await generateText({
404
541
  model,
405
542
  prompt,
@@ -409,9 +546,15 @@ async function generateBranchName(description, options, context, template) {
409
546
  }
410
547
  async function generateBranchNameFromDiff(diff, options, template) {
411
548
  const model = await getModel(options);
412
- const prompt = buildPrompt(template, "checkout", {
413
- diff: diff.slice(0, 8e3)
414
- }, options.language, "Respond with ONLY the branch name, nothing else.");
549
+ const prompt = buildPrompt(
550
+ template,
551
+ "checkout",
552
+ {
553
+ diff: diff.slice(0, 8e3)
554
+ },
555
+ options.language,
556
+ "Respond with ONLY the branch name, nothing else."
557
+ );
415
558
  const result = await generateText({
416
559
  model,
417
560
  prompt,
@@ -421,9 +564,15 @@ async function generateBranchNameFromDiff(diff, options, template) {
421
564
  }
422
565
  async function generateStashName(diff, options, template) {
423
566
  const model = await getModel(options);
424
- const prompt = buildPrompt(template, "stash", {
425
- diff: diff.slice(0, 4e3)
426
- }, options.language, "Respond with ONLY the stash name, nothing else.");
567
+ const prompt = buildPrompt(
568
+ template,
569
+ "stash",
570
+ {
571
+ diff: diff.slice(0, 4e3)
572
+ },
573
+ options.language,
574
+ "Respond with ONLY the stash name, nothing else."
575
+ );
427
576
  const result = await generateText({
428
577
  model,
429
578
  prompt,
@@ -453,13 +602,18 @@ async function generateWorkSummary(context, options, format = "custom", template
453
602
  const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
454
603
  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}` : ""}.`;
455
604
  const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
456
- const prompt = buildPrompt(template, "summary", {
457
- author: context.author,
458
- period,
459
- format: formatHint,
460
- commits: commitList,
461
- diff: context.diff?.slice(0, 6e3)
462
- }, options.language);
605
+ const prompt = buildPrompt(
606
+ template,
607
+ "summary",
608
+ {
609
+ author: context.author,
610
+ period,
611
+ format: formatHint,
612
+ commits: commitList,
613
+ diff: context.diff?.slice(0, 6e3)
614
+ },
615
+ options.language
616
+ );
463
617
  const result = await generateObject({
464
618
  model,
465
619
  schema: WorkSummarySchema,
@@ -475,12 +629,17 @@ async function generateWorkSummary(context, options, format = "custom", template
475
629
  }
476
630
  async function resolveConflict(conflictedContent, context, options, template) {
477
631
  const model = await getModel(options);
478
- const prompt = buildPrompt(template, "merge", {
479
- filename: context.filename,
480
- oursRef: context.oursRef,
481
- theirsRef: context.theirsRef,
482
- content: conflictedContent
483
- }, options.language);
632
+ const prompt = buildPrompt(
633
+ template,
634
+ "merge",
635
+ {
636
+ filename: context.filename,
637
+ oursRef: context.oursRef,
638
+ theirsRef: context.theirsRef,
639
+ content: conflictedContent
640
+ },
641
+ options.language
642
+ );
484
643
  const result = await generateObject({
485
644
  model,
486
645
  schema: ConflictResolutionSchema,
@@ -490,11 +649,17 @@ async function resolveConflict(conflictedContent, context, options, template) {
490
649
  }
491
650
  async function generateGitignore(context, options, template) {
492
651
  const model = await getModel(options);
493
- const prompt = buildPrompt(template, "gitignore", {
494
- files: context.files,
495
- configFiles: context.configFiles,
496
- existingGitignore: context.existingGitignore
497
- }, options.language, "Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks.");
652
+ const prompt = buildPrompt(
653
+ template,
654
+ "gitignore",
655
+ {
656
+ files: context.files,
657
+ configFiles: context.configFiles,
658
+ existingGitignore: context.existingGitignore
659
+ },
660
+ options.language,
661
+ "Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks."
662
+ );
498
663
  const result = await generateText({
499
664
  model,
500
665
  prompt,
@@ -502,102 +667,6 @@ async function generateGitignore(context, options, template) {
502
667
  });
503
668
  return result.text.trim();
504
669
  }
505
-
506
- // src/lib/config.ts
507
- import { homedir as homedir2 } from "os";
508
- import { join as join2 } from "path";
509
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
510
- import { execSync } from "child_process";
511
- var DEFAULT_CONFIG = {
512
- lang: "en"
513
- };
514
- function getGlobalConfigPath() {
515
- const configDir = join2(homedir2(), ".config", "gut");
516
- return join2(configDir, "config.json");
517
- }
518
- function getRepoRoot() {
519
- try {
520
- return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
521
- } catch {
522
- return null;
523
- }
524
- }
525
- function getLocalConfigPath() {
526
- const repoRoot = getRepoRoot();
527
- if (!repoRoot) return null;
528
- return join2(repoRoot, ".gut", "config.json");
529
- }
530
- function ensureGlobalConfigDir() {
531
- const configDir = join2(homedir2(), ".config", "gut");
532
- if (!existsSync2(configDir)) {
533
- mkdirSync(configDir, { recursive: true });
534
- }
535
- }
536
- function ensureLocalConfigDir() {
537
- const repoRoot = getRepoRoot();
538
- if (!repoRoot) return;
539
- const gutDir = join2(repoRoot, ".gut");
540
- if (!existsSync2(gutDir)) {
541
- mkdirSync(gutDir, { recursive: true });
542
- }
543
- }
544
- function readConfigFile(path) {
545
- if (!existsSync2(path)) return {};
546
- try {
547
- return JSON.parse(readFileSync2(path, "utf-8"));
548
- } catch {
549
- return {};
550
- }
551
- }
552
- function getGlobalConfig() {
553
- const globalPath = getGlobalConfigPath();
554
- return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
555
- }
556
- function getLocalConfig() {
557
- const localPath = getLocalConfigPath();
558
- if (!localPath) return {};
559
- return readConfigFile(localPath);
560
- }
561
- function getConfig() {
562
- const globalConfig = getGlobalConfig();
563
- const localConfig = getLocalConfig();
564
- return { ...globalConfig, ...localConfig };
565
- }
566
- function setGlobalConfig(key, value) {
567
- ensureGlobalConfigDir();
568
- const config = getGlobalConfig();
569
- config[key] = value;
570
- writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
571
- }
572
- function setLocalConfig(key, value) {
573
- const localPath = getLocalConfigPath();
574
- if (!localPath) {
575
- throw new Error("Not in a git repository");
576
- }
577
- ensureLocalConfigDir();
578
- const config = getLocalConfig();
579
- config[key] = value;
580
- writeFileSync(localPath, JSON.stringify(config, null, 2));
581
- }
582
- function getLanguage() {
583
- return getConfig().lang;
584
- }
585
- function setLanguage(lang, local = false) {
586
- if (local) {
587
- setLocalConfig("lang", lang);
588
- } else {
589
- setGlobalConfig("lang", lang);
590
- }
591
- }
592
- function getLanguageInstruction(lang) {
593
- switch (lang) {
594
- case "ja":
595
- return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
596
- case "en":
597
- default:
598
- return "";
599
- }
600
- }
601
670
  export {
602
671
  deleteApiKey,
603
672
  findTemplate,