gut-cli 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/index.js CHANGED
@@ -123,7 +123,7 @@ function findTemplate(repoRoot, templateName) {
123
123
  }
124
124
  return null;
125
125
  }
126
- function buildPrompt(userTemplate, templateName, context, language) {
126
+ function buildPrompt(userTemplate, templateName, context, language, outputFormat) {
127
127
  let contextXml = "<context>\n";
128
128
  for (const [key, value] of Object.entries(context)) {
129
129
  if (value) {
@@ -136,7 +136,12 @@ ${value}
136
136
  contextXml += "</context>\n\n";
137
137
  const template = userTemplate || loadTemplate(templateName);
138
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>";
139
+ const outputSection = outputFormat ? `
140
+
141
+ <output-format>
142
+ ${outputFormat}
143
+ </output-format>` : "";
144
+ return contextXml + "<instructions>\n" + template + langInstruction + "\n</instructions>" + outputSection;
140
145
  }
141
146
  var DEFAULT_MODELS = {
142
147
  gemini: "gemini-2.0-flash",
@@ -186,7 +191,7 @@ async function generateCommitMessage(diff, options, template) {
186
191
  const model = await getModel(options);
187
192
  const prompt = buildPrompt(template, "commit", {
188
193
  diff: diff.slice(0, 8e3)
189
- }, options.language);
194
+ }, options.language, "Respond with ONLY the commit message, nothing else.");
190
195
  const result = await generateText({
191
196
  model,
192
197
  prompt,
@@ -201,7 +206,13 @@ async function generatePRDescription(context, options, template) {
201
206
  currentBranch: context.currentBranch,
202
207
  commits: context.commits.map((c) => `- ${c}`).join("\n"),
203
208
  diff: context.diff.slice(0, 6e3)
204
- }, options.language);
209
+ }, options.language, `Respond in JSON format:
210
+ \`\`\`json
211
+ {
212
+ "title": "...",
213
+ "body": "..."
214
+ }
215
+ \`\`\``);
205
216
  const result = await generateText({
206
217
  model,
207
218
  prompt,
@@ -388,7 +399,7 @@ async function generateBranchName(description, options, context, template) {
388
399
  description,
389
400
  type: context?.type,
390
401
  issue: context?.issue
391
- }, options.language);
402
+ }, options.language, "Respond with ONLY the branch name, nothing else.");
392
403
  const result = await generateText({
393
404
  model,
394
405
  prompt,
@@ -400,7 +411,7 @@ async function generateBranchNameFromDiff(diff, options, template) {
400
411
  const model = await getModel(options);
401
412
  const prompt = buildPrompt(template, "checkout", {
402
413
  diff: diff.slice(0, 8e3)
403
- }, options.language);
414
+ }, options.language, "Respond with ONLY the branch name, nothing else.");
404
415
  const result = await generateText({
405
416
  model,
406
417
  prompt,
@@ -412,7 +423,7 @@ async function generateStashName(diff, options, template) {
412
423
  const model = await getModel(options);
413
424
  const prompt = buildPrompt(template, "stash", {
414
425
  diff: diff.slice(0, 4e3)
415
- }, options.language);
426
+ }, options.language, "Respond with ONLY the stash name, nothing else.");
416
427
  const result = await generateText({
417
428
  model,
418
429
  prompt,
@@ -483,7 +494,7 @@ async function generateGitignore(context, options, template) {
483
494
  files: context.files,
484
495
  configFiles: context.configFiles,
485
496
  existingGitignore: context.existingGitignore
486
- }, options.language);
497
+ }, options.language, "Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks.");
487
498
  const result = await generateText({
488
499
  model,
489
500
  prompt,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/ai.ts","../../src/lib/credentials.ts","../../src/lib/config.ts"],"sourcesContent":["import { generateText, generateObject } from 'ai'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createOllama } from 'ollama-ai-provider'\nimport { z } from 'zod'\nimport { existsSync, readFileSync } from 'fs'\nimport { join, dirname } from 'path'\nimport { homedir } from 'os'\nimport { fileURLToPath } from 'url'\nimport { getApiKey, Provider } from './credentials.js'\nimport { type Language } from './config.js'\n\nexport type { Language }\n\nexport interface AIOptions {\n provider: Provider\n model?: string\n ollamaBaseUrl?: string // For Ollama provider\n apiKey?: string // Optional: directly provide API key (bypasses keytar/env lookup)\n language?: Language // Language for AI responses ('en' | 'ja')\n}\n\n// Get the directory where gut is installed (for reading default templates)\n// Works for both CLI (dist/index.js) and library (dist/lib/index.js) entry points\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nfunction findGutRoot(): string {\n // Search upward from __dirname until we find a directory containing .gut/\n let current = __dirname\n for (let i = 0; i < 5; i++) {\n const gutPath = join(current, '.gut')\n if (existsSync(gutPath)) {\n return current\n }\n current = dirname(current)\n }\n // Fallback: assume we're in dist/lib/ or dist/\n return join(__dirname, '..')\n}\n\nconst GUT_ROOT = findGutRoot()\n\n/**\n * Load a default template from gut's own .gut/ folder\n */\nfunction loadTemplate(name: string): string {\n const templatePath = join(GUT_ROOT, '.gut', `${name}.md`)\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, 'utf-8')\n }\n throw new Error(`Template not found: ${templatePath}`)\n}\n\n/**\n * Get the global templates directory path\n */\nfunction getGlobalTemplatesDir(): string {\n return join(homedir(), '.config', 'gut', 'templates')\n}\n\n/**\n * Find a global template from ~/.config/gut/templates/\n * @param templateName - Name of the template file (without .md extension)\n * @returns Template content if found, null otherwise\n */\nexport function findGlobalTemplate(templateName: string): string | null {\n const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`)\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, 'utf-8')\n }\n return null\n}\n\n/**\n * Find a user's project template from .gut/ folder\n * @param repoRoot - The root directory of the user's repository\n * @param templateName - Name of the template file (without .md extension)\n * @returns Template content if found, null otherwise\n */\nexport function findTemplate(repoRoot: string, templateName: string): string | null {\n // Priority 1: Project-level template (.gut/)\n const projectTemplatePath = join(repoRoot, '.gut', `${templateName}.md`)\n if (existsSync(projectTemplatePath)) {\n return readFileSync(projectTemplatePath, 'utf-8')\n }\n\n // Priority 2: Global template (~/.config/gut/templates/)\n const globalTemplate = findGlobalTemplate(templateName)\n if (globalTemplate) {\n return globalTemplate\n }\n\n return null\n}\n\n/**\n * Build a prompt with context in XML format and instructions from template.\n * Templates don't need to include variables - context is automatically prepended.\n *\n * @param userTemplate - User-provided template string or null/undefined\n * @param templateName - Name of the default template file in .gut/ (without .md extension)\n * @param context - Context data to include (diff, commits, etc.)\n * @param language - Language for AI responses\n * @returns Complete prompt with XML context and instructions\n */\nfunction buildPrompt(\n userTemplate: string | null | undefined,\n templateName: string,\n context: Record<string, string | undefined>,\n language?: Language\n): string {\n // Build XML context\n let contextXml = '<context>\\n'\n for (const [key, value] of Object.entries(context)) {\n if (value) {\n contextXml += `<${key}>\\n${value}\\n</${key}>\\n`\n }\n }\n contextXml += '</context>\\n\\n'\n\n // Get template (user template takes priority)\n const template = userTemplate || loadTemplate(templateName)\n\n // Build language instruction\n const langInstruction = language === 'ja'\n ? '\\n\\nIMPORTANT: Respond in Japanese (日本語で回答してください).'\n : ''\n\n return contextXml + '<instructions>\\n' + template + langInstruction + '\\n</instructions>'\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n gemini: 'gemini-2.0-flash',\n openai: 'gpt-4o-mini',\n anthropic: 'claude-sonnet-4-20250514',\n ollama: 'llama3.2'\n}\n\nasync function getModel(options: AIOptions) {\n const modelName = options.model || DEFAULT_MODELS[options.provider]\n\n // Helper to get API key: use provided key or fall back to keytar/env\n async function resolveApiKey(): Promise<string | null> {\n if (options.apiKey) return options.apiKey\n return getApiKey(options.provider)\n }\n\n // Ollama doesn't require an API key\n if (options.provider !== 'ollama') {\n const apiKey = await resolveApiKey()\n if (!apiKey) {\n throw new Error(\n `No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`\n )\n }\n }\n\n switch (options.provider) {\n case 'gemini': {\n const apiKey = await resolveApiKey()\n const google = createGoogleGenerativeAI({ apiKey: apiKey! })\n return google(modelName)\n }\n case 'openai': {\n const apiKey = await resolveApiKey()\n const openai = createOpenAI({ apiKey: apiKey! })\n return openai(modelName)\n }\n case 'anthropic': {\n const apiKey = await resolveApiKey()\n const anthropic = createAnthropic({ apiKey: apiKey! })\n return anthropic(modelName)\n }\n case 'ollama': {\n const ollama = createOllama({\n baseURL: options.ollamaBaseUrl || 'http://localhost:11434/api'\n })\n return ollama(modelName)\n }\n }\n}\n\nexport async function generateCommitMessage(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'commit', {\n diff: diff.slice(0, 8000)\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 500\n })\n\n return result.text.trim()\n}\n\nexport async function generatePRDescription(\n context: {\n baseBranch: string\n currentBranch: string\n commits: string[]\n diff: string\n },\n options: AIOptions,\n template?: string\n): Promise<{ title: string; body: string }> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'pr', {\n baseBranch: context.baseBranch,\n currentBranch: context.currentBranch,\n commits: context.commits.map((c) => `- ${c}`).join('\\n'),\n diff: context.diff.slice(0, 6000)\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 2000\n })\n\n try {\n const cleaned = result.text.replace(/```json\\n?|\\n?```/g, '').trim()\n return JSON.parse(cleaned)\n } catch {\n return {\n title: context.currentBranch.replace(/[-_]/g, ' '),\n body: result.text\n }\n }\n}\n\nconst CodeReviewSchema = z.object({\n summary: z.string().describe('Brief overall assessment'),\n issues: z.array(\n z.object({\n severity: z.enum(['critical', 'warning', 'suggestion']),\n file: z.string(),\n line: z.number().optional(),\n message: z.string(),\n suggestion: z.string().optional()\n })\n ),\n positives: z.array(z.string()).describe('Good practices observed')\n})\n\nexport type CodeReview = z.infer<typeof CodeReviewSchema>\n\nexport async function generateCodeReview(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<CodeReview> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'review', {\n diff: diff.slice(0, 10000)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: CodeReviewSchema,\n prompt\n })\n\n return result.object\n}\n\nconst ChangelogSchema = z.object({\n version: z.string().optional().describe('Version string if detected'),\n date: z.string().describe('Release date in YYYY-MM-DD format'),\n sections: z.array(\n z.object({\n type: z.string().describe('Section type (Added, Changed, Fixed, Removed, etc.)'),\n items: z.array(z.string()).describe('List of changes in this section')\n })\n ),\n summary: z.string().optional().describe('Brief summary of this release')\n})\n\nexport type Changelog = z.infer<typeof ChangelogSchema>\n\nexport async function generateChangelog(\n context: {\n commits: Array<{ hash: string; message: string; author: string; date: string }>\n diff: string\n fromRef: string\n toRef: string\n },\n options: AIOptions,\n template?: string\n): Promise<Changelog> {\n const model = await getModel(options)\n\n const commitList = context.commits\n .map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`)\n .join('\\n')\n\n const prompt = buildPrompt(template, 'changelog', {\n fromRef: context.fromRef,\n toRef: context.toRef,\n commits: commitList,\n diff: context.diff.slice(0, 8000),\n todayDate: new Date().toISOString().split('T')[0]\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ChangelogSchema,\n prompt\n })\n\n return result.object\n}\n\nconst ConflictResolutionSchema = z.object({\n resolvedContent: z.string().describe('The resolved file content'),\n explanation: z.string().describe('Brief explanation of how the conflict was resolved'),\n strategy: z.enum(['ours', 'theirs', 'combined', 'rewritten']).describe('Resolution strategy used')\n})\n\nexport type ConflictResolution = z.infer<typeof ConflictResolutionSchema>\n\nconst ExplanationSchema = z.object({\n summary: z.string().describe('One-line summary of what this file/commit/PR does'),\n purpose: z.string().describe('The purpose and role of this code'),\n changes: z.array(\n z.object({\n file: z.string(),\n description: z.string().describe('Description of this file or component')\n })\n ),\n impact: z.string().describe('What impact or role this has in the project'),\n notes: z.array(z.string()).optional().describe('Important considerations or caveats')\n})\n\nexport type Explanation = z.infer<typeof ExplanationSchema>\n\nexport async function generateExplanation(\n context: {\n type: 'commit' | 'pr' | 'file-history' | 'file-content' | 'uncommitted' | 'staged'\n title: string\n diff?: string\n content?: string\n metadata: {\n hash?: string\n author?: string\n date?: string\n prNumber?: string\n baseBranch?: string\n headBranch?: string\n commits?: string[]\n filePath?: string\n }\n },\n options: AIOptions,\n template?: string\n): Promise<Explanation> {\n const model = await getModel(options)\n\n // Handle file content explanation\n if (context.type === 'file-content') {\n const prompt = buildPrompt(template, 'explain-file', {\n filePath: context.metadata.filePath || '',\n content: context.content?.slice(0, 15000) || ''\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ExplanationSchema,\n prompt\n })\n return result.object\n }\n\n // Build context info for diff-based explanations\n let contextInfo: string\n let targetType: string\n\n if (context.type === 'pr') {\n contextInfo = `Pull Request: #${context.metadata.prNumber}\nTitle: ${context.title}\nBranch: ${context.metadata.headBranch} -> ${context.metadata.baseBranch}\nCommits:\n${context.metadata.commits?.map((c) => `- ${c}`).join('\\n') || 'N/A'}`\n targetType = 'pull request'\n } else if (context.type === 'file-history') {\n contextInfo = `File: ${context.metadata.filePath}\nRecent commits:\n${context.metadata.commits?.map((c) => `- ${c}`).join('\\n') || 'N/A'}\nLatest author: ${context.metadata.author}\nLatest date: ${context.metadata.date}`\n targetType = 'file changes'\n } else if (context.type === 'uncommitted' || context.type === 'staged') {\n contextInfo = context.type === 'staged' ? 'Staged changes (ready to commit)' : 'Uncommitted changes (work in progress)'\n targetType = context.type === 'staged' ? 'staged changes' : 'uncommitted changes'\n } else {\n contextInfo = `Commit: ${context.metadata.hash?.slice(0, 7)}\nMessage: ${context.title}\nAuthor: ${context.metadata.author}\nDate: ${context.metadata.date}`\n targetType = 'commit'\n }\n\n const prompt = buildPrompt(template, 'explain', {\n targetType,\n contextInfo,\n diff: context.diff?.slice(0, 12000) || ''\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ExplanationSchema,\n prompt\n })\n\n return result.object\n}\n\nconst CommitSearchSchema = z.object({\n matches: z.array(\n z.object({\n hash: z.string().describe('Commit hash'),\n reason: z.string().describe('Why this commit matches the query')\n })\n ),\n summary: z.string().optional().describe('Brief summary of the search results')\n})\n\nexport interface CommitSearchResult {\n matches: Array<{\n hash: string\n message: string\n author: string\n email: string\n date: string\n reason: string\n relevance?: 'high' | 'medium' | 'low'\n }>\n summary?: string\n}\n\nexport async function searchCommits(\n query: string,\n commits: Array<{\n hash: string\n message: string\n author: string\n email: string\n date: string\n }>,\n options: AIOptions,\n maxResults: number = 5,\n template?: string\n): Promise<CommitSearchResult> {\n const model = await getModel(options)\n\n const commitList = commits\n .map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split('T')[0]} | ${c.message.split('\\n')[0]}`)\n .join('\\n')\n\n const prompt = buildPrompt(template, 'find', {\n query,\n commits: commitList,\n maxResults: String(maxResults)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: CommitSearchSchema,\n prompt\n })\n\n // Enrich results with full commit data and assign relevance based on position\n const enrichedMatches = result.object.matches.map((match, index) => {\n const commit = commits.find((c) => c.hash.startsWith(match.hash))\n if (!commit) {\n return null\n }\n const relevance: 'high' | 'medium' | 'low' = index === 0 ? 'high' : index < 3 ? 'medium' : 'low'\n return {\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n email: commit.email,\n date: commit.date,\n reason: match.reason,\n relevance\n }\n }).filter((m): m is NonNullable<typeof m> => m !== null)\n\n return {\n matches: enrichedMatches,\n summary: result.object.summary\n }\n}\n\nexport async function generateBranchName(\n description: string,\n options: AIOptions,\n context?: {\n type?: string\n issue?: string\n },\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'branch', {\n description,\n type: context?.type,\n issue: context?.issue\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim().replace(/[^a-zA-Z0-9/_-]/g, '')\n}\n\nexport async function generateBranchNameFromDiff(\n diff: string,\n options: AIOptions,\n template?: string | null\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'checkout', {\n diff: diff.slice(0, 8000)\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim().replace(/[^a-zA-Z0-9/_-]/g, '')\n}\n\nexport async function generateStashName(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'stash', {\n diff: diff.slice(0, 4000)\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim()\n}\n\nconst WorkSummarySchema = z.object({\n title: z.string().describe('One-line title for the summary'),\n overview: z.string().describe('Brief overview of what was accomplished'),\n highlights: z.array(z.string()).describe('Key accomplishments or highlights'),\n details: z.array(\n z.object({\n category: z.string().describe('Category (e.g., Feature, Bug Fix, Refactor)'),\n items: z.array(z.string()).describe('List of items in this category')\n })\n ),\n stats: z.object({\n commits: z.number(),\n filesChanged: z.number().optional(),\n additions: z.number().optional(),\n deletions: z.number().optional()\n }).optional()\n})\n\nexport type WorkSummary = z.infer<typeof WorkSummarySchema>\n\nexport async function generateWorkSummary(\n context: {\n commits: Array<{ hash: string; message: string; date: string }>\n author: string\n since: string\n until?: string\n diff?: string\n },\n options: AIOptions,\n format: 'daily' | 'weekly' | 'custom' = 'custom',\n template?: string\n): Promise<WorkSummary> {\n const model = await getModel(options)\n\n const commitList = context.commits\n .map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split('\\n')[0]} (${c.date.split('T')[0]})`)\n .join('\\n')\n\n const formatHint = format === 'daily'\n ? 'This is a daily report. Focus on today\\'s accomplishments.'\n : format === 'weekly'\n ? 'This is a weekly report. Summarize the week\\'s work at a higher level.'\n : `This is a summary from ${context.since}${context.until ? ` to ${context.until}` : ''}.`\n\n const period = `${context.since}${context.until ? ` to ${context.until}` : ' to now'}`\n\n const prompt = buildPrompt(template, 'summary', {\n author: context.author,\n period,\n format: formatHint,\n commits: commitList,\n diff: context.diff?.slice(0, 6000)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: WorkSummarySchema,\n prompt\n })\n\n return {\n ...result.object,\n stats: {\n commits: context.commits.length,\n ...result.object.stats\n }\n }\n}\n\nexport async function resolveConflict(\n conflictedContent: string,\n context: {\n filename: string\n oursRef: string\n theirsRef: string\n },\n options: AIOptions,\n template?: string\n): Promise<ConflictResolution> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'merge', {\n filename: context.filename,\n oursRef: context.oursRef,\n theirsRef: context.theirsRef,\n content: conflictedContent\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ConflictResolutionSchema,\n prompt\n })\n\n return result.object\n}\n\nexport async function generateGitignore(\n context: {\n files: string\n configFiles?: string\n existingGitignore?: string\n },\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'gitignore', {\n files: context.files,\n configFiles: context.configFiles,\n existingGitignore: context.existingGitignore\n }, options.language)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 2000\n })\n\n return result.text.trim()\n}\n","import { createRequire } from 'module'\n\nconst SERVICE_NAME = 'gut-cli'\n\nexport type Provider = 'gemini' | 'openai' | 'anthropic' | 'ollama'\n\n// Providers that require API keys\ntype ApiKeyProvider = Exclude<Provider, 'ollama'>\n\nconst PROVIDER_KEY_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'gemini-api-key',\n openai: 'openai-api-key',\n anthropic: 'anthropic-api-key'\n}\n\nconst ENV_VAR_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'GUT_GEMINI_API_KEY',\n openai: 'GUT_OPENAI_API_KEY',\n anthropic: 'GUT_ANTHROPIC_API_KEY'\n}\n\nconst FALLBACK_ENV_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'GEMINI_API_KEY',\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY'\n}\n\nfunction getKeytar(): typeof import('keytar') | null {\n try {\n // Use createRequire to resolve keytar from gut's own node_modules\n // This ensures it works when gut is globally installed\n const require = createRequire(import.meta.url)\n return require('keytar')\n } catch {\n return null\n }\n}\n\nexport async function saveApiKey(provider: Provider, apiKey: string): Promise<void> {\n if (provider === 'ollama') {\n throw new Error('Ollama does not require an API key')\n }\n const keytar = getKeytar()\n if (!keytar) {\n throw new Error('Keychain not available. Set environment variable instead.')\n }\n await keytar.setPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider], apiKey)\n}\n\nexport async function getApiKey(provider: Provider): Promise<string | null> {\n // Ollama doesn't need an API key\n if (provider === 'ollama') {\n return null\n }\n\n // 1. Check environment variable (GUT_*_API_KEY)\n const envKey = process.env[ENV_VAR_MAP[provider]]\n if (envKey) return envKey\n\n // 2. Check fallback environment variable (*_API_KEY)\n const fallbackKey = process.env[FALLBACK_ENV_MAP[provider]]\n if (fallbackKey) return fallbackKey\n\n // 3. Check system keychain\n const keytar = getKeytar()\n if (!keytar) return null\n return keytar.getPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider])\n}\n\nexport async function deleteApiKey(provider: Provider): Promise<boolean> {\n if (provider === 'ollama') {\n throw new Error('Ollama does not use an API key')\n }\n const keytar = getKeytar()\n if (!keytar) {\n throw new Error('Keychain not available.')\n }\n return keytar.deletePassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider])\n}\n\nexport async function listProviders(): Promise<{ provider: Provider; hasKey: boolean }[]> {\n const apiKeyProviders: ApiKeyProvider[] = ['gemini', 'openai', 'anthropic']\n const results = await Promise.all(\n apiKeyProviders.map(async (provider) => ({\n provider: provider as Provider,\n hasKey: !!(await getApiKey(provider))\n }))\n )\n // Add ollama (always available, no key needed)\n results.push({ provider: 'ollama', hasKey: true })\n return results\n}\n\nexport function getProviderDisplayName(provider: Provider): string {\n const names: Record<Provider, string> = {\n gemini: 'Google Gemini',\n openai: 'OpenAI',\n anthropic: 'Anthropic Claude',\n ollama: 'Ollama (Local)'\n }\n return names[provider]\n}\n","import { homedir } from 'os'\nimport { join } from 'path'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'\nimport { execSync } from 'child_process'\n\nexport type Language = 'en' | 'ja'\n\nexport interface GutConfig {\n lang: Language\n}\n\nconst DEFAULT_CONFIG: GutConfig = {\n lang: 'en'\n}\n\nfunction getGlobalConfigPath(): string {\n const configDir = join(homedir(), '.config', 'gut')\n return join(configDir, 'config.json')\n}\n\nfunction getRepoRoot(): string | null {\n try {\n return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim()\n } catch {\n return null\n }\n}\n\nfunction getLocalConfigPath(): string | null {\n const repoRoot = getRepoRoot()\n if (!repoRoot) return null\n return join(repoRoot, '.gut', 'config.json')\n}\n\nfunction ensureGlobalConfigDir(): void {\n const configDir = join(homedir(), '.config', 'gut')\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true })\n }\n}\n\nfunction ensureLocalConfigDir(): void {\n const repoRoot = getRepoRoot()\n if (!repoRoot) return\n const gutDir = join(repoRoot, '.gut')\n if (!existsSync(gutDir)) {\n mkdirSync(gutDir, { recursive: true })\n }\n}\n\nfunction readConfigFile(path: string): Partial<GutConfig> {\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return {}\n }\n}\n\nexport function getGlobalConfig(): GutConfig {\n const globalPath = getGlobalConfigPath()\n return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) }\n}\n\nexport function getLocalConfig(): Partial<GutConfig> {\n const localPath = getLocalConfigPath()\n if (!localPath) return {}\n return readConfigFile(localPath)\n}\n\nexport function getConfig(): GutConfig {\n // Local config overrides global config\n const globalConfig = getGlobalConfig()\n const localConfig = getLocalConfig()\n return { ...globalConfig, ...localConfig }\n}\n\nexport function setGlobalConfig<K extends keyof GutConfig>(key: K, value: GutConfig[K]): void {\n ensureGlobalConfigDir()\n const config = getGlobalConfig()\n config[key] = value\n writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2))\n}\n\nexport function setLocalConfig<K extends keyof GutConfig>(key: K, value: GutConfig[K]): void {\n const localPath = getLocalConfigPath()\n if (!localPath) {\n throw new Error('Not in a git repository')\n }\n ensureLocalConfigDir()\n const config = getLocalConfig()\n config[key] = value\n writeFileSync(localPath, JSON.stringify(config, null, 2))\n}\n\nexport function getLanguage(): Language {\n return getConfig().lang\n}\n\nexport function setLanguage(lang: Language, local: boolean = false): void {\n if (local) {\n setLocalConfig('lang', lang)\n } else {\n setGlobalConfig('lang', lang)\n }\n}\n\nexport function getLanguageInstruction(lang: Language): string {\n switch (lang) {\n case 'ja':\n return '\\n\\nIMPORTANT: Respond in Japanese (日本語で回答してください).'\n case 'en':\n default:\n return ''\n }\n}\n\nexport const VALID_LANGUAGES: Language[] = ['en', 'ja']\n\nexport function isValidLanguage(lang: string): lang is Language {\n return VALID_LANGUAGES.includes(lang as Language)\n}\n"],"mappings":"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","names":["require","prompt","result","homedir","join","existsSync","readFileSync"]}
1
+ {"version":3,"sources":["../../src/lib/ai.ts","../../src/lib/credentials.ts","../../src/lib/config.ts"],"sourcesContent":["import { generateText, generateObject } from 'ai'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createOllama } from 'ollama-ai-provider'\nimport { z } from 'zod'\nimport { existsSync, readFileSync } from 'fs'\nimport { join, dirname } from 'path'\nimport { homedir } from 'os'\nimport { fileURLToPath } from 'url'\nimport { getApiKey, Provider } from './credentials.js'\nimport { type Language } from './config.js'\n\nexport type { Language }\n\nexport interface AIOptions {\n provider: Provider\n model?: string\n ollamaBaseUrl?: string // For Ollama provider\n apiKey?: string // Optional: directly provide API key (bypasses keytar/env lookup)\n language?: Language // Language for AI responses ('en' | 'ja')\n}\n\n// Get the directory where gut is installed (for reading default templates)\n// Works for both CLI (dist/index.js) and library (dist/lib/index.js) entry points\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nfunction findGutRoot(): string {\n // Search upward from __dirname until we find a directory containing .gut/\n let current = __dirname\n for (let i = 0; i < 5; i++) {\n const gutPath = join(current, '.gut')\n if (existsSync(gutPath)) {\n return current\n }\n current = dirname(current)\n }\n // Fallback: assume we're in dist/lib/ or dist/\n return join(__dirname, '..')\n}\n\nconst GUT_ROOT = findGutRoot()\n\n/**\n * Load a default template from gut's own .gut/ folder\n */\nfunction loadTemplate(name: string): string {\n const templatePath = join(GUT_ROOT, '.gut', `${name}.md`)\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, 'utf-8')\n }\n throw new Error(`Template not found: ${templatePath}`)\n}\n\n/**\n * Get the global templates directory path\n */\nfunction getGlobalTemplatesDir(): string {\n return join(homedir(), '.config', 'gut', 'templates')\n}\n\n/**\n * Find a global template from ~/.config/gut/templates/\n * @param templateName - Name of the template file (without .md extension)\n * @returns Template content if found, null otherwise\n */\nexport function findGlobalTemplate(templateName: string): string | null {\n const templatePath = join(getGlobalTemplatesDir(), `${templateName}.md`)\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, 'utf-8')\n }\n return null\n}\n\n/**\n * Find a user's project template from .gut/ folder\n * @param repoRoot - The root directory of the user's repository\n * @param templateName - Name of the template file (without .md extension)\n * @returns Template content if found, null otherwise\n */\nexport function findTemplate(repoRoot: string, templateName: string): string | null {\n // Priority 1: Project-level template (.gut/)\n const projectTemplatePath = join(repoRoot, '.gut', `${templateName}.md`)\n if (existsSync(projectTemplatePath)) {\n return readFileSync(projectTemplatePath, 'utf-8')\n }\n\n // Priority 2: Global template (~/.config/gut/templates/)\n const globalTemplate = findGlobalTemplate(templateName)\n if (globalTemplate) {\n return globalTemplate\n }\n\n return null\n}\n\n/**\n * Build a prompt with context in XML format and instructions from template.\n * Templates don't need to include variables - context is automatically prepended.\n * Output format instructions are also injected automatically, so templates remain pure instructions.\n *\n * @param userTemplate - User-provided template string or null/undefined\n * @param templateName - Name of the default template file in .gut/ (without .md extension)\n * @param context - Context data to include (diff, commits, etc.)\n * @param language - Language for AI responses\n * @param outputFormat - Output format instructions (auto-injected, not user-editable)\n * @returns Complete prompt with XML context and instructions\n */\nfunction buildPrompt(\n userTemplate: string | null | undefined,\n templateName: string,\n context: Record<string, string | undefined>,\n language?: Language,\n outputFormat?: string\n): string {\n // Build XML context\n let contextXml = '<context>\\n'\n for (const [key, value] of Object.entries(context)) {\n if (value) {\n contextXml += `<${key}>\\n${value}\\n</${key}>\\n`\n }\n }\n contextXml += '</context>\\n\\n'\n\n // Get template (user template takes priority)\n const template = userTemplate || loadTemplate(templateName)\n\n // Build language instruction\n const langInstruction = language === 'ja'\n ? '\\n\\nIMPORTANT: Respond in Japanese (日本語で回答してください).'\n : ''\n\n // Build output format section (auto-injected, not user-editable)\n const outputSection = outputFormat\n ? `\\n\\n<output-format>\\n${outputFormat}\\n</output-format>`\n : ''\n\n return contextXml + '<instructions>\\n' + template + langInstruction + '\\n</instructions>' + outputSection\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n gemini: 'gemini-2.0-flash',\n openai: 'gpt-4o-mini',\n anthropic: 'claude-sonnet-4-20250514',\n ollama: 'llama3.2'\n}\n\nasync function getModel(options: AIOptions) {\n const modelName = options.model || DEFAULT_MODELS[options.provider]\n\n // Helper to get API key: use provided key or fall back to keytar/env\n async function resolveApiKey(): Promise<string | null> {\n if (options.apiKey) return options.apiKey\n return getApiKey(options.provider)\n }\n\n // Ollama doesn't require an API key\n if (options.provider !== 'ollama') {\n const apiKey = await resolveApiKey()\n if (!apiKey) {\n throw new Error(\n `No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`\n )\n }\n }\n\n switch (options.provider) {\n case 'gemini': {\n const apiKey = await resolveApiKey()\n const google = createGoogleGenerativeAI({ apiKey: apiKey! })\n return google(modelName)\n }\n case 'openai': {\n const apiKey = await resolveApiKey()\n const openai = createOpenAI({ apiKey: apiKey! })\n return openai(modelName)\n }\n case 'anthropic': {\n const apiKey = await resolveApiKey()\n const anthropic = createAnthropic({ apiKey: apiKey! })\n return anthropic(modelName)\n }\n case 'ollama': {\n const ollama = createOllama({\n baseURL: options.ollamaBaseUrl || 'http://localhost:11434/api'\n })\n return ollama(modelName)\n }\n }\n}\n\nexport async function generateCommitMessage(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'commit', {\n diff: diff.slice(0, 8000)\n }, options.language, 'Respond with ONLY the commit message, nothing else.')\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 500\n })\n\n return result.text.trim()\n}\n\nexport async function generatePRDescription(\n context: {\n baseBranch: string\n currentBranch: string\n commits: string[]\n diff: string\n },\n options: AIOptions,\n template?: string\n): Promise<{ title: string; body: string }> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'pr', {\n baseBranch: context.baseBranch,\n currentBranch: context.currentBranch,\n commits: context.commits.map((c) => `- ${c}`).join('\\n'),\n diff: context.diff.slice(0, 6000)\n }, options.language, `Respond in JSON format:\n\\`\\`\\`json\n{\n \"title\": \"...\",\n \"body\": \"...\"\n}\n\\`\\`\\``)\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 2000\n })\n\n try {\n const cleaned = result.text.replace(/```json\\n?|\\n?```/g, '').trim()\n return JSON.parse(cleaned)\n } catch {\n return {\n title: context.currentBranch.replace(/[-_]/g, ' '),\n body: result.text\n }\n }\n}\n\nconst CodeReviewSchema = z.object({\n summary: z.string().describe('Brief overall assessment'),\n issues: z.array(\n z.object({\n severity: z.enum(['critical', 'warning', 'suggestion']),\n file: z.string(),\n line: z.number().optional(),\n message: z.string(),\n suggestion: z.string().optional()\n })\n ),\n positives: z.array(z.string()).describe('Good practices observed')\n})\n\nexport type CodeReview = z.infer<typeof CodeReviewSchema>\n\nexport async function generateCodeReview(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<CodeReview> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'review', {\n diff: diff.slice(0, 10000)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: CodeReviewSchema,\n prompt\n })\n\n return result.object\n}\n\nconst ChangelogSchema = z.object({\n version: z.string().optional().describe('Version string if detected'),\n date: z.string().describe('Release date in YYYY-MM-DD format'),\n sections: z.array(\n z.object({\n type: z.string().describe('Section type (Added, Changed, Fixed, Removed, etc.)'),\n items: z.array(z.string()).describe('List of changes in this section')\n })\n ),\n summary: z.string().optional().describe('Brief summary of this release')\n})\n\nexport type Changelog = z.infer<typeof ChangelogSchema>\n\nexport async function generateChangelog(\n context: {\n commits: Array<{ hash: string; message: string; author: string; date: string }>\n diff: string\n fromRef: string\n toRef: string\n },\n options: AIOptions,\n template?: string\n): Promise<Changelog> {\n const model = await getModel(options)\n\n const commitList = context.commits\n .map((c) => `- ${c.hash.slice(0, 7)} ${c.message} (${c.author})`)\n .join('\\n')\n\n const prompt = buildPrompt(template, 'changelog', {\n fromRef: context.fromRef,\n toRef: context.toRef,\n commits: commitList,\n diff: context.diff.slice(0, 8000),\n todayDate: new Date().toISOString().split('T')[0]\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ChangelogSchema,\n prompt\n })\n\n return result.object\n}\n\nconst ConflictResolutionSchema = z.object({\n resolvedContent: z.string().describe('The resolved file content'),\n explanation: z.string().describe('Brief explanation of how the conflict was resolved'),\n strategy: z.enum(['ours', 'theirs', 'combined', 'rewritten']).describe('Resolution strategy used')\n})\n\nexport type ConflictResolution = z.infer<typeof ConflictResolutionSchema>\n\nconst ExplanationSchema = z.object({\n summary: z.string().describe('One-line summary of what this file/commit/PR does'),\n purpose: z.string().describe('The purpose and role of this code'),\n changes: z.array(\n z.object({\n file: z.string(),\n description: z.string().describe('Description of this file or component')\n })\n ),\n impact: z.string().describe('What impact or role this has in the project'),\n notes: z.array(z.string()).optional().describe('Important considerations or caveats')\n})\n\nexport type Explanation = z.infer<typeof ExplanationSchema>\n\nexport async function generateExplanation(\n context: {\n type: 'commit' | 'pr' | 'file-history' | 'file-content' | 'uncommitted' | 'staged'\n title: string\n diff?: string\n content?: string\n metadata: {\n hash?: string\n author?: string\n date?: string\n prNumber?: string\n baseBranch?: string\n headBranch?: string\n commits?: string[]\n filePath?: string\n }\n },\n options: AIOptions,\n template?: string\n): Promise<Explanation> {\n const model = await getModel(options)\n\n // Handle file content explanation\n if (context.type === 'file-content') {\n const prompt = buildPrompt(template, 'explain-file', {\n filePath: context.metadata.filePath || '',\n content: context.content?.slice(0, 15000) || ''\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ExplanationSchema,\n prompt\n })\n return result.object\n }\n\n // Build context info for diff-based explanations\n let contextInfo: string\n let targetType: string\n\n if (context.type === 'pr') {\n contextInfo = `Pull Request: #${context.metadata.prNumber}\nTitle: ${context.title}\nBranch: ${context.metadata.headBranch} -> ${context.metadata.baseBranch}\nCommits:\n${context.metadata.commits?.map((c) => `- ${c}`).join('\\n') || 'N/A'}`\n targetType = 'pull request'\n } else if (context.type === 'file-history') {\n contextInfo = `File: ${context.metadata.filePath}\nRecent commits:\n${context.metadata.commits?.map((c) => `- ${c}`).join('\\n') || 'N/A'}\nLatest author: ${context.metadata.author}\nLatest date: ${context.metadata.date}`\n targetType = 'file changes'\n } else if (context.type === 'uncommitted' || context.type === 'staged') {\n contextInfo = context.type === 'staged' ? 'Staged changes (ready to commit)' : 'Uncommitted changes (work in progress)'\n targetType = context.type === 'staged' ? 'staged changes' : 'uncommitted changes'\n } else {\n contextInfo = `Commit: ${context.metadata.hash?.slice(0, 7)}\nMessage: ${context.title}\nAuthor: ${context.metadata.author}\nDate: ${context.metadata.date}`\n targetType = 'commit'\n }\n\n const prompt = buildPrompt(template, 'explain', {\n targetType,\n contextInfo,\n diff: context.diff?.slice(0, 12000) || ''\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ExplanationSchema,\n prompt\n })\n\n return result.object\n}\n\nconst CommitSearchSchema = z.object({\n matches: z.array(\n z.object({\n hash: z.string().describe('Commit hash'),\n reason: z.string().describe('Why this commit matches the query')\n })\n ),\n summary: z.string().optional().describe('Brief summary of the search results')\n})\n\nexport interface CommitSearchResult {\n matches: Array<{\n hash: string\n message: string\n author: string\n email: string\n date: string\n reason: string\n relevance?: 'high' | 'medium' | 'low'\n }>\n summary?: string\n}\n\nexport async function searchCommits(\n query: string,\n commits: Array<{\n hash: string\n message: string\n author: string\n email: string\n date: string\n }>,\n options: AIOptions,\n maxResults: number = 5,\n template?: string\n): Promise<CommitSearchResult> {\n const model = await getModel(options)\n\n const commitList = commits\n .map((c) => `${c.hash.slice(0, 7)} | ${c.author} | ${c.date.split('T')[0]} | ${c.message.split('\\n')[0]}`)\n .join('\\n')\n\n const prompt = buildPrompt(template, 'find', {\n query,\n commits: commitList,\n maxResults: String(maxResults)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: CommitSearchSchema,\n prompt\n })\n\n // Enrich results with full commit data and assign relevance based on position\n const enrichedMatches = result.object.matches.map((match, index) => {\n const commit = commits.find((c) => c.hash.startsWith(match.hash))\n if (!commit) {\n return null\n }\n const relevance: 'high' | 'medium' | 'low' = index === 0 ? 'high' : index < 3 ? 'medium' : 'low'\n return {\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n email: commit.email,\n date: commit.date,\n reason: match.reason,\n relevance\n }\n }).filter((m): m is NonNullable<typeof m> => m !== null)\n\n return {\n matches: enrichedMatches,\n summary: result.object.summary\n }\n}\n\nexport async function generateBranchName(\n description: string,\n options: AIOptions,\n context?: {\n type?: string\n issue?: string\n },\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'branch', {\n description,\n type: context?.type,\n issue: context?.issue\n }, options.language, 'Respond with ONLY the branch name, nothing else.')\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim().replace(/[^a-zA-Z0-9/_-]/g, '')\n}\n\nexport async function generateBranchNameFromDiff(\n diff: string,\n options: AIOptions,\n template?: string | null\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'checkout', {\n diff: diff.slice(0, 8000)\n }, options.language, 'Respond with ONLY the branch name, nothing else.')\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim().replace(/[^a-zA-Z0-9/_-]/g, '')\n}\n\nexport async function generateStashName(\n diff: string,\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'stash', {\n diff: diff.slice(0, 4000)\n }, options.language, 'Respond with ONLY the stash name, nothing else.')\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 100\n })\n\n return result.text.trim()\n}\n\nconst WorkSummarySchema = z.object({\n title: z.string().describe('One-line title for the summary'),\n overview: z.string().describe('Brief overview of what was accomplished'),\n highlights: z.array(z.string()).describe('Key accomplishments or highlights'),\n details: z.array(\n z.object({\n category: z.string().describe('Category (e.g., Feature, Bug Fix, Refactor)'),\n items: z.array(z.string()).describe('List of items in this category')\n })\n ),\n stats: z.object({\n commits: z.number(),\n filesChanged: z.number().optional(),\n additions: z.number().optional(),\n deletions: z.number().optional()\n }).optional()\n})\n\nexport type WorkSummary = z.infer<typeof WorkSummarySchema>\n\nexport async function generateWorkSummary(\n context: {\n commits: Array<{ hash: string; message: string; date: string }>\n author: string\n since: string\n until?: string\n diff?: string\n },\n options: AIOptions,\n format: 'daily' | 'weekly' | 'custom' = 'custom',\n template?: string\n): Promise<WorkSummary> {\n const model = await getModel(options)\n\n const commitList = context.commits\n .map((c) => `- ${c.hash.slice(0, 7)} ${c.message.split('\\n')[0]} (${c.date.split('T')[0]})`)\n .join('\\n')\n\n const formatHint = format === 'daily'\n ? 'This is a daily report. Focus on today\\'s accomplishments.'\n : format === 'weekly'\n ? 'This is a weekly report. Summarize the week\\'s work at a higher level.'\n : `This is a summary from ${context.since}${context.until ? ` to ${context.until}` : ''}.`\n\n const period = `${context.since}${context.until ? ` to ${context.until}` : ' to now'}`\n\n const prompt = buildPrompt(template, 'summary', {\n author: context.author,\n period,\n format: formatHint,\n commits: commitList,\n diff: context.diff?.slice(0, 6000)\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: WorkSummarySchema,\n prompt\n })\n\n return {\n ...result.object,\n stats: {\n commits: context.commits.length,\n ...result.object.stats\n }\n }\n}\n\nexport async function resolveConflict(\n conflictedContent: string,\n context: {\n filename: string\n oursRef: string\n theirsRef: string\n },\n options: AIOptions,\n template?: string\n): Promise<ConflictResolution> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'merge', {\n filename: context.filename,\n oursRef: context.oursRef,\n theirsRef: context.theirsRef,\n content: conflictedContent\n }, options.language)\n\n const result = await generateObject({\n model,\n schema: ConflictResolutionSchema,\n prompt\n })\n\n return result.object\n}\n\nexport async function generateGitignore(\n context: {\n files: string\n configFiles?: string\n existingGitignore?: string\n },\n options: AIOptions,\n template?: string\n): Promise<string> {\n const model = await getModel(options)\n\n const prompt = buildPrompt(template, 'gitignore', {\n files: context.files,\n configFiles: context.configFiles,\n existingGitignore: context.existingGitignore\n }, options.language, 'Respond with ONLY the .gitignore content, nothing else. No explanations or markdown code blocks.')\n\n const result = await generateText({\n model,\n prompt,\n maxTokens: 2000\n })\n\n return result.text.trim()\n}\n","import { createRequire } from 'module'\n\nconst SERVICE_NAME = 'gut-cli'\n\nexport type Provider = 'gemini' | 'openai' | 'anthropic' | 'ollama'\n\n// Providers that require API keys\ntype ApiKeyProvider = Exclude<Provider, 'ollama'>\n\nconst PROVIDER_KEY_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'gemini-api-key',\n openai: 'openai-api-key',\n anthropic: 'anthropic-api-key'\n}\n\nconst ENV_VAR_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'GUT_GEMINI_API_KEY',\n openai: 'GUT_OPENAI_API_KEY',\n anthropic: 'GUT_ANTHROPIC_API_KEY'\n}\n\nconst FALLBACK_ENV_MAP: Record<ApiKeyProvider, string> = {\n gemini: 'GEMINI_API_KEY',\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY'\n}\n\nfunction getKeytar(): typeof import('keytar') | null {\n try {\n // Use createRequire to resolve keytar from gut's own node_modules\n // This ensures it works when gut is globally installed\n const require = createRequire(import.meta.url)\n return require('keytar')\n } catch {\n return null\n }\n}\n\nexport async function saveApiKey(provider: Provider, apiKey: string): Promise<void> {\n if (provider === 'ollama') {\n throw new Error('Ollama does not require an API key')\n }\n const keytar = getKeytar()\n if (!keytar) {\n throw new Error('Keychain not available. Set environment variable instead.')\n }\n await keytar.setPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider], apiKey)\n}\n\nexport async function getApiKey(provider: Provider): Promise<string | null> {\n // Ollama doesn't need an API key\n if (provider === 'ollama') {\n return null\n }\n\n // 1. Check environment variable (GUT_*_API_KEY)\n const envKey = process.env[ENV_VAR_MAP[provider]]\n if (envKey) return envKey\n\n // 2. Check fallback environment variable (*_API_KEY)\n const fallbackKey = process.env[FALLBACK_ENV_MAP[provider]]\n if (fallbackKey) return fallbackKey\n\n // 3. Check system keychain\n const keytar = getKeytar()\n if (!keytar) return null\n return keytar.getPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider])\n}\n\nexport async function deleteApiKey(provider: Provider): Promise<boolean> {\n if (provider === 'ollama') {\n throw new Error('Ollama does not use an API key')\n }\n const keytar = getKeytar()\n if (!keytar) {\n throw new Error('Keychain not available.')\n }\n return keytar.deletePassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider])\n}\n\nexport async function listProviders(): Promise<{ provider: Provider; hasKey: boolean }[]> {\n const apiKeyProviders: ApiKeyProvider[] = ['gemini', 'openai', 'anthropic']\n const results = await Promise.all(\n apiKeyProviders.map(async (provider) => ({\n provider: provider as Provider,\n hasKey: !!(await getApiKey(provider))\n }))\n )\n // Add ollama (always available, no key needed)\n results.push({ provider: 'ollama', hasKey: true })\n return results\n}\n\nexport function getProviderDisplayName(provider: Provider): string {\n const names: Record<Provider, string> = {\n gemini: 'Google Gemini',\n openai: 'OpenAI',\n anthropic: 'Anthropic Claude',\n ollama: 'Ollama (Local)'\n }\n return names[provider]\n}\n","import { homedir } from 'os'\nimport { join } from 'path'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'\nimport { execSync } from 'child_process'\n\nexport type Language = 'en' | 'ja'\n\nexport interface GutConfig {\n lang: Language\n}\n\nconst DEFAULT_CONFIG: GutConfig = {\n lang: 'en'\n}\n\nfunction getGlobalConfigPath(): string {\n const configDir = join(homedir(), '.config', 'gut')\n return join(configDir, 'config.json')\n}\n\nfunction getRepoRoot(): string | null {\n try {\n return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim()\n } catch {\n return null\n }\n}\n\nfunction getLocalConfigPath(): string | null {\n const repoRoot = getRepoRoot()\n if (!repoRoot) return null\n return join(repoRoot, '.gut', 'config.json')\n}\n\nfunction ensureGlobalConfigDir(): void {\n const configDir = join(homedir(), '.config', 'gut')\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true })\n }\n}\n\nfunction ensureLocalConfigDir(): void {\n const repoRoot = getRepoRoot()\n if (!repoRoot) return\n const gutDir = join(repoRoot, '.gut')\n if (!existsSync(gutDir)) {\n mkdirSync(gutDir, { recursive: true })\n }\n}\n\nfunction readConfigFile(path: string): Partial<GutConfig> {\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return {}\n }\n}\n\nexport function getGlobalConfig(): GutConfig {\n const globalPath = getGlobalConfigPath()\n return { ...DEFAULT_CONFIG, ...readConfigFile(globalPath) }\n}\n\nexport function getLocalConfig(): Partial<GutConfig> {\n const localPath = getLocalConfigPath()\n if (!localPath) return {}\n return readConfigFile(localPath)\n}\n\nexport function getConfig(): GutConfig {\n // Local config overrides global config\n const globalConfig = getGlobalConfig()\n const localConfig = getLocalConfig()\n return { ...globalConfig, ...localConfig }\n}\n\nexport function setGlobalConfig<K extends keyof GutConfig>(key: K, value: GutConfig[K]): void {\n ensureGlobalConfigDir()\n const config = getGlobalConfig()\n config[key] = value\n writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2))\n}\n\nexport function setLocalConfig<K extends keyof GutConfig>(key: K, value: GutConfig[K]): void {\n const localPath = getLocalConfigPath()\n if (!localPath) {\n throw new Error('Not in a git repository')\n }\n ensureLocalConfigDir()\n const config = getLocalConfig()\n config[key] = value\n writeFileSync(localPath, JSON.stringify(config, null, 2))\n}\n\nexport function getLanguage(): Language {\n return getConfig().lang\n}\n\nexport function setLanguage(lang: Language, local: boolean = false): void {\n if (local) {\n setLocalConfig('lang', lang)\n } else {\n setGlobalConfig('lang', lang)\n }\n}\n\nexport function getLanguageInstruction(lang: Language): string {\n switch (lang) {\n case 'ja':\n return '\\n\\nIMPORTANT: Respond in Japanese (日本語で回答してください).'\n case 'en':\n default:\n return ''\n }\n}\n\nexport const VALID_LANGUAGES: Language[] = ['en', 'ja']\n\nexport function isValidLanguage(lang: string): lang is Language {\n return VALID_LANGUAGES.includes(lang as Language)\n}\n"],"mappings":"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","names":["require","prompt","result","homedir","join","existsSync","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gut-cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Git Utility Tool - AI-powered git commands for smarter workflows",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/index.js",