gut-cli 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 = 'en' | 'ja';
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,9 +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 as existsSync2, readFileSync as readFileSync2 } from "fs";
9
- import { join as join2, dirname } from "path";
10
- import { homedir as homedir2 } from "os";
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { join, dirname } from "path";
10
+ import { homedir } from "os";
11
11
  import { fileURLToPath } from "url";
12
12
 
13
13
  // src/lib/credentials.ts
@@ -80,138 +80,42 @@ async function listProviders() {
80
80
  return results;
81
81
  }
82
82
 
83
- // src/lib/config.ts
84
- import { homedir } from "os";
85
- import { join } from "path";
86
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
87
- import { execSync } from "child_process";
88
- var DEFAULT_CONFIG = {
89
- lang: "en"
90
- };
91
- function getGlobalConfigPath() {
92
- const configDir = join(homedir(), ".config", "gut");
93
- return join(configDir, "config.json");
94
- }
95
- function getRepoRoot() {
96
- try {
97
- return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
98
- } catch {
99
- return null;
100
- }
101
- }
102
- function getLocalConfigPath() {
103
- const repoRoot = getRepoRoot();
104
- if (!repoRoot) return null;
105
- return join(repoRoot, ".gut", "config.json");
106
- }
107
- function ensureGlobalConfigDir() {
108
- const configDir = join(homedir(), ".config", "gut");
109
- if (!existsSync(configDir)) {
110
- mkdirSync(configDir, { recursive: true });
111
- }
112
- }
113
- function ensureLocalConfigDir() {
114
- const repoRoot = getRepoRoot();
115
- if (!repoRoot) return;
116
- const gutDir = join(repoRoot, ".gut");
117
- if (!existsSync(gutDir)) {
118
- mkdirSync(gutDir, { recursive: true });
119
- }
120
- }
121
- function readConfigFile(path) {
122
- if (!existsSync(path)) return {};
123
- try {
124
- return JSON.parse(readFileSync(path, "utf-8"));
125
- } catch {
126
- return {};
127
- }
128
- }
129
- function getGlobalConfig() {
130
- const globalPath = getGlobalConfigPath();
131
- return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) };
132
- }
133
- function getLocalConfig() {
134
- const localPath = getLocalConfigPath();
135
- if (!localPath) return {};
136
- return readConfigFile(localPath);
137
- }
138
- function getConfig() {
139
- const globalConfig = getGlobalConfig();
140
- const localConfig = getLocalConfig();
141
- return { ...globalConfig, ...localConfig };
142
- }
143
- function setGlobalConfig(key, value) {
144
- ensureGlobalConfigDir();
145
- const config = getGlobalConfig();
146
- config[key] = value;
147
- writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
148
- }
149
- function setLocalConfig(key, value) {
150
- const localPath = getLocalConfigPath();
151
- if (!localPath) {
152
- throw new Error("Not in a git repository");
153
- }
154
- ensureLocalConfigDir();
155
- const config = getLocalConfig();
156
- config[key] = value;
157
- writeFileSync(localPath, JSON.stringify(config, null, 2));
158
- }
159
- function getLanguage() {
160
- return getConfig().lang;
161
- }
162
- function setLanguage(lang, local = false) {
163
- if (local) {
164
- setLocalConfig("lang", lang);
165
- } else {
166
- setGlobalConfig("lang", lang);
167
- }
168
- }
169
- function getLanguageInstruction(lang) {
170
- switch (lang) {
171
- case "ja":
172
- return "\n\nIMPORTANT: Respond in Japanese (\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u3066\u304F\u3060\u3055\u3044).";
173
- case "en":
174
- default:
175
- return "";
176
- }
177
- }
178
-
179
83
  // src/lib/ai.ts
180
84
  var __filename = fileURLToPath(import.meta.url);
181
85
  var __dirname = dirname(__filename);
182
86
  function findGutRoot() {
183
87
  let current = __dirname;
184
88
  for (let i = 0; i < 5; i++) {
185
- const gutPath = join2(current, ".gut");
186
- if (existsSync2(gutPath)) {
89
+ const gutPath = join(current, ".gut");
90
+ if (existsSync(gutPath)) {
187
91
  return current;
188
92
  }
189
93
  current = dirname(current);
190
94
  }
191
- return join2(__dirname, "..");
95
+ return join(__dirname, "..");
192
96
  }
193
97
  var GUT_ROOT = findGutRoot();
194
98
  function loadTemplate(name) {
195
- const templatePath = join2(GUT_ROOT, ".gut", `${name}.md`);
196
- if (existsSync2(templatePath)) {
197
- return readFileSync2(templatePath, "utf-8");
99
+ const templatePath = join(GUT_ROOT, ".gut", `${name}.md`);
100
+ if (existsSync(templatePath)) {
101
+ return readFileSync(templatePath, "utf-8");
198
102
  }
199
103
  throw new Error(`Template not found: ${templatePath}`);
200
104
  }
201
105
  function getGlobalTemplatesDir() {
202
- return join2(homedir2(), ".config", "gut", "templates");
106
+ return join(homedir(), ".config", "gut", "templates");
203
107
  }
204
108
  function findGlobalTemplate(templateName) {
205
- const templatePath = join2(getGlobalTemplatesDir(), `${templateName}.md`);
206
- if (existsSync2(templatePath)) {
207
- return readFileSync2(templatePath, "utf-8");
109
+ const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`);
110
+ if (existsSync(templatePath)) {
111
+ return readFileSync(templatePath, "utf-8");
208
112
  }
209
113
  return null;
210
114
  }
211
115
  function findTemplate(repoRoot, templateName) {
212
- const projectTemplatePath = join2(repoRoot, ".gut", `${templateName}.md`);
213
- if (existsSync2(projectTemplatePath)) {
214
- return readFileSync2(projectTemplatePath, "utf-8");
116
+ const projectTemplatePath = join(repoRoot, ".gut", `${templateName}.md`);
117
+ if (existsSync(projectTemplatePath)) {
118
+ return readFileSync(projectTemplatePath, "utf-8");
215
119
  }
216
120
  const globalTemplate = findGlobalTemplate(templateName);
217
121
  if (globalTemplate) {
@@ -219,19 +123,20 @@ function findTemplate(repoRoot, templateName) {
219
123
  }
220
124
  return null;
221
125
  }
222
- function applyTemplate(userTemplate, templateName, variables) {
223
- const langInstruction = getLanguageInstruction(getLanguage());
224
- let result = userTemplate || loadTemplate(templateName);
225
- result = result.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_, key, content) => {
226
- return variables[key] ? content : "";
227
- });
228
- for (const [key, value] of Object.entries(variables)) {
229
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value || "");
230
- }
231
- if (langInstruction) {
232
- result += langInstruction;
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
+ }
233
135
  }
234
- return result;
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>";
235
140
  }
236
141
  var DEFAULT_MODELS = {
237
142
  gemini: "gemini-2.0-flash",
@@ -279,9 +184,9 @@ async function getModel(options) {
279
184
  }
280
185
  async function generateCommitMessage(diff, options, template) {
281
186
  const model = await getModel(options);
282
- const prompt = applyTemplate(template, "commit", {
187
+ const prompt = buildPrompt(template, "commit", {
283
188
  diff: diff.slice(0, 8e3)
284
- });
189
+ }, options.language);
285
190
  const result = await generateText({
286
191
  model,
287
192
  prompt,
@@ -291,12 +196,12 @@ async function generateCommitMessage(diff, options, template) {
291
196
  }
292
197
  async function generatePRDescription(context, options, template) {
293
198
  const model = await getModel(options);
294
- const prompt = applyTemplate(template, "pr", {
199
+ const prompt = buildPrompt(template, "pr", {
295
200
  baseBranch: context.baseBranch,
296
201
  currentBranch: context.currentBranch,
297
202
  commits: context.commits.map((c) => `- ${c}`).join("\n"),
298
203
  diff: context.diff.slice(0, 6e3)
299
- });
204
+ }, options.language);
300
205
  const result = await generateText({
301
206
  model,
302
207
  prompt,
@@ -327,9 +232,9 @@ var CodeReviewSchema = z.object({
327
232
  });
328
233
  async function generateCodeReview(diff, options, template) {
329
234
  const model = await getModel(options);
330
- const prompt = applyTemplate(template, "review", {
235
+ const prompt = buildPrompt(template, "review", {
331
236
  diff: diff.slice(0, 1e4)
332
- });
237
+ }, options.language);
333
238
  const result = await generateObject({
334
239
  model,
335
240
  schema: CodeReviewSchema,
@@ -351,13 +256,13 @@ var ChangelogSchema = z.object({
351
256
  async function generateChangelog(context, options, template) {
352
257
  const model = await getModel(options);
353
258
  const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`).join("\n");
354
- const prompt = applyTemplate(template, "changelog", {
259
+ const prompt = buildPrompt(template, "changelog", {
355
260
  fromRef: context.fromRef,
356
261
  toRef: context.toRef,
357
262
  commits: commitList,
358
263
  diff: context.diff.slice(0, 8e3),
359
264
  todayDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
360
- });
265
+ }, options.language);
361
266
  const result = await generateObject({
362
267
  model,
363
268
  schema: ChangelogSchema,
@@ -385,10 +290,10 @@ var ExplanationSchema = z.object({
385
290
  async function generateExplanation(context, options, template) {
386
291
  const model = await getModel(options);
387
292
  if (context.type === "file-content") {
388
- const prompt2 = applyTemplate(template, "explain-file", {
293
+ const prompt2 = buildPrompt(template, "explain-file", {
389
294
  filePath: context.metadata.filePath || "",
390
295
  content: context.content?.slice(0, 15e3) || ""
391
- });
296
+ }, options.language);
392
297
  const result2 = await generateObject({
393
298
  model,
394
299
  schema: ExplanationSchema,
@@ -422,11 +327,11 @@ Author: ${context.metadata.author}
422
327
  Date: ${context.metadata.date}`;
423
328
  targetType = "commit";
424
329
  }
425
- const prompt = applyTemplate(template, "explain", {
330
+ const prompt = buildPrompt(template, "explain", {
426
331
  targetType,
427
- context: contextInfo,
332
+ contextInfo,
428
333
  diff: context.diff?.slice(0, 12e3) || ""
429
- });
334
+ }, options.language);
430
335
  const result = await generateObject({
431
336
  model,
432
337
  schema: ExplanationSchema,
@@ -446,11 +351,11 @@ var CommitSearchSchema = z.object({
446
351
  async function searchCommits(query, commits, options, maxResults = 5, template) {
447
352
  const model = await getModel(options);
448
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");
449
- const prompt = applyTemplate(template, "find", {
354
+ const prompt = buildPrompt(template, "find", {
450
355
  query,
451
356
  commits: commitList,
452
357
  maxResults: String(maxResults)
453
- });
358
+ }, options.language);
454
359
  const result = await generateObject({
455
360
  model,
456
361
  schema: CommitSearchSchema,
@@ -479,11 +384,11 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
479
384
  }
480
385
  async function generateBranchName(description, options, context, template) {
481
386
  const model = await getModel(options);
482
- const prompt = applyTemplate(template, "branch", {
387
+ const prompt = buildPrompt(template, "branch", {
483
388
  description,
484
389
  type: context?.type,
485
390
  issue: context?.issue
486
- });
391
+ }, options.language);
487
392
  const result = await generateText({
488
393
  model,
489
394
  prompt,
@@ -493,9 +398,9 @@ async function generateBranchName(description, options, context, template) {
493
398
  }
494
399
  async function generateBranchNameFromDiff(diff, options, template) {
495
400
  const model = await getModel(options);
496
- const prompt = applyTemplate(template, "checkout", {
401
+ const prompt = buildPrompt(template, "checkout", {
497
402
  diff: diff.slice(0, 8e3)
498
- });
403
+ }, options.language);
499
404
  const result = await generateText({
500
405
  model,
501
406
  prompt,
@@ -505,9 +410,9 @@ async function generateBranchNameFromDiff(diff, options, template) {
505
410
  }
506
411
  async function generateStashName(diff, options, template) {
507
412
  const model = await getModel(options);
508
- const prompt = applyTemplate(template, "stash", {
413
+ const prompt = buildPrompt(template, "stash", {
509
414
  diff: diff.slice(0, 4e3)
510
- });
415
+ }, options.language);
511
416
  const result = await generateText({
512
417
  model,
513
418
  prompt,
@@ -537,13 +442,13 @@ async function generateWorkSummary(context, options, format = "custom", template
537
442
  const commitList = context.commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split("\n")[0]} (${c.date.split("T")[0]})`).join("\n");
538
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}` : ""}.`;
539
444
  const period = `${context.since}${context.until ? ` to ${context.until}` : " to now"}`;
540
- const prompt = applyTemplate(template, "summary", {
445
+ const prompt = buildPrompt(template, "summary", {
541
446
  author: context.author,
542
447
  period,
543
448
  format: formatHint,
544
449
  commits: commitList,
545
450
  diff: context.diff?.slice(0, 6e3)
546
- });
451
+ }, options.language);
547
452
  const result = await generateObject({
548
453
  model,
549
454
  schema: WorkSummarySchema,
@@ -559,12 +464,12 @@ async function generateWorkSummary(context, options, format = "custom", template
559
464
  }
560
465
  async function resolveConflict(conflictedContent, context, options, template) {
561
466
  const model = await getModel(options);
562
- const prompt = applyTemplate(template, "merge", {
467
+ const prompt = buildPrompt(template, "merge", {
563
468
  filename: context.filename,
564
469
  oursRef: context.oursRef,
565
470
  theirsRef: context.theirsRef,
566
471
  content: conflictedContent
567
- });
472
+ }, options.language);
568
473
  const result = await generateObject({
569
474
  model,
570
475
  schema: ConflictResolutionSchema,
@@ -574,11 +479,11 @@ async function resolveConflict(conflictedContent, context, options, template) {
574
479
  }
575
480
  async function generateGitignore(context, options, template) {
576
481
  const model = await getModel(options);
577
- const prompt = applyTemplate(template, "gitignore", {
482
+ const prompt = buildPrompt(template, "gitignore", {
578
483
  files: context.files,
579
484
  configFiles: context.configFiles,
580
485
  existingGitignore: context.existingGitignore
581
- });
486
+ }, options.language);
582
487
  const result = await generateText({
583
488
  model,
584
489
  prompt,
@@ -586,6 +491,102 @@ async function generateGitignore(context, options, template) {
586
491
  });
587
492
  return result.text.trim();
588
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
+ }
589
590
  export {
590
591
  deleteApiKey,
591
592
  findTemplate,