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 CHANGED
@@ -108,12 +108,16 @@ var FALLBACK_ENV_MAP = {
108
108
  };
109
109
  async function getKeytar() {
110
110
  try {
111
- return await import("keytar");
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 providers = ["gemini", "openai", "anthropic"];
150
+ const apiKeyProviders = ["gemini", "openai", "anthropic"];
141
151
  const results = await Promise.all(
142
- providers.map(async (provider) => ({
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
- var GUT_ROOT = join2(__dirname, "..");
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: "high"
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