gut-cli 0.1.14 → 0.1.15

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 CHANGED
@@ -114,6 +114,9 @@ async function getKeytar() {
114
114
  }
115
115
  }
116
116
  async function saveApiKey(provider, apiKey) {
117
+ if (provider === "ollama") {
118
+ throw new Error("Ollama does not require an API key");
119
+ }
117
120
  const keytar = await getKeytar();
118
121
  if (!keytar) {
119
122
  throw new Error("Keychain not available. Set environment variable instead.");
@@ -121,6 +124,9 @@ async function saveApiKey(provider, apiKey) {
121
124
  await keytar.setPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider], apiKey);
122
125
  }
123
126
  async function getApiKey(provider) {
127
+ if (provider === "ollama") {
128
+ return null;
129
+ }
124
130
  const envKey = process.env[ENV_VAR_MAP[provider]];
125
131
  if (envKey) return envKey;
126
132
  const fallbackKey = process.env[FALLBACK_ENV_MAP[provider]];
@@ -130,6 +136,9 @@ async function getApiKey(provider) {
130
136
  return keytar.getPassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
131
137
  }
132
138
  async function deleteApiKey(provider) {
139
+ if (provider === "ollama") {
140
+ throw new Error("Ollama does not use an API key");
141
+ }
133
142
  const keytar = await getKeytar();
134
143
  if (!keytar) {
135
144
  throw new Error("Keychain not available.");
@@ -137,20 +146,22 @@ async function deleteApiKey(provider) {
137
146
  return keytar.deletePassword(SERVICE_NAME, PROVIDER_KEY_MAP[provider]);
138
147
  }
139
148
  async function listProviders() {
140
- const providers = ["gemini", "openai", "anthropic"];
149
+ const apiKeyProviders = ["gemini", "openai", "anthropic"];
141
150
  const results = await Promise.all(
142
- providers.map(async (provider) => ({
151
+ apiKeyProviders.map(async (provider) => ({
143
152
  provider,
144
153
  hasKey: !!await getApiKey(provider)
145
154
  }))
146
155
  );
156
+ results.push({ provider: "ollama", hasKey: true });
147
157
  return results;
148
158
  }
149
159
  function getProviderDisplayName(provider) {
150
160
  const names = {
151
161
  gemini: "Google Gemini",
152
162
  openai: "OpenAI",
153
- anthropic: "Anthropic Claude"
163
+ anthropic: "Anthropic Claude",
164
+ ollama: "Ollama (Local)"
154
165
  };
155
166
  return names[provider];
156
167
  }
@@ -270,6 +281,7 @@ import { generateText, generateObject } from "ai";
270
281
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
271
282
  import { createOpenAI } from "@ai-sdk/openai";
272
283
  import { createAnthropic } from "@ai-sdk/anthropic";
284
+ import { createOllama } from "ollama-ai-provider";
273
285
  import { z } from "zod";
274
286
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
275
287
  import { join as join2, dirname } from "path";
@@ -410,29 +422,45 @@ function applyTemplate(userTemplate, templateName, variables) {
410
422
  var DEFAULT_MODELS = {
411
423
  gemini: "gemini-2.0-flash",
412
424
  openai: "gpt-4o-mini",
413
- anthropic: "claude-sonnet-4-20250514"
425
+ anthropic: "claude-sonnet-4-20250514",
426
+ ollama: "llama3.2"
414
427
  };
415
428
  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
429
  const modelName = options.model || DEFAULT_MODELS[options.provider];
430
+ async function resolveApiKey() {
431
+ if (options.apiKey) return options.apiKey;
432
+ return getApiKey(options.provider);
433
+ }
434
+ if (options.provider !== "ollama") {
435
+ const apiKey = await resolveApiKey();
436
+ if (!apiKey) {
437
+ throw new Error(
438
+ `No API key found for ${options.provider}. Run: gut auth login --provider ${options.provider}`
439
+ );
440
+ }
441
+ }
423
442
  switch (options.provider) {
424
443
  case "gemini": {
444
+ const apiKey = await resolveApiKey();
425
445
  const google = createGoogleGenerativeAI({ apiKey });
426
446
  return google(modelName);
427
447
  }
428
448
  case "openai": {
449
+ const apiKey = await resolveApiKey();
429
450
  const openai = createOpenAI({ apiKey });
430
451
  return openai(modelName);
431
452
  }
432
453
  case "anthropic": {
454
+ const apiKey = await resolveApiKey();
433
455
  const anthropic = createAnthropic({ apiKey });
434
456
  return anthropic(modelName);
435
457
  }
458
+ case "ollama": {
459
+ const ollama = createOllama({
460
+ baseURL: options.ollamaBaseUrl || "http://localhost:11434/api"
461
+ });
462
+ return ollama(modelName);
463
+ }
436
464
  }
437
465
  }
438
466
  async function generateCommitMessage(diff, options, template) {
@@ -614,11 +642,12 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
614
642
  schema: CommitSearchSchema,
615
643
  prompt
616
644
  });
617
- const enrichedMatches = result.object.matches.map((match) => {
645
+ const enrichedMatches = result.object.matches.map((match, index) => {
618
646
  const commit = commits.find((c) => c.hash.startsWith(match.hash));
619
647
  if (!commit) {
620
648
  return null;
621
649
  }
650
+ const relevance = index === 0 ? "high" : index < 3 ? "medium" : "low";
622
651
  return {
623
652
  hash: commit.hash,
624
653
  message: commit.message,
@@ -626,15 +655,9 @@ async function searchCommits(query, commits, options, maxResults = 5, template)
626
655
  email: commit.email,
627
656
  date: commit.date,
628
657
  reason: match.reason,
629
- relevance: "high"
630
- // First results are most relevant
658
+ relevance
631
659
  };
632
660
  }).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
661
  return {
639
662
  matches: enrichedMatches,
640
663
  summary: result.object.summary