gut-cli 0.1.14 → 0.1.16
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/index.js +55 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +300 -0
- package/dist/lib/index.js +595 -0
- package/dist/lib/index.js.map +1 -0
- package/package.json +17 -2
package/dist/index.js
CHANGED
|
@@ -108,12 +108,16 @@ var FALLBACK_ENV_MAP = {
|
|
|
108
108
|
};
|
|
109
109
|
async function getKeytar() {
|
|
110
110
|
try {
|
|
111
|
-
|
|
111
|
+
const keytar = await import("keytar");
|
|
112
|
+
return keytar.default || keytar;
|
|
112
113
|
} catch {
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
async function saveApiKey(provider, apiKey) {
|
|
118
|
+
if (provider === "ollama") {
|
|
119
|
+
throw new Error("Ollama does not require an API key");
|
|
120
|
+
}
|
|
117
121
|
const keytar = await getKeytar();
|
|
118
122
|
if (!keytar) {
|
|
119
123
|
throw new Error("Keychain not available. Set environment variable instead.");
|
|
@@ -121,6 +125,9 @@ async function saveApiKey(provider, apiKey) {
|
|
|
121
125
|
await keytar.setPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider], apiKey);
|
|
122
126
|
}
|
|
123
127
|
async function getApiKey(provider) {
|
|
128
|
+
if (provider === "ollama") {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
124
131
|
const envKey = process.env[ENV_VAR_MAP[provider]];
|
|
125
132
|
if (envKey) return envKey;
|
|
126
133
|
const fallbackKey = process.env[FALLBACK_ENV_MAP[provider]];
|
|
@@ -130,6 +137,9 @@ async function getApiKey(provider) {
|
|
|
130
137
|
return keytar.getPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
|
|
131
138
|
}
|
|
132
139
|
async function deleteApiKey(provider) {
|
|
140
|
+
if (provider === "ollama") {
|
|
141
|
+
throw new Error("Ollama does not use an API key");
|
|
142
|
+
}
|
|
133
143
|
const keytar = await getKeytar();
|
|
134
144
|
if (!keytar) {
|
|
135
145
|
throw new Error("Keychain not available.");
|
|
@@ -137,20 +147,22 @@ async function deleteApiKey(provider) {
|
|
|
137
147
|
return keytar.deletePassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
|
|
138
148
|
}
|
|
139
149
|
async function listProviders() {
|
|
140
|
-
const
|
|
150
|
+
const apiKeyProviders = ["gemini", "openai", "anthropic"];
|
|
141
151
|
const results = await Promise.all(
|
|
142
|
-
|
|
152
|
+
apiKeyProviders.map(async (provider) => ({
|
|
143
153
|
provider,
|
|
144
154
|
hasKey: !!await getApiKey(provider)
|
|
145
155
|
}))
|
|
146
156
|
);
|
|
157
|
+
results.push({ provider: "ollama", hasKey: true });
|
|
147
158
|
return results;
|
|
148
159
|
}
|
|
149
160
|
function getProviderDisplayName(provider) {
|
|
150
161
|
const names = {
|
|
151
162
|
gemini: "Google Gemini",
|
|
152
163
|
openai: "OpenAI",
|
|
153
|
-
anthropic: "Anthropic Claude"
|
|
164
|
+
anthropic: "Anthropic Claude",
|
|
165
|
+
ollama: "Ollama (Local)"
|
|
154
166
|
};
|
|
155
167
|
return names[provider];
|
|
156
168
|
}
|
|
@@ -270,6 +282,7 @@ import { generateText, generateObject } from "ai";
|
|
|
270
282
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
271
283
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
272
284
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
285
|
+
import { createOllama } from "ollama-ai-provider";
|
|
273
286
|
import { z } from "zod";
|
|
274
287
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
275
288
|
import { join as join2, dirname } from "path";
|
|
@@ -378,7 +391,18 @@ function isValidLanguage(lang) {
|
|
|
378
391
|
// src/lib/ai.ts
|
|
379
392
|
var __filename = fileURLToPath(import.meta.url);
|
|
380
393
|
var __dirname = dirname(__filename);
|
|
381
|
-
|
|
394
|
+
function findGutRoot() {
|
|
395
|
+
let current = __dirname;
|
|
396
|
+
for (let i = 0; i < 5; i++) {
|
|
397
|
+
const gutPath = join2(current, ".gut");
|
|
398
|
+
if (existsSync2(gutPath)) {
|
|
399
|
+
return current;
|
|
400
|
+
}
|
|
401
|
+
current = dirname(current);
|
|
402
|
+
}
|
|
403
|
+
return join2(__dirname, "..");
|
|
404
|
+
}
|
|
405
|
+
var GUT_ROOT = findGutRoot();
|
|
382
406
|
function loadTemplate(name) {
|
|
383
407
|
const templatePath = join2(GUT_ROOT, ".gut", `${name}.md`);
|
|
384
408
|
if (existsSync2(templatePath)) {
|
|
@@ -410,29 +434,45 @@ function applyTemplate(userTemplate, templateName, variables) {
|
|
|
410
434
|
var DEFAULT_MODELS = {
|
|
411
435
|
gemini: "gemini-2.0-flash",
|
|
412
436
|
openai: "gpt-4o-mini",
|
|
413
|
-
anthropic: "claude-sonnet-4-20250514"
|
|
437
|
+
anthropic: "claude-sonnet-4-20250514",
|
|
438
|
+
ollama: "llama3.2"
|
|
414
439
|
};
|
|
415
440
|
async function getModel(options) {
|
|
416
|
-
const apiKey = await getApiKey(options.provider);
|
|
417
|
-
if (!apiKey) {
|
|
418
|
-
throw new Error(
|
|
419
|
-
`No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
441
|
const modelName = options.model || DEFAULT_MODELS[options.provider];
|
|
442
|
+
async function resolveApiKey() {
|
|
443
|
+
if (options.apiKey) return options.apiKey;
|
|
444
|
+
return getApiKey(options.provider);
|
|
445
|
+
}
|
|
446
|
+
if (options.provider !== "ollama") {
|
|
447
|
+
const apiKey = await resolveApiKey();
|
|
448
|
+
if (!apiKey) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
423
454
|
switch (options.provider) {
|
|
424
455
|
case "gemini": {
|
|
456
|
+
const apiKey = await resolveApiKey();
|
|
425
457
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
426
458
|
return google(modelName);
|
|
427
459
|
}
|
|
428
460
|
case "openai": {
|
|
461
|
+
const apiKey = await resolveApiKey();
|
|
429
462
|
const openai = createOpenAI({ apiKey });
|
|
430
463
|
return openai(modelName);
|
|
431
464
|
}
|
|
432
465
|
case "anthropic": {
|
|
466
|
+
const apiKey = await resolveApiKey();
|
|
433
467
|
const anthropic = createAnthropic({ apiKey });
|
|
434
468
|
return anthropic(modelName);
|
|
435
469
|
}
|
|
470
|
+
case "ollama": {
|
|
471
|
+
const ollama = createOllama({
|
|
472
|
+
baseURL: options.ollamaBaseUrl || "http://localhost:11434/api"
|
|
473
|
+
});
|
|
474
|
+
return ollama(modelName);
|
|
475
|
+
}
|
|
436
476
|
}
|
|
437
477
|
}
|
|
438
478
|
async function generateCommitMessage(diff, options, template) {
|
|
@@ -614,11 +654,12 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
614
654
|
schema: CommitSearchSchema,
|
|
615
655
|
prompt
|
|
616
656
|
});
|
|
617
|
-
const enrichedMatches = result.object.matches.map((match) => {
|
|
657
|
+
const enrichedMatches = result.object.matches.map((match, index) => {
|
|
618
658
|
const commit = commits.find((c) => c.hash.startsWith(match.hash));
|
|
619
659
|
if (!commit) {
|
|
620
660
|
return null;
|
|
621
661
|
}
|
|
662
|
+
const relevance = index === 0 ? "high" : index < 3 ? "medium" : "low";
|
|
622
663
|
return {
|
|
623
664
|
hash: commit.hash,
|
|
624
665
|
message: commit.message,
|
|
@@ -626,15 +667,9 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
|
|
|
626
667
|
email: commit.email,
|
|
627
668
|
date: commit.date,
|
|
628
669
|
reason: match.reason,
|
|
629
|
-
relevance
|
|
630
|
-
// First results are most relevant
|
|
670
|
+
relevance
|
|
631
671
|
};
|
|
632
672
|
}).filter((m) => m !== null);
|
|
633
|
-
enrichedMatches.forEach((match, index) => {
|
|
634
|
-
if (index === 0) match.relevance = "high";
|
|
635
|
-
else if (index < 3) match.relevance = "medium";
|
|
636
|
-
else match.relevance = "low";
|
|
637
|
-
});
|
|
638
673
|
return {
|
|
639
674
|
matches: enrichedMatches,
|
|
640
675
|
summary: result.object.summary
|