opencommit 3.2.11 → 3.2.12

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.
Files changed (3) hide show
  1. package/README.md +22 -0
  2. package/out/cli.cjs +646 -123
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -201,6 +201,28 @@ or for as a cheaper option:
201
201
  oco config set OCO_MODEL=gpt-3.5-turbo
202
202
  ```
203
203
 
204
+ ### Model Management
205
+
206
+ OpenCommit automatically fetches available models from your provider when you run `oco setup`. Models are cached for 7 days to reduce API calls.
207
+
208
+ To see available models for your current provider:
209
+
210
+ ```sh
211
+ oco models
212
+ ```
213
+
214
+ To refresh the model list (e.g., after new models are released):
215
+
216
+ ```sh
217
+ oco models --refresh
218
+ ```
219
+
220
+ To see models for a specific provider:
221
+
222
+ ```sh
223
+ oco models --provider anthropic
224
+ ```
225
+
204
226
  ### Switch to other LLM providers with a custom URL
205
227
 
206
228
  By default OpenCommit uses [OpenAI](https://openai.com).
package/out/cli.cjs CHANGED
@@ -48509,7 +48509,7 @@ function G3(t2, e3) {
48509
48509
  // package.json
48510
48510
  var package_default = {
48511
48511
  name: "opencommit",
48512
- version: "3.2.11",
48512
+ version: "3.2.12",
48513
48513
  description: "Auto-generate impressive commits in 1 second. Killing lame commits with AI \u{1F92F}\u{1F52B}",
48514
48514
  keywords: [
48515
48515
  "git",
@@ -53911,9 +53911,6 @@ var { AnthropicError: AnthropicError2, APIError: APIError2, APIConnectionError:
53911
53911
  })(Anthropic || (Anthropic = {}));
53912
53912
  var sdk_default = Anthropic;
53913
53913
 
53914
- // src/engine/anthropic.ts
53915
- init_dist2();
53916
-
53917
53914
  // node_modules/axios/lib/helpers/bind.js
53918
53915
  function bind(fn, thisArg) {
53919
53916
  return function wrap() {
@@ -57224,6 +57221,51 @@ var {
57224
57221
  } = axios_default;
57225
57222
 
57226
57223
  // src/utils/errors.ts
57224
+ var PROVIDER_BILLING_URLS = {
57225
+ ["anthropic" /* ANTHROPIC */]: "https://console.anthropic.com/settings/billing",
57226
+ ["openai" /* OPENAI */]: "https://platform.openai.com/settings/organization/billing",
57227
+ ["gemini" /* GEMINI */]: "https://aistudio.google.com/app/plan",
57228
+ ["groq" /* GROQ */]: "https://console.groq.com/settings/billing",
57229
+ ["mistral" /* MISTRAL */]: "https://console.mistral.ai/billing/",
57230
+ ["deepseek" /* DEEPSEEK */]: "https://platform.deepseek.com/usage",
57231
+ ["openrouter" /* OPENROUTER */]: "https://openrouter.ai/credits",
57232
+ ["aimlapi" /* AIMLAPI */]: "https://aimlapi.com/app/billing",
57233
+ ["azure" /* AZURE */]: "https://portal.azure.com/#view/Microsoft_Azure_CostManagement",
57234
+ ["ollama" /* OLLAMA */]: null,
57235
+ ["mlx" /* MLX */]: null,
57236
+ ["flowise" /* FLOWISE */]: null,
57237
+ ["test" /* TEST */]: null
57238
+ };
57239
+ var InsufficientCreditsError = class extends Error {
57240
+ constructor(provider, message) {
57241
+ super(message || `Insufficient credits or quota for provider '${provider}'`);
57242
+ this.name = "InsufficientCreditsError";
57243
+ this.provider = provider;
57244
+ }
57245
+ };
57246
+ var RateLimitError3 = class extends Error {
57247
+ constructor(provider, retryAfter, message) {
57248
+ super(message || `Rate limit exceeded for provider '${provider}'`);
57249
+ this.name = "RateLimitError";
57250
+ this.provider = provider;
57251
+ this.retryAfter = retryAfter;
57252
+ }
57253
+ };
57254
+ var ServiceUnavailableError = class extends Error {
57255
+ constructor(provider, statusCode = 503, message) {
57256
+ super(message || `Service unavailable for provider '${provider}'`);
57257
+ this.name = "ServiceUnavailableError";
57258
+ this.provider = provider;
57259
+ this.statusCode = statusCode;
57260
+ }
57261
+ };
57262
+ var AuthenticationError3 = class extends Error {
57263
+ constructor(provider, message) {
57264
+ super(message || `Authentication failed for provider '${provider}'`);
57265
+ this.name = "AuthenticationError";
57266
+ this.provider = provider;
57267
+ }
57268
+ };
57227
57269
  var ModelNotFoundError = class extends Error {
57228
57270
  constructor(modelName, provider, statusCode = 404) {
57229
57271
  super(`Model '${modelName}' not found for provider '${provider}'`);
@@ -57233,6 +57275,13 @@ var ModelNotFoundError = class extends Error {
57233
57275
  this.statusCode = statusCode;
57234
57276
  }
57235
57277
  };
57278
+ var ApiKeyMissingError = class extends Error {
57279
+ constructor(provider) {
57280
+ super(`API key is missing for provider '${provider}'`);
57281
+ this.name = "ApiKeyMissingError";
57282
+ this.provider = provider;
57283
+ }
57284
+ };
57236
57285
  function isModelNotFoundError(error) {
57237
57286
  if (error instanceof ModelNotFoundError) {
57238
57287
  return true;
@@ -57257,6 +57306,24 @@ function isModelNotFoundError(error) {
57257
57306
  }
57258
57307
  return false;
57259
57308
  }
57309
+ function isApiKeyError(error) {
57310
+ if (error instanceof ApiKeyMissingError) {
57311
+ return true;
57312
+ }
57313
+ if (error instanceof Error) {
57314
+ const message = error.message.toLowerCase();
57315
+ if (message.includes("api key") || message.includes("apikey") || message.includes("authentication") || message.includes("unauthorized") || message.includes("invalid_api_key") || message.includes("incorrect api key")) {
57316
+ return true;
57317
+ }
57318
+ if ("response" in error) {
57319
+ const response = error.response;
57320
+ if (response?.status === 401) {
57321
+ return true;
57322
+ }
57323
+ }
57324
+ }
57325
+ return false;
57326
+ }
57260
57327
  function getSuggestedModels(provider, failedModel) {
57261
57328
  const providerKey = provider.toLowerCase();
57262
57329
  const models = MODEL_LIST[providerKey];
@@ -57265,6 +57332,276 @@ function getSuggestedModels(provider, failedModel) {
57265
57332
  }
57266
57333
  return models.filter((m5) => m5 !== failedModel).slice(0, 5);
57267
57334
  }
57335
+ function isInsufficientCreditsError(error) {
57336
+ if (error instanceof InsufficientCreditsError) {
57337
+ return true;
57338
+ }
57339
+ if (error instanceof Error) {
57340
+ const message = error.message.toLowerCase();
57341
+ if (message.includes("insufficient") || message.includes("credit") || message.includes("quota") || message.includes("balance") || message.includes("billing") || message.includes("payment") || message.includes("exceeded") || message.includes("limit reached") || message.includes("no remaining")) {
57342
+ return true;
57343
+ }
57344
+ if ("status" in error && error.status === 402) {
57345
+ return true;
57346
+ }
57347
+ if ("response" in error) {
57348
+ const response = error.response;
57349
+ if (response?.status === 402) {
57350
+ return true;
57351
+ }
57352
+ }
57353
+ }
57354
+ return false;
57355
+ }
57356
+ function isRateLimitError(error) {
57357
+ if (error instanceof RateLimitError3) {
57358
+ return true;
57359
+ }
57360
+ if (error instanceof Error) {
57361
+ const message = error.message.toLowerCase();
57362
+ if (message.includes("rate limit") || message.includes("rate_limit") || message.includes("too many requests") || message.includes("throttle")) {
57363
+ return true;
57364
+ }
57365
+ if ("status" in error && error.status === 429) {
57366
+ return true;
57367
+ }
57368
+ if ("response" in error) {
57369
+ const response = error.response;
57370
+ if (response?.status === 429) {
57371
+ return true;
57372
+ }
57373
+ }
57374
+ }
57375
+ return false;
57376
+ }
57377
+ function isServiceUnavailableError(error) {
57378
+ if (error instanceof ServiceUnavailableError) {
57379
+ return true;
57380
+ }
57381
+ if (error instanceof Error) {
57382
+ const message = error.message.toLowerCase();
57383
+ if (message.includes("service unavailable") || message.includes("server error") || message.includes("internal error") || message.includes("temporarily unavailable") || message.includes("overloaded")) {
57384
+ return true;
57385
+ }
57386
+ const status = error.status || error.response?.status;
57387
+ if (status && status >= 500 && status < 600) {
57388
+ return true;
57389
+ }
57390
+ }
57391
+ return false;
57392
+ }
57393
+ function formatUserFriendlyError(error, provider) {
57394
+ const billingUrl = PROVIDER_BILLING_URLS[provider] || null;
57395
+ if (error instanceof InsufficientCreditsError) {
57396
+ return {
57397
+ title: "Insufficient Credits",
57398
+ message: `Your ${provider} account has insufficient credits or quota.`,
57399
+ helpUrl: billingUrl,
57400
+ suggestion: "Add credits to your account to continue using the service."
57401
+ };
57402
+ }
57403
+ if (error instanceof RateLimitError3) {
57404
+ const retryMsg = error.retryAfter ? `Please wait ${error.retryAfter} seconds before retrying.` : "Please wait a moment before retrying.";
57405
+ return {
57406
+ title: "Rate Limit Exceeded",
57407
+ message: `You've made too many requests to ${provider}.`,
57408
+ helpUrl: billingUrl,
57409
+ suggestion: retryMsg
57410
+ };
57411
+ }
57412
+ if (error instanceof ServiceUnavailableError) {
57413
+ return {
57414
+ title: "Service Unavailable",
57415
+ message: `The ${provider} service is temporarily unavailable.`,
57416
+ helpUrl: null,
57417
+ suggestion: "Please try again in a few moments."
57418
+ };
57419
+ }
57420
+ if (error instanceof AuthenticationError3) {
57421
+ return {
57422
+ title: "Authentication Failed",
57423
+ message: `Your ${provider} API key is invalid or expired.`,
57424
+ helpUrl: billingUrl,
57425
+ suggestion: "Run `oco setup` to configure a valid API key."
57426
+ };
57427
+ }
57428
+ if (error instanceof ModelNotFoundError) {
57429
+ return {
57430
+ title: "Model Not Found",
57431
+ message: `The model '${error.modelName}' is not available for ${provider}.`,
57432
+ helpUrl: null,
57433
+ suggestion: "Run `oco setup` to select a valid model."
57434
+ };
57435
+ }
57436
+ if (isInsufficientCreditsError(error)) {
57437
+ return {
57438
+ title: "Insufficient Credits",
57439
+ message: `Your ${provider} account has insufficient credits or quota.`,
57440
+ helpUrl: billingUrl,
57441
+ suggestion: "Add credits to your account to continue using the service."
57442
+ };
57443
+ }
57444
+ if (isRateLimitError(error)) {
57445
+ return {
57446
+ title: "Rate Limit Exceeded",
57447
+ message: `You've made too many requests to ${provider}.`,
57448
+ helpUrl: billingUrl,
57449
+ suggestion: "Please wait a moment before retrying."
57450
+ };
57451
+ }
57452
+ if (isServiceUnavailableError(error)) {
57453
+ return {
57454
+ title: "Service Unavailable",
57455
+ message: `The ${provider} service is temporarily unavailable.`,
57456
+ helpUrl: null,
57457
+ suggestion: "Please try again in a few moments."
57458
+ };
57459
+ }
57460
+ if (isApiKeyError(error)) {
57461
+ return {
57462
+ title: "Authentication Failed",
57463
+ message: `Your ${provider} API key is invalid or expired.`,
57464
+ helpUrl: billingUrl,
57465
+ suggestion: "Run `oco setup` to configure a valid API key."
57466
+ };
57467
+ }
57468
+ if (isModelNotFoundError(error)) {
57469
+ const model = error.modelName || error.model || "unknown";
57470
+ return {
57471
+ title: "Model Not Found",
57472
+ message: `The model '${model}' is not available for ${provider}.`,
57473
+ helpUrl: null,
57474
+ suggestion: "Run `oco setup` to select a valid model."
57475
+ };
57476
+ }
57477
+ const errorMessage = error instanceof Error ? error.message : String(error);
57478
+ return {
57479
+ title: "Error",
57480
+ message: errorMessage,
57481
+ helpUrl: null,
57482
+ suggestion: "Run `oco setup` to reconfigure or check your settings."
57483
+ };
57484
+ }
57485
+ function printFormattedError(formatted) {
57486
+ let output = `
57487
+ ${source_default.red("\u2716")} ${source_default.bold.red(formatted.title)}
57488
+ `;
57489
+ output += ` ${formatted.message}
57490
+ `;
57491
+ if (formatted.helpUrl) {
57492
+ output += `
57493
+ ${source_default.cyan("Help:")} ${source_default.underline(formatted.helpUrl)}
57494
+ `;
57495
+ }
57496
+ if (formatted.suggestion) {
57497
+ output += `
57498
+ ${source_default.yellow("Suggestion:")} ${formatted.suggestion}
57499
+ `;
57500
+ }
57501
+ return output;
57502
+ }
57503
+
57504
+ // src/utils/engineErrorHandler.ts
57505
+ function getStatusCode(error) {
57506
+ if (typeof error?.status === "number") {
57507
+ return error.status;
57508
+ }
57509
+ if (axios_default.isAxiosError(error)) {
57510
+ return error.response?.status ?? null;
57511
+ }
57512
+ if (typeof error?.response?.status === "number") {
57513
+ return error.response.status;
57514
+ }
57515
+ return null;
57516
+ }
57517
+ function getRetryAfter(error) {
57518
+ const headers = error?.response?.headers;
57519
+ if (headers) {
57520
+ const retryAfter = headers["retry-after"] || headers["Retry-After"];
57521
+ if (retryAfter) {
57522
+ const seconds = parseInt(retryAfter, 10);
57523
+ if (!isNaN(seconds)) {
57524
+ return seconds;
57525
+ }
57526
+ }
57527
+ }
57528
+ return void 0;
57529
+ }
57530
+ function extractErrorMessage(error) {
57531
+ if (error instanceof Error) {
57532
+ return error.message;
57533
+ }
57534
+ const apiError = error?.response?.data?.error;
57535
+ if (apiError) {
57536
+ if (typeof apiError === "string") {
57537
+ return apiError;
57538
+ }
57539
+ if (apiError.message) {
57540
+ return apiError.message;
57541
+ }
57542
+ }
57543
+ const errorData = error?.error;
57544
+ if (errorData) {
57545
+ if (typeof errorData === "string") {
57546
+ return errorData;
57547
+ }
57548
+ if (errorData.message) {
57549
+ return errorData.message;
57550
+ }
57551
+ }
57552
+ if (typeof error === "string") {
57553
+ return error;
57554
+ }
57555
+ return "An unknown error occurred";
57556
+ }
57557
+ function isModelNotFoundMessage(message) {
57558
+ const lowerMessage = message.toLowerCase();
57559
+ return lowerMessage.includes("model") && (lowerMessage.includes("not found") || lowerMessage.includes("does not exist") || lowerMessage.includes("invalid") || lowerMessage.includes("pull")) || lowerMessage.includes("does_not_exist");
57560
+ }
57561
+ function isInsufficientCreditsMessage(message) {
57562
+ const lowerMessage = message.toLowerCase();
57563
+ return lowerMessage.includes("insufficient") || lowerMessage.includes("credit") || lowerMessage.includes("quota") || lowerMessage.includes("balance too low") || lowerMessage.includes("billing") || lowerMessage.includes("payment required") || lowerMessage.includes("exceeded");
57564
+ }
57565
+ function normalizeEngineError(error, provider, model) {
57566
+ if (error instanceof ModelNotFoundError || error instanceof AuthenticationError3 || error instanceof InsufficientCreditsError || error instanceof RateLimitError3 || error instanceof ServiceUnavailableError) {
57567
+ return error;
57568
+ }
57569
+ const statusCode = getStatusCode(error);
57570
+ const message = extractErrorMessage(error);
57571
+ switch (statusCode) {
57572
+ case 401:
57573
+ return new AuthenticationError3(provider, message);
57574
+ case 402:
57575
+ return new InsufficientCreditsError(provider, message);
57576
+ case 404:
57577
+ if (isModelNotFoundMessage(message)) {
57578
+ return new ModelNotFoundError(model, provider, 404);
57579
+ }
57580
+ return error instanceof Error ? error : new Error(message);
57581
+ case 429:
57582
+ const retryAfter = getRetryAfter(error);
57583
+ return new RateLimitError3(provider, retryAfter, message);
57584
+ case 500:
57585
+ case 502:
57586
+ case 503:
57587
+ case 504:
57588
+ return new ServiceUnavailableError(provider, statusCode, message);
57589
+ }
57590
+ if (isModelNotFoundMessage(message)) {
57591
+ return new ModelNotFoundError(model, provider, 404);
57592
+ }
57593
+ if (isInsufficientCreditsMessage(message)) {
57594
+ return new InsufficientCreditsError(provider, message);
57595
+ }
57596
+ const lowerMessage = message.toLowerCase();
57597
+ if (lowerMessage.includes("rate limit") || lowerMessage.includes("rate_limit") || lowerMessage.includes("too many requests")) {
57598
+ return new RateLimitError3(provider, void 0, message);
57599
+ }
57600
+ if (lowerMessage.includes("unauthorized") || lowerMessage.includes("api key") || lowerMessage.includes("apikey") || lowerMessage.includes("authentication") || lowerMessage.includes("invalid_api_key")) {
57601
+ return new AuthenticationError3(provider, message);
57602
+ }
57603
+ return error instanceof Error ? error : new Error(message);
57604
+ }
57268
57605
 
57269
57606
  // src/utils/removeContentTags.ts
57270
57607
  function removeContentTags(content, tag) {
@@ -57342,25 +57679,7 @@ var AnthropicEngine = class {
57342
57679
  let content = message;
57343
57680
  return removeContentTags(content, "think");
57344
57681
  } catch (error) {
57345
- const err = error;
57346
- if (err.message?.toLowerCase().includes("model") && (err.message?.toLowerCase().includes("not found") || err.message?.toLowerCase().includes("does not exist") || err.message?.toLowerCase().includes("invalid"))) {
57347
- throw new ModelNotFoundError(this.config.model, "anthropic", 404);
57348
- }
57349
- if ("status" in error && error.status === 404) {
57350
- throw new ModelNotFoundError(this.config.model, "anthropic", 404);
57351
- }
57352
- ce(`${source_default.red("\u2716")} ${err?.message || err}`);
57353
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
57354
- const anthropicAiError = error.response.data.error;
57355
- if (anthropicAiError?.message) ce(anthropicAiError.message);
57356
- ce(
57357
- "For help look into README https://github.com/di-sukharev/opencommit#setup"
57358
- );
57359
- }
57360
- if (axios_default.isAxiosError(error) && error.response?.status === 404) {
57361
- throw new ModelNotFoundError(this.config.model, "anthropic", 404);
57362
- }
57363
- throw err;
57682
+ throw normalizeEngineError(error, "anthropic", this.config.model);
57364
57683
  }
57365
57684
  };
57366
57685
  this.config = config7;
@@ -61176,7 +61495,6 @@ var OpenAIClient = class {
61176
61495
  };
61177
61496
 
61178
61497
  // src/engine/azure.ts
61179
- init_dist2();
61180
61498
  var AzureEngine = class {
61181
61499
  constructor(config7) {
61182
61500
  this.generateCommitMessage = async (messages) => {
@@ -61196,17 +61514,7 @@ var AzureEngine = class {
61196
61514
  let content = message?.content;
61197
61515
  return removeContentTags(content, "think");
61198
61516
  } catch (error) {
61199
- ce(`${source_default.red("\u2716")} ${this.config.model}`);
61200
- const err = error;
61201
- ce(`${source_default.red("\u2716")} ${JSON.stringify(error)}`);
61202
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
61203
- const openAiError = error.response.data.error;
61204
- if (openAiError?.message) ce(openAiError.message);
61205
- ce(
61206
- "For help look into README https://github.com/di-sukharev/opencommit#setup"
61207
- );
61208
- }
61209
- throw err;
61517
+ throw normalizeEngineError(error, "azure", this.config.model);
61210
61518
  }
61211
61519
  };
61212
61520
  this.config = config7;
@@ -61240,9 +61548,8 @@ var FlowiseEngine = class {
61240
61548
  const message = response.data;
61241
61549
  let content = message?.text;
61242
61550
  return removeContentTags(content, "think");
61243
- } catch (err) {
61244
- const message = err.response?.data?.error ?? err.message;
61245
- throw new Error("local model issues. details: " + message);
61551
+ } catch (error) {
61552
+ throw normalizeEngineError(error, "flowise", this.config.model);
61246
61553
  }
61247
61554
  }
61248
61555
  };
@@ -62102,18 +62409,7 @@ var GeminiEngine = class {
62102
62409
  const content = result.response.text();
62103
62410
  return removeContentTags(content, "think");
62104
62411
  } catch (error) {
62105
- const err = error;
62106
- if (err.message?.toLowerCase().includes("model") && (err.message?.toLowerCase().includes("not found") || err.message?.toLowerCase().includes("does not exist") || err.message?.toLowerCase().includes("invalid"))) {
62107
- throw new ModelNotFoundError(this.config.model, "gemini", 404);
62108
- }
62109
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
62110
- const geminiError = error.response.data.error;
62111
- if (geminiError) throw new Error(geminiError?.message);
62112
- }
62113
- if (axios_default.isAxiosError(error) && error.response?.status === 404) {
62114
- throw new ModelNotFoundError(this.config.model, "gemini", 404);
62115
- }
62116
- throw err;
62412
+ throw normalizeEngineError(error, "gemini", this.config.model);
62117
62413
  }
62118
62414
  }
62119
62415
  };
@@ -62146,15 +62442,8 @@ var OllamaEngine = class {
62146
62442
  const { message } = response.data;
62147
62443
  let content = message?.content;
62148
62444
  return removeContentTags(content, "think");
62149
- } catch (err) {
62150
- const message = err.response?.data?.error ?? err.message;
62151
- if (message?.toLowerCase().includes("model") && (message?.toLowerCase().includes("not found") || message?.toLowerCase().includes("does not exist") || message?.toLowerCase().includes("pull"))) {
62152
- throw new ModelNotFoundError(this.config.model, "ollama", 404);
62153
- }
62154
- if (err.response?.status === 404) {
62155
- throw new ModelNotFoundError(this.config.model, "ollama", 404);
62156
- }
62157
- throw new Error(`Ollama provider error: ${message}`);
62445
+ } catch (error) {
62446
+ throw normalizeEngineError(error, "ollama", this.config.model);
62158
62447
  }
62159
62448
  }
62160
62449
  };
@@ -62166,7 +62455,7 @@ __export(error_exports2, {
62166
62455
  APIConnectionTimeoutError: () => APIConnectionTimeoutError3,
62167
62456
  APIError: () => APIError3,
62168
62457
  APIUserAbortError: () => APIUserAbortError3,
62169
- AuthenticationError: () => AuthenticationError3,
62458
+ AuthenticationError: () => AuthenticationError4,
62170
62459
  BadRequestError: () => BadRequestError3,
62171
62460
  ConflictError: () => ConflictError3,
62172
62461
  ContentFilterFinishReasonError: () => ContentFilterFinishReasonError,
@@ -62175,7 +62464,7 @@ __export(error_exports2, {
62175
62464
  NotFoundError: () => NotFoundError3,
62176
62465
  OpenAIError: () => OpenAIError,
62177
62466
  PermissionDeniedError: () => PermissionDeniedError3,
62178
- RateLimitError: () => RateLimitError3,
62467
+ RateLimitError: () => RateLimitError4,
62179
62468
  UnprocessableEntityError: () => UnprocessableEntityError3
62180
62469
  });
62181
62470
 
@@ -63447,7 +63736,7 @@ var APIError3 = class _APIError extends OpenAIError {
63447
63736
  return new BadRequestError3(status, error, message, headers);
63448
63737
  }
63449
63738
  if (status === 401) {
63450
- return new AuthenticationError3(status, error, message, headers);
63739
+ return new AuthenticationError4(status, error, message, headers);
63451
63740
  }
63452
63741
  if (status === 403) {
63453
63742
  return new PermissionDeniedError3(status, error, message, headers);
@@ -63462,7 +63751,7 @@ var APIError3 = class _APIError extends OpenAIError {
63462
63751
  return new UnprocessableEntityError3(status, error, message, headers);
63463
63752
  }
63464
63753
  if (status === 429) {
63465
- return new RateLimitError3(status, error, message, headers);
63754
+ return new RateLimitError4(status, error, message, headers);
63466
63755
  }
63467
63756
  if (status >= 500) {
63468
63757
  return new InternalServerError3(status, error, message, headers);
@@ -63495,7 +63784,7 @@ var BadRequestError3 = class extends APIError3 {
63495
63784
  this.status = 400;
63496
63785
  }
63497
63786
  };
63498
- var AuthenticationError3 = class extends APIError3 {
63787
+ var AuthenticationError4 = class extends APIError3 {
63499
63788
  constructor() {
63500
63789
  super(...arguments);
63501
63790
  this.status = 401;
@@ -63525,7 +63814,7 @@ var UnprocessableEntityError3 = class extends APIError3 {
63525
63814
  this.status = 422;
63526
63815
  }
63527
63816
  };
63528
- var RateLimitError3 = class extends APIError3 {
63817
+ var RateLimitError4 = class extends APIError3 {
63529
63818
  constructor() {
63530
63819
  super(...arguments);
63531
63820
  this.status = 429;
@@ -66834,15 +67123,15 @@ OpenAI.APIConnectionTimeoutError = APIConnectionTimeoutError3;
66834
67123
  OpenAI.APIUserAbortError = APIUserAbortError3;
66835
67124
  OpenAI.NotFoundError = NotFoundError3;
66836
67125
  OpenAI.ConflictError = ConflictError3;
66837
- OpenAI.RateLimitError = RateLimitError3;
67126
+ OpenAI.RateLimitError = RateLimitError4;
66838
67127
  OpenAI.BadRequestError = BadRequestError3;
66839
- OpenAI.AuthenticationError = AuthenticationError3;
67128
+ OpenAI.AuthenticationError = AuthenticationError4;
66840
67129
  OpenAI.InternalServerError = InternalServerError3;
66841
67130
  OpenAI.PermissionDeniedError = PermissionDeniedError3;
66842
67131
  OpenAI.UnprocessableEntityError = UnprocessableEntityError3;
66843
67132
  OpenAI.toFile = toFile2;
66844
67133
  OpenAI.fileFromPath = fileFromPath4;
66845
- var { OpenAIError: OpenAIError2, APIError: APIError4, APIConnectionError: APIConnectionError4, APIConnectionTimeoutError: APIConnectionTimeoutError4, APIUserAbortError: APIUserAbortError4, NotFoundError: NotFoundError4, ConflictError: ConflictError4, RateLimitError: RateLimitError4, BadRequestError: BadRequestError4, AuthenticationError: AuthenticationError4, InternalServerError: InternalServerError4, PermissionDeniedError: PermissionDeniedError4, UnprocessableEntityError: UnprocessableEntityError4 } = error_exports2;
67134
+ var { OpenAIError: OpenAIError2, APIError: APIError4, APIConnectionError: APIConnectionError4, APIConnectionTimeoutError: APIConnectionTimeoutError4, APIUserAbortError: APIUserAbortError4, NotFoundError: NotFoundError4, ConflictError: ConflictError4, RateLimitError: RateLimitError5, BadRequestError: BadRequestError4, AuthenticationError: AuthenticationError5, InternalServerError: InternalServerError4, PermissionDeniedError: PermissionDeniedError4, UnprocessableEntityError: UnprocessableEntityError4 } = error_exports2;
66846
67135
  (function(OpenAI2) {
66847
67136
  OpenAI2.Page = Page;
66848
67137
  OpenAI2.CursorPage = CursorPage;
@@ -66883,21 +67172,7 @@ var OpenAiEngine = class {
66883
67172
  let content = message?.content;
66884
67173
  return removeContentTags(content, "think");
66885
67174
  } catch (error) {
66886
- const err = error;
66887
- if (err.message?.toLowerCase().includes("model") && (err.message?.toLowerCase().includes("not found") || err.message?.toLowerCase().includes("does not exist") || err.message?.toLowerCase().includes("invalid"))) {
66888
- throw new ModelNotFoundError(this.config.model, "openai", 404);
66889
- }
66890
- if ("status" in error && error.status === 404) {
66891
- throw new ModelNotFoundError(this.config.model, "openai", 404);
66892
- }
66893
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
66894
- const openAiError = error.response.data.error;
66895
- if (openAiError) throw new Error(openAiError.message);
66896
- }
66897
- if (axios_default.isAxiosError(error) && error.response?.status === 404) {
66898
- throw new ModelNotFoundError(this.config.model, "openai", 404);
66899
- }
66900
- throw err;
67175
+ throw normalizeEngineError(error, "openai", this.config.model);
66901
67176
  }
66902
67177
  };
66903
67178
  this.config = config7;
@@ -66941,12 +67216,7 @@ var MistralAiEngine = class {
66941
67216
  let content = message.content;
66942
67217
  return removeContentTags(content, "think");
66943
67218
  } catch (error) {
66944
- const err = error;
66945
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
66946
- const mistralError = error.response.data.error;
66947
- if (mistralError) throw new Error(mistralError.message);
66948
- }
66949
- throw err;
67219
+ throw normalizeEngineError(error, "mistral", this.config.model);
66950
67220
  }
66951
67221
  };
66952
67222
  this.config = config7;
@@ -66995,9 +67265,8 @@ var MLXEngine = class {
66995
67265
  const message = choices[0].message;
66996
67266
  let content = message?.content;
66997
67267
  return removeContentTags(content, "think");
66998
- } catch (err) {
66999
- const message = err.response?.data?.error ?? err.message;
67000
- throw new Error(`MLX provider error: ${message}`);
67268
+ } catch (error) {
67269
+ throw normalizeEngineError(error, "mlx", this.config.model);
67001
67270
  }
67002
67271
  }
67003
67272
  };
@@ -67027,12 +67296,7 @@ var DeepseekEngine = class extends OpenAiEngine {
67027
67296
  let content = message?.content;
67028
67297
  return removeContentTags(content, "think");
67029
67298
  } catch (error) {
67030
- const err = error;
67031
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
67032
- const openAiError = error.response.data.error;
67033
- if (openAiError) throw new Error(openAiError.message);
67034
- }
67035
- throw err;
67299
+ throw normalizeEngineError(error, "deepseek", this.config.model);
67036
67300
  }
67037
67301
  };
67038
67302
  }
@@ -67051,12 +67315,7 @@ var AimlApiEngine = class {
67051
67315
  const message = response.data.choices?.[0]?.message;
67052
67316
  return message?.content ?? null;
67053
67317
  } catch (error) {
67054
- const err = error;
67055
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
67056
- const apiError = error.response.data.error;
67057
- if (apiError) throw new Error(apiError.message);
67058
- }
67059
- throw err;
67318
+ throw normalizeEngineError(error, "aimlapi", this.config.model);
67060
67319
  }
67061
67320
  };
67062
67321
  this.client = axios_default.create({
@@ -67086,12 +67345,7 @@ var OpenRouterEngine = class {
67086
67345
  let content = message?.content;
67087
67346
  return removeContentTags(content, "think");
67088
67347
  } catch (error) {
67089
- const err = error;
67090
- if (axios_default.isAxiosError(error) && error.response?.status === 401) {
67091
- const openRouterError = error.response.data.error;
67092
- if (openRouterError) throw new Error(openRouterError.message);
67093
- }
67094
- throw err;
67348
+ throw normalizeEngineError(error, "openrouter", this.config.model);
67095
67349
  }
67096
67350
  };
67097
67351
  this.client = axios_default.create({
@@ -68153,9 +68407,10 @@ ${source_default.grey("\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2
68153
68407
  commitGenerationSpinner.stop(
68154
68408
  `${source_default.red("\u2716")} Failed to generate the commit message`
68155
68409
  );
68156
- console.log(error);
68157
- const err = error;
68158
- ce(`${source_default.red("\u2716")} ${err?.message || err}`);
68410
+ const errorConfig = getConfig();
68411
+ const provider = errorConfig.OCO_AI_PROVIDER || "openai";
68412
+ const formatted = formatUserFriendlyError(error, provider);
68413
+ ce(printFormattedError(formatted));
68159
68414
  process.exit(1);
68160
68415
  }
68161
68416
  };
@@ -68458,9 +68713,97 @@ async function fetchOllamaModels(baseUrl = "http://localhost:11434") {
68458
68713
  return [];
68459
68714
  }
68460
68715
  }
68461
- async function fetchModelsForProvider(provider, apiKey, baseUrl) {
68716
+ async function fetchAnthropicModels(apiKey) {
68717
+ try {
68718
+ const response = await fetch("https://api.anthropic.com/v1/models", {
68719
+ headers: {
68720
+ "x-api-key": apiKey,
68721
+ "anthropic-version": "2023-06-01"
68722
+ }
68723
+ });
68724
+ if (!response.ok) {
68725
+ return MODEL_LIST.anthropic;
68726
+ }
68727
+ const data = await response.json();
68728
+ const models = data.data?.map((m5) => m5.id).filter((id) => id.startsWith("claude-")).sort();
68729
+ return models && models.length > 0 ? models : MODEL_LIST.anthropic;
68730
+ } catch {
68731
+ return MODEL_LIST.anthropic;
68732
+ }
68733
+ }
68734
+ async function fetchMistralModels(apiKey) {
68735
+ try {
68736
+ const response = await fetch("https://api.mistral.ai/v1/models", {
68737
+ headers: {
68738
+ Authorization: `Bearer ${apiKey}`
68739
+ }
68740
+ });
68741
+ if (!response.ok) {
68742
+ return MODEL_LIST.mistral;
68743
+ }
68744
+ const data = await response.json();
68745
+ const models = data.data?.map((m5) => m5.id).sort();
68746
+ return models && models.length > 0 ? models : MODEL_LIST.mistral;
68747
+ } catch {
68748
+ return MODEL_LIST.mistral;
68749
+ }
68750
+ }
68751
+ async function fetchGroqModels(apiKey) {
68752
+ try {
68753
+ const response = await fetch("https://api.groq.com/openai/v1/models", {
68754
+ headers: {
68755
+ Authorization: `Bearer ${apiKey}`
68756
+ }
68757
+ });
68758
+ if (!response.ok) {
68759
+ return MODEL_LIST.groq;
68760
+ }
68761
+ const data = await response.json();
68762
+ const models = data.data?.map((m5) => m5.id).sort();
68763
+ return models && models.length > 0 ? models : MODEL_LIST.groq;
68764
+ } catch {
68765
+ return MODEL_LIST.groq;
68766
+ }
68767
+ }
68768
+ async function fetchOpenRouterModels(apiKey) {
68769
+ try {
68770
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
68771
+ headers: {
68772
+ Authorization: `Bearer ${apiKey}`
68773
+ }
68774
+ });
68775
+ if (!response.ok) {
68776
+ return MODEL_LIST.openrouter;
68777
+ }
68778
+ const data = await response.json();
68779
+ const models = data.data?.filter(
68780
+ (m5) => m5.context_length && m5.context_length > 0
68781
+ ).map((m5) => m5.id).sort();
68782
+ return models && models.length > 0 ? models : MODEL_LIST.openrouter;
68783
+ } catch {
68784
+ return MODEL_LIST.openrouter;
68785
+ }
68786
+ }
68787
+ async function fetchDeepSeekModels(apiKey) {
68788
+ try {
68789
+ const response = await fetch("https://api.deepseek.com/v1/models", {
68790
+ headers: {
68791
+ Authorization: `Bearer ${apiKey}`
68792
+ }
68793
+ });
68794
+ if (!response.ok) {
68795
+ return MODEL_LIST.deepseek;
68796
+ }
68797
+ const data = await response.json();
68798
+ const models = data.data?.map((m5) => m5.id).sort();
68799
+ return models && models.length > 0 ? models : MODEL_LIST.deepseek;
68800
+ } catch {
68801
+ return MODEL_LIST.deepseek;
68802
+ }
68803
+ }
68804
+ async function fetchModelsForProvider(provider, apiKey, baseUrl, forceRefresh = false) {
68462
68805
  const cache = readCache();
68463
- if (isCacheValid(cache) && cache.models[provider]) {
68806
+ if (!forceRefresh && isCacheValid(cache) && cache.models[provider]) {
68464
68807
  return cache.models[provider];
68465
68808
  }
68466
68809
  let models = [];
@@ -68476,25 +68819,45 @@ async function fetchModelsForProvider(provider, apiKey, baseUrl) {
68476
68819
  models = await fetchOllamaModels(baseUrl);
68477
68820
  break;
68478
68821
  case "anthropic" /* ANTHROPIC */:
68479
- models = MODEL_LIST.anthropic;
68822
+ if (apiKey) {
68823
+ models = await fetchAnthropicModels(apiKey);
68824
+ } else {
68825
+ models = MODEL_LIST.anthropic;
68826
+ }
68480
68827
  break;
68481
68828
  case "gemini" /* GEMINI */:
68482
68829
  models = MODEL_LIST.gemini;
68483
68830
  break;
68484
68831
  case "groq" /* GROQ */:
68485
- models = MODEL_LIST.groq;
68832
+ if (apiKey) {
68833
+ models = await fetchGroqModels(apiKey);
68834
+ } else {
68835
+ models = MODEL_LIST.groq;
68836
+ }
68486
68837
  break;
68487
68838
  case "mistral" /* MISTRAL */:
68488
- models = MODEL_LIST.mistral;
68839
+ if (apiKey) {
68840
+ models = await fetchMistralModels(apiKey);
68841
+ } else {
68842
+ models = MODEL_LIST.mistral;
68843
+ }
68489
68844
  break;
68490
68845
  case "deepseek" /* DEEPSEEK */:
68491
- models = MODEL_LIST.deepseek;
68846
+ if (apiKey) {
68847
+ models = await fetchDeepSeekModels(apiKey);
68848
+ } else {
68849
+ models = MODEL_LIST.deepseek;
68850
+ }
68492
68851
  break;
68493
68852
  case "aimlapi" /* AIMLAPI */:
68494
68853
  models = MODEL_LIST.aimlapi;
68495
68854
  break;
68496
68855
  case "openrouter" /* OPENROUTER */:
68497
- models = MODEL_LIST.openrouter;
68856
+ if (apiKey) {
68857
+ models = await fetchOpenRouterModels(apiKey);
68858
+ } else {
68859
+ models = MODEL_LIST.openrouter;
68860
+ }
68498
68861
  break;
68499
68862
  default:
68500
68863
  models = MODEL_LIST.openai;
@@ -68504,6 +68867,31 @@ async function fetchModelsForProvider(provider, apiKey, baseUrl) {
68504
68867
  writeCache(existingCache);
68505
68868
  return models;
68506
68869
  }
68870
+ function clearModelCache() {
68871
+ try {
68872
+ if ((0, import_fs5.existsSync)(MODEL_CACHE_PATH)) {
68873
+ (0, import_fs5.writeFileSync)(MODEL_CACHE_PATH, "{}", "utf8");
68874
+ }
68875
+ } catch {
68876
+ }
68877
+ }
68878
+ function getCacheInfo() {
68879
+ const cache = readCache();
68880
+ if (!cache) {
68881
+ return { timestamp: null, providers: [] };
68882
+ }
68883
+ return {
68884
+ timestamp: cache.timestamp,
68885
+ providers: Object.keys(cache.models || {})
68886
+ };
68887
+ }
68888
+ function getCachedModels(provider) {
68889
+ const cache = readCache();
68890
+ if (!cache || !cache.models[provider]) {
68891
+ return null;
68892
+ }
68893
+ return cache.models[provider];
68894
+ }
68507
68895
 
68508
68896
  // src/commands/setup.ts
68509
68897
  var PROVIDER_DISPLAY_NAMES = {
@@ -68582,17 +68970,42 @@ ${source_default.dim(` Get your key at: ${url2}`)}`;
68582
68970
  }
68583
68971
  });
68584
68972
  }
68973
+ function formatCacheAge(timestamp) {
68974
+ if (!timestamp) return "";
68975
+ const ageMs = Date.now() - timestamp;
68976
+ const days = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
68977
+ const hours = Math.floor(ageMs / (1e3 * 60 * 60));
68978
+ if (days > 0) {
68979
+ return `${days} day${days === 1 ? "" : "s"} ago`;
68980
+ } else if (hours > 0) {
68981
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
68982
+ }
68983
+ return "just now";
68984
+ }
68585
68985
  async function selectModel(provider, apiKey) {
68986
+ const providerDisplayName = PROVIDER_DISPLAY_NAMES[provider]?.split(" (")[0] || provider;
68586
68987
  const loadingSpinner = le();
68587
- loadingSpinner.start("Fetching available models...");
68988
+ loadingSpinner.start(`Fetching models from ${providerDisplayName}...`);
68588
68989
  let models = [];
68990
+ let usedFallback = false;
68589
68991
  try {
68590
68992
  models = await fetchModelsForProvider(provider, apiKey);
68591
68993
  } catch {
68994
+ usedFallback = true;
68592
68995
  const providerKey = provider.toLowerCase();
68593
68996
  models = MODEL_LIST[providerKey] || [];
68594
68997
  }
68595
- loadingSpinner.stop("Models loaded");
68998
+ const cacheInfo = getCacheInfo();
68999
+ const cacheAge = formatCacheAge(cacheInfo.timestamp);
69000
+ if (usedFallback) {
69001
+ loadingSpinner.stop(
69002
+ source_default.yellow("Could not fetch models from API. Using default list.")
69003
+ );
69004
+ } else if (cacheAge) {
69005
+ loadingSpinner.stop(`Models loaded ${source_default.dim(`(cached ${cacheAge})`)}`);
69006
+ } else {
69007
+ loadingSpinner.stop("Models loaded");
69008
+ }
68596
69009
  if (models.length === 0) {
68597
69010
  if (NO_API_KEY_PROVIDERS.includes(provider)) {
68598
69011
  return await J4({
@@ -68844,6 +69257,116 @@ var setupCommand = G3(
68844
69257
  }
68845
69258
  );
68846
69259
 
69260
+ // src/commands/models.ts
69261
+ init_dist2();
69262
+ function formatCacheAge2(timestamp) {
69263
+ if (!timestamp) return "never";
69264
+ const ageMs = Date.now() - timestamp;
69265
+ const days = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
69266
+ const hours = Math.floor(ageMs / (1e3 * 60 * 60));
69267
+ const minutes = Math.floor(ageMs / (1e3 * 60));
69268
+ if (days > 0) {
69269
+ return `${days} day${days === 1 ? "" : "s"} ago`;
69270
+ } else if (hours > 0) {
69271
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
69272
+ } else if (minutes > 0) {
69273
+ return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
69274
+ }
69275
+ return "just now";
69276
+ }
69277
+ async function listModels(provider, useCache = true) {
69278
+ const config7 = getConfig();
69279
+ const apiKey = config7.OCO_API_KEY;
69280
+ const currentModel = config7.OCO_MODEL;
69281
+ let models = [];
69282
+ if (useCache) {
69283
+ const cached = getCachedModels(provider);
69284
+ if (cached) {
69285
+ models = cached;
69286
+ }
69287
+ }
69288
+ if (models.length === 0) {
69289
+ const providerKey = provider.toLowerCase();
69290
+ models = MODEL_LIST[providerKey] || [];
69291
+ }
69292
+ console.log(`
69293
+ ${source_default.bold("Available models for")} ${source_default.cyan(provider)}:
69294
+ `);
69295
+ if (models.length === 0) {
69296
+ console.log(source_default.dim(" No models found"));
69297
+ } else {
69298
+ models.forEach((model) => {
69299
+ const isCurrent = model === currentModel;
69300
+ const prefix = isCurrent ? source_default.green("* ") : " ";
69301
+ const label = isCurrent ? source_default.green(model) : model;
69302
+ console.log(`${prefix}${label}`);
69303
+ });
69304
+ }
69305
+ console.log("");
69306
+ }
69307
+ async function refreshModels(provider) {
69308
+ const config7 = getConfig();
69309
+ const apiKey = config7.OCO_API_KEY;
69310
+ const loadingSpinner = le();
69311
+ loadingSpinner.start(`Fetching models from ${provider}...`);
69312
+ clearModelCache();
69313
+ try {
69314
+ const models = await fetchModelsForProvider(provider, apiKey, void 0, true);
69315
+ loadingSpinner.stop(`${source_default.green("+")} Fetched ${models.length} models`);
69316
+ await listModels(provider, true);
69317
+ } catch (error) {
69318
+ loadingSpinner.stop(source_default.red("Failed to fetch models"));
69319
+ console.error(source_default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
69320
+ }
69321
+ }
69322
+ var modelsCommand = G3(
69323
+ {
69324
+ name: "models" /* models */,
69325
+ help: {
69326
+ description: "List and manage cached models for your AI provider"
69327
+ },
69328
+ flags: {
69329
+ refresh: {
69330
+ type: Boolean,
69331
+ alias: "r",
69332
+ description: "Clear cache and re-fetch models from the provider",
69333
+ default: false
69334
+ },
69335
+ provider: {
69336
+ type: String,
69337
+ alias: "p",
69338
+ description: "Specify provider (defaults to current OCO_AI_PROVIDER)"
69339
+ }
69340
+ }
69341
+ },
69342
+ async ({ flags }) => {
69343
+ const config7 = getConfig();
69344
+ const provider = flags.provider || config7.OCO_AI_PROVIDER || "openai" /* OPENAI */;
69345
+ ae(source_default.bgCyan(" OpenCommit Models "));
69346
+ const cacheInfo = getCacheInfo();
69347
+ if (cacheInfo.timestamp) {
69348
+ console.log(
69349
+ source_default.dim(` Cache last updated: ${formatCacheAge2(cacheInfo.timestamp)}`)
69350
+ );
69351
+ if (cacheInfo.providers.length > 0) {
69352
+ console.log(
69353
+ source_default.dim(` Cached providers: ${cacheInfo.providers.join(", ")}`)
69354
+ );
69355
+ }
69356
+ } else {
69357
+ console.log(source_default.dim(" No cached models"));
69358
+ }
69359
+ if (flags.refresh) {
69360
+ await refreshModels(provider);
69361
+ } else {
69362
+ await listModels(provider);
69363
+ }
69364
+ ce(
69365
+ `Run ${source_default.cyan("oco models --refresh")} to update the model list`
69366
+ );
69367
+ }
69368
+ );
69369
+
68847
69370
  // src/utils/checkIsLatestVersion.ts
68848
69371
  init_dist2();
68849
69372
 
@@ -69036,7 +69559,7 @@ Z2(
69036
69559
  {
69037
69560
  version: package_default.version,
69038
69561
  name: "opencommit",
69039
- commands: [configCommand, hookCommand, commitlintConfigCommand, setupCommand],
69562
+ commands: [configCommand, hookCommand, commitlintConfigCommand, setupCommand, modelsCommand],
69040
69563
  flags: {
69041
69564
  fgm: {
69042
69565
  type: Boolean,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencommit",
3
- "version": "3.2.11",
3
+ "version": "3.2.12",
4
4
  "description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
5
5
  "keywords": [
6
6
  "git",