gut-cli 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gut/branch.md +1 -13
- package/.gut/changelog.md +1 -13
- package/.gut/checkout.md +1 -9
- package/.gut/commit.md +1 -7
- package/.gut/explain-file.md +1 -9
- package/.gut/explain.md +1 -11
- package/.gut/find.md +2 -8
- package/.gut/gitignore.md +2 -24
- package/.gut/merge.md +1 -12
- package/.gut/pr.md +1 -13
- package/.gut/review.md +1 -7
- package/.gut/stash.md +1 -7
- package/.gut/summary.md +1 -19
- package/README.md +49 -17
- package/dist/index.js +298 -215
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +7 -6
- package/dist/lib/index.js +162 -146
- package/dist/lib/index.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/index.d.ts
CHANGED
|
@@ -9,11 +9,17 @@ declare function listProviders(): Promise<{
|
|
|
9
9
|
hasKey: boolean;
|
|
10
10
|
}[]>;
|
|
11
11
|
|
|
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
|
+
|
|
12
17
|
interface AIOptions {
|
|
13
18
|
provider: Provider;
|
|
14
19
|
model?: string;
|
|
15
20
|
ollamaBaseUrl?: string;
|
|
16
21
|
apiKey?: string;
|
|
22
|
+
language?: Language;
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
25
|
* Find a user's project template from .gut/ folder
|
|
@@ -292,9 +298,4 @@ declare function generateGitignore(context: {
|
|
|
292
298
|
existingGitignore?: string;
|
|
293
299
|
}, options: AIOptions, template?: string): Promise<string>;
|
|
294
300
|
|
|
295
|
-
type Language
|
|
296
|
-
declare function getLanguage(): Language;
|
|
297
|
-
declare function setLanguage(lang: Language, local?: boolean): void;
|
|
298
|
-
declare function getLanguageInstruction(lang: Language): string;
|
|
299
|
-
|
|
300
|
-
export { type AIOptions, type Changelog, type CodeReview, type CommitSearchResult, type ConflictResolution, type Explanation, type Provider, type WorkSummary, deleteApiKey, findTemplate, generateBranchName, generateBranchNameFromDiff, generateChangelog, generateCodeReview, generateCommitMessage, generateExplanation, generateGitignore, generatePRDescription, generateStashName, generateWorkSummary, getApiKey, getLanguage, getLanguageInstruction, listProviders, resolveConflict, saveApiKey, searchCommits, setLanguage };
|
|
301
|
+
export { type AIOptions, type Changelog, type CodeReview, type CommitSearchResult, type ConflictResolution, type Explanation, type Language, type Provider, type WorkSummary, deleteApiKey, findTemplate, generateBranchName, generateBranchNameFromDiff, generateChangelog, generateCodeReview, generateCommitMessage, generateExplanation, generateGitignore, generatePRDescription, generateStashName, generateWorkSummary, getApiKey, getLanguage, getLanguageInstruction, listProviders, resolveConflict, saveApiKey, searchCommits, setLanguage };
|
package/dist/lib/index.js
CHANGED
|
@@ -5,8 +5,9 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
5
5
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
6
6
|
import { createOllama } from "ollama-ai-provider";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { existsSync
|
|
9
|
-
import { join
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
10
11
|
import { fileURLToPath } from "url";
|
|
11
12
|
|
|
12
13
|
// src/lib/credentials.ts
|
|
@@ -79,144 +80,63 @@ async function listProviders() {
|
|
|
79
80
|
return results;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
// src/lib/config.ts
|
|
83
|
-
import { homedir } from "os";
|
|
84
|
-
import { join } from "path";
|
|
85
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
86
|
-
import { execSync } from "child_process";
|
|
87
|
-
var DEFAULT_CONFIG = {
|
|
88
|
-
lang: "en"
|
|
89
|
-
};
|
|
90
|
-
function getGlobalConfigPath() {
|
|
91
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
92
|
-
return join(configDir, "config.json");
|
|
93
|
-
}
|
|
94
|
-
function getRepoRoot() {
|
|
95
|
-
try {
|
|
96
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
97
|
-
} catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function getLocalConfigPath() {
|
|
102
|
-
const repoRoot = getRepoRoot();
|
|
103
|
-
if (!repoRoot) return null;
|
|
104
|
-
return join(repoRoot, ".gut", "config.json");
|
|
105
|
-
}
|
|
106
|
-
function ensureGlobalConfigDir() {
|
|
107
|
-
const configDir = join(homedir(), ".config", "gut");
|
|
108
|
-
if (!existsSync(configDir)) {
|
|
109
|
-
mkdirSync(configDir, { recursive: true });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function ensureLocalConfigDir() {
|
|
113
|
-
const repoRoot = getRepoRoot();
|
|
114
|
-
if (!repoRoot) return;
|
|
115
|
-
const gutDir = join(repoRoot, ".gut");
|
|
116
|
-
if (!existsSync(gutDir)) {
|
|
117
|
-
mkdirSync(gutDir, { recursive: true });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function readConfigFile(path) {
|
|
121
|
-
if (!existsSync(path)) return {};
|
|
122
|
-
try {
|
|
123
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
124
|
-
} catch {
|
|
125
|
-
return {};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function getGlobalConfig() {
|
|
129
|
-
const globalPath = getGlobalConfigPath();
|
|
130
|
-
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
131
|
-
}
|
|
132
|
-
function getLocalConfig() {
|
|
133
|
-
const localPath = getLocalConfigPath();
|
|
134
|
-
if (!localPath) return {};
|
|
135
|
-
return readConfigFile(localPath);
|
|
136
|
-
}
|
|
137
|
-
function getConfig() {
|
|
138
|
-
const globalConfig = getGlobalConfig();
|
|
139
|
-
const localConfig = getLocalConfig();
|
|
140
|
-
return { ...globalConfig, ...localConfig };
|
|
141
|
-
}
|
|
142
|
-
function setGlobalConfig(key, value) {
|
|
143
|
-
ensureGlobalConfigDir();
|
|
144
|
-
const config = getGlobalConfig();
|
|
145
|
-
config[key] = value;
|
|
146
|
-
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
147
|
-
}
|
|
148
|
-
function setLocalConfig(key, value) {
|
|
149
|
-
const localPath = getLocalConfigPath();
|
|
150
|
-
if (!localPath) {
|
|
151
|
-
throw new Error("Not in a git repository");
|
|
152
|
-
}
|
|
153
|
-
ensureLocalConfigDir();
|
|
154
|
-
const config = getLocalConfig();
|
|
155
|
-
config[key] = value;
|
|
156
|
-
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
157
|
-
}
|
|
158
|
-
function getLanguage() {
|
|
159
|
-
return getConfig().lang;
|
|
160
|
-
}
|
|
161
|
-
function setLanguage(lang, local = false) {
|
|
162
|
-
if (local) {
|
|
163
|
-
setLocalConfig("lang", lang);
|
|
164
|
-
} else {
|
|
165
|
-
setGlobalConfig("lang", lang);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function getLanguageInstruction(lang) {
|
|
169
|
-
switch (lang) {
|
|
170
|
-
case "ja":
|
|
171
|
-
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
172
|
-
case "en":
|
|
173
|
-
default:
|
|
174
|
-
return "";
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
83
|
// src/lib/ai.ts
|
|
179
84
|
var __filename = fileURLToPath(import.meta.url);
|
|
180
85
|
var __dirname = dirname(__filename);
|
|
181
86
|
function findGutRoot() {
|
|
182
87
|
let current = __dirname;
|
|
183
88
|
for (let i = 0; i < 5; i++) {
|
|
184
|
-
const gutPath =
|
|
185
|
-
if (
|
|
89
|
+
const gutPath = join(current, ".gut");
|
|
90
|
+
if (existsSync(gutPath)) {
|
|
186
91
|
return current;
|
|
187
92
|
}
|
|
188
93
|
current = dirname(current);
|
|
189
94
|
}
|
|
190
|
-
return
|
|
95
|
+
return join(__dirname, "..");
|
|
191
96
|
}
|
|
192
97
|
var GUT_ROOT = findGutRoot();
|
|
193
98
|
function loadTemplate(name) {
|
|
194
|
-
const templatePath =
|
|
195
|
-
if (
|
|
196
|
-
return
|
|
99
|
+
const templatePath = join(GUT_ROOT, ".gut", `${name}.md`);
|
|
100
|
+
if (existsSync(templatePath)) {
|
|
101
|
+
return readFileSync(templatePath, "utf-8");
|
|
197
102
|
}
|
|
198
103
|
throw new Error(`Template not found: ${templatePath}`);
|
|
199
104
|
}
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
105
|
+
function getGlobalTemplatesDir() {
|
|
106
|
+
return join(homedir(), ".config", "gut", "templates");
|
|
107
|
+
}
|
|
108
|
+
function findGlobalTemplate(templateName) {
|
|
109
|
+
const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`);
|
|
110
|
+
if (existsSync(templatePath)) {
|
|
111
|
+
return readFileSync(templatePath, "utf-8");
|
|
204
112
|
}
|
|
205
113
|
return null;
|
|
206
114
|
}
|
|
207
|
-
function
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return variables[key] ? content : "";
|
|
212
|
-
});
|
|
213
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
214
|
-
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value || "");
|
|
115
|
+
function findTemplate(repoRoot, templateName) {
|
|
116
|
+
const projectTemplatePath = join(repoRoot, ".gut", `${templateName}.md`);
|
|
117
|
+
if (existsSync(projectTemplatePath)) {
|
|
118
|
+
return readFileSync(projectTemplatePath, "utf-8");
|
|
215
119
|
}
|
|
216
|
-
|
|
217
|
-
|
|
120
|
+
const globalTemplate = findGlobalTemplate(templateName);
|
|
121
|
+
if (globalTemplate) {
|
|
122
|
+
return globalTemplate;
|
|
218
123
|
}
|
|
219
|
-
return
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function buildPrompt(userTemplate, templateName, context, language) {
|
|
127
|
+
let contextXml = "<context>\n";
|
|
128
|
+
for (const [key, value] of Object.entries(context)) {
|
|
129
|
+
if (value) {
|
|
130
|
+
contextXml += `<${key}>
|
|
131
|
+
${value}
|
|
132
|
+
</${key}>
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
contextXml += "</context>\n\n";
|
|
137
|
+
const template = userTemplate || loadTemplate(templateName);
|
|
138
|
+
const langInstruction = language === "ja" ? "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044)." : "";
|
|
139
|
+
return contextXml + "<instructions>\n" + template + langInstruction + "\n</instructions>";
|
|
220
140
|
}
|
|
221
141
|
var DEFAULT_MODELS = {
|
|
222
142
|
gemini: "gemini-2.0-flash",
|
|
@@ -264,9 +184,9 @@ async function getModel(options) {
|
|
|
264
184
|
}
|
|
265
185
|
async function generateCommitMessage(diff, options, template) {
|
|
266
186
|
const model = await getModel(options);
|
|
267
|
-
const prompt =
|
|
187
|
+
const prompt = buildPrompt(template, "commit", {
|
|
268
188
|
diff: diff.slice(0, 8e3)
|
|
269
|
-
});
|
|
189
|
+
}, options.language);
|
|
270
190
|
const result = await generateText({
|
|
271
191
|
model,
|
|
272
192
|
prompt,
|
|
@@ -276,12 +196,12 @@ async function generateCommitMessage(diff, options, template) {
|
|
|
276
196
|
}
|
|
277
197
|
async function generatePRDescription(context, options, template) {
|
|
278
198
|
const model = await getModel(options);
|
|
279
|
-
const prompt =
|
|
199
|
+
const prompt = buildPrompt(template, "pr", {
|
|
280
200
|
baseBranch: context.baseBranch,
|
|
281
201
|
currentBranch: context.currentBranch,
|
|
282
202
|
commits: context.commits.map((c) => `- ${c}`).join("\n"),
|
|
283
203
|
diff: context.diff.slice(0, 6e3)
|
|
284
|
-
});
|
|
204
|
+
}, options.language);
|
|
285
205
|
const result = await generateText({
|
|
286
206
|
model,
|
|
287
207
|
prompt,
|
|
@@ -312,9 +232,9 @@ var CodeReviewSchema = z.object({
|
|
|
312
232
|
});
|
|
313
233
|
async function generateCodeReview(diff, options, template) {
|
|
314
234
|
const model = await getModel(options);
|
|
315
|
-
const prompt =
|
|
235
|
+
const prompt = buildPrompt(template, "review", {
|
|
316
236
|
diff: diff.slice(0, 1e4)
|
|
317
|
-
});
|
|
237
|
+
}, options.language);
|
|
318
238
|
const result = await generateObject({
|
|
319
239
|
model,
|
|
320
240
|
schema: CodeReviewSchema,
|
|
@@ -336,13 +256,13 @@ var ChangelogSchema = z.object({
|
|
|
336
256
|
async function generateChangelog(context, options, template) {
|
|
337
257
|
const model = await getModel(options);
|
|
338
258
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
|
|
339
|
-
const prompt =
|
|
259
|
+
const prompt = buildPrompt(template, "changelog", {
|
|
340
260
|
fromRef: context.fromRef,
|
|
341
261
|
toRef: context.toRef,
|
|
342
262
|
commits: commitList,
|
|
343
263
|
diff: context.diff.slice(0, 8e3),
|
|
344
264
|
todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
345
|
-
});
|
|
265
|
+
}, options.language);
|
|
346
266
|
const result = await generateObject({
|
|
347
267
|
model,
|
|
348
268
|
schema: ChangelogSchema,
|
|
@@ -370,10 +290,10 @@ var ExplanationSchema = z.object({
|
|
|
370
290
|
async function generateExplanation(context, options, template) {
|
|
371
291
|
const model = await getModel(options);
|
|
372
292
|
if (context.type === "file-content") {
|
|
373
|
-
const prompt2 =
|
|
293
|
+
const prompt2 = buildPrompt(template, "explain-file", {
|
|
374
294
|
filePath: context.metadata.filePath || "",
|
|
375
295
|
content: context.content?.slice(0, 15e3) || ""
|
|
376
|
-
});
|
|
296
|
+
}, options.language);
|
|
377
297
|
const result2 = await generateObject({
|
|
378
298
|
model,
|
|
379
299
|
schema: ExplanationSchema,
|
|
@@ -407,11 +327,11 @@ Author: ${context.metadata.author}
|
|
|
407
327
|
Date: ${context.metadata.date}`;
|
|
408
328
|
targetType = "commit";
|
|
409
329
|
}
|
|
410
|
-
const prompt =
|
|
330
|
+
const prompt = buildPrompt(template, "explain", {
|
|
411
331
|
targetType,
|
|
412
|
-
|
|
332
|
+
contextInfo,
|
|
413
333
|
diff: context.diff?.slice(0, 12e3) || ""
|
|
414
|
-
});
|
|
334
|
+
}, options.language);
|
|
415
335
|
const result = await generateObject({
|
|
416
336
|
model,
|
|
417
337
|
schema: ExplanationSchema,
|
|
@@ -431,11 +351,11 @@ var CommitSearchSchema = z.object({
|
|
|
431
351
|
async function searchCommits(query, commits, options, maxResults = 5, template) {
|
|
432
352
|
const model = await getModel(options);
|
|
433
353
|
const commitList = commits.map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split("T")[0]} | ${c.message.split("\n")[0]}`).join("\n");
|
|
434
|
-
const prompt =
|
|
354
|
+
const prompt = buildPrompt(template, "find", {
|
|
435
355
|
query,
|
|
436
356
|
commits: commitList,
|
|
437
357
|
maxResults: String(maxResults)
|
|
438
|
-
});
|
|
358
|
+
}, options.language);
|
|
439
359
|
const result = await generateObject({
|
|
440
360
|
model,
|
|
441
361
|
schema: CommitSearchSchema,
|
|
@@ -464,11 +384,11 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
464
384
|
}
|
|
465
385
|
async function generateBranchName(description, options, context, template) {
|
|
466
386
|
const model = await getModel(options);
|
|
467
|
-
const prompt =
|
|
387
|
+
const prompt = buildPrompt(template, "branch", {
|
|
468
388
|
description,
|
|
469
389
|
type: context?.type,
|
|
470
390
|
issue: context?.issue
|
|
471
|
-
});
|
|
391
|
+
}, options.language);
|
|
472
392
|
const result = await generateText({
|
|
473
393
|
model,
|
|
474
394
|
prompt,
|
|
@@ -478,9 +398,9 @@ async function generateBranchName(description, options, context, template) {
|
|
|
478
398
|
}
|
|
479
399
|
async function generateBranchNameFromDiff(diff, options, template) {
|
|
480
400
|
const model = await getModel(options);
|
|
481
|
-
const prompt =
|
|
401
|
+
const prompt = buildPrompt(template, "checkout", {
|
|
482
402
|
diff: diff.slice(0, 8e3)
|
|
483
|
-
});
|
|
403
|
+
}, options.language);
|
|
484
404
|
const result = await generateText({
|
|
485
405
|
model,
|
|
486
406
|
prompt,
|
|
@@ -490,9 +410,9 @@ async function generateBranchNameFromDiff(diff, options, template) {
|
|
|
490
410
|
}
|
|
491
411
|
async function generateStashName(diff, options, template) {
|
|
492
412
|
const model = await getModel(options);
|
|
493
|
-
const prompt =
|
|
413
|
+
const prompt = buildPrompt(template, "stash", {
|
|
494
414
|
diff: diff.slice(0, 4e3)
|
|
495
|
-
});
|
|
415
|
+
}, options.language);
|
|
496
416
|
const result = await generateText({
|
|
497
417
|
model,
|
|
498
418
|
prompt,
|
|
@@ -522,13 +442,13 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
522
442
|
const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
|
|
523
443
|
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}` : ""}.`;
|
|
524
444
|
const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
|
|
525
|
-
const prompt =
|
|
445
|
+
const prompt = buildPrompt(template, "summary", {
|
|
526
446
|
author: context.author,
|
|
527
447
|
period,
|
|
528
448
|
format: formatHint,
|
|
529
449
|
commits: commitList,
|
|
530
450
|
diff: context.diff?.slice(0, 6e3)
|
|
531
|
-
});
|
|
451
|
+
}, options.language);
|
|
532
452
|
const result = await generateObject({
|
|
533
453
|
model,
|
|
534
454
|
schema: WorkSummarySchema,
|
|
@@ -544,12 +464,12 @@ async function generateWorkSummary(context, options, format = "custom", template
|
|
|
544
464
|
}
|
|
545
465
|
async function resolveConflict(conflictedContent, context, options, template) {
|
|
546
466
|
const model = await getModel(options);
|
|
547
|
-
const prompt =
|
|
467
|
+
const prompt = buildPrompt(template, "merge", {
|
|
548
468
|
filename: context.filename,
|
|
549
469
|
oursRef: context.oursRef,
|
|
550
470
|
theirsRef: context.theirsRef,
|
|
551
471
|
content: conflictedContent
|
|
552
|
-
});
|
|
472
|
+
}, options.language);
|
|
553
473
|
const result = await generateObject({
|
|
554
474
|
model,
|
|
555
475
|
schema: ConflictResolutionSchema,
|
|
@@ -559,11 +479,11 @@ async function resolveConflict(conflictedContent, context, options, template) {
|
|
|
559
479
|
}
|
|
560
480
|
async function generateGitignore(context, options, template) {
|
|
561
481
|
const model = await getModel(options);
|
|
562
|
-
const prompt =
|
|
482
|
+
const prompt = buildPrompt(template, "gitignore", {
|
|
563
483
|
files: context.files,
|
|
564
484
|
configFiles: context.configFiles,
|
|
565
485
|
existingGitignore: context.existingGitignore
|
|
566
|
-
});
|
|
486
|
+
}, options.language);
|
|
567
487
|
const result = await generateText({
|
|
568
488
|
model,
|
|
569
489
|
prompt,
|
|
@@ -571,6 +491,102 @@ async function generateGitignore(context, options, template) {
|
|
|
571
491
|
});
|
|
572
492
|
return result.text.trim();
|
|
573
493
|
}
|
|
494
|
+
|
|
495
|
+
// src/lib/config.ts
|
|
496
|
+
import { homedir as homedir2 } from "os";
|
|
497
|
+
import { join as join2 } from "path";
|
|
498
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
499
|
+
import { execSync } from "child_process";
|
|
500
|
+
var DEFAULT_CONFIG = {
|
|
501
|
+
lang: "en"
|
|
502
|
+
};
|
|
503
|
+
function getGlobalConfigPath() {
|
|
504
|
+
const configDir = join2(homedir2(), ".config", "gut");
|
|
505
|
+
return join2(configDir, "config.json");
|
|
506
|
+
}
|
|
507
|
+
function getRepoRoot() {
|
|
508
|
+
try {
|
|
509
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
510
|
+
} catch {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function getLocalConfigPath() {
|
|
515
|
+
const repoRoot = getRepoRoot();
|
|
516
|
+
if (!repoRoot) return null;
|
|
517
|
+
return join2(repoRoot, ".gut", "config.json");
|
|
518
|
+
}
|
|
519
|
+
function ensureGlobalConfigDir() {
|
|
520
|
+
const configDir = join2(homedir2(), ".config", "gut");
|
|
521
|
+
if (!existsSync2(configDir)) {
|
|
522
|
+
mkdirSync(configDir, { recursive: true });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function ensureLocalConfigDir() {
|
|
526
|
+
const repoRoot = getRepoRoot();
|
|
527
|
+
if (!repoRoot) return;
|
|
528
|
+
const gutDir = join2(repoRoot, ".gut");
|
|
529
|
+
if (!existsSync2(gutDir)) {
|
|
530
|
+
mkdirSync(gutDir, { recursive: true });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function readConfigFile(path) {
|
|
534
|
+
if (!existsSync2(path)) return {};
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
537
|
+
} catch {
|
|
538
|
+
return {};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function getGlobalConfig() {
|
|
542
|
+
const globalPath = getGlobalConfigPath();
|
|
543
|
+
return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
|
|
544
|
+
}
|
|
545
|
+
function getLocalConfig() {
|
|
546
|
+
const localPath = getLocalConfigPath();
|
|
547
|
+
if (!localPath) return {};
|
|
548
|
+
return readConfigFile(localPath);
|
|
549
|
+
}
|
|
550
|
+
function getConfig() {
|
|
551
|
+
const globalConfig = getGlobalConfig();
|
|
552
|
+
const localConfig = getLocalConfig();
|
|
553
|
+
return { ...globalConfig, ...localConfig };
|
|
554
|
+
}
|
|
555
|
+
function setGlobalConfig(key, value) {
|
|
556
|
+
ensureGlobalConfigDir();
|
|
557
|
+
const config = getGlobalConfig();
|
|
558
|
+
config[key] = value;
|
|
559
|
+
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
560
|
+
}
|
|
561
|
+
function setLocalConfig(key, value) {
|
|
562
|
+
const localPath = getLocalConfigPath();
|
|
563
|
+
if (!localPath) {
|
|
564
|
+
throw new Error("Not in a git repository");
|
|
565
|
+
}
|
|
566
|
+
ensureLocalConfigDir();
|
|
567
|
+
const config = getLocalConfig();
|
|
568
|
+
config[key] = value;
|
|
569
|
+
writeFileSync(localPath, JSON.stringify(config, null, 2));
|
|
570
|
+
}
|
|
571
|
+
function getLanguage() {
|
|
572
|
+
return getConfig().lang;
|
|
573
|
+
}
|
|
574
|
+
function setLanguage(lang, local = false) {
|
|
575
|
+
if (local) {
|
|
576
|
+
setLocalConfig("lang", lang);
|
|
577
|
+
} else {
|
|
578
|
+
setGlobalConfig("lang", lang);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function getLanguageInstruction(lang) {
|
|
582
|
+
switch (lang) {
|
|
583
|
+
case "ja":
|
|
584
|
+
return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
|
|
585
|
+
case "en":
|
|
586
|
+
default:
|
|
587
|
+
return "";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
574
590
|
export {
|
|
575
591
|
deleteApiKey,
|
|
576
592
|
findTemplate,
|