@kyubiware/commit-mint 0.10.2 โ†’ 0.12.0

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/bin.mjs CHANGED
@@ -33,7 +33,7 @@ var __exportAll = (all, no_symbols) => {
33
33
  //#region package.json
34
34
  var package_default = {
35
35
  name: "@kyubiware/commit-mint",
36
- version: "0.10.2",
36
+ version: "0.12.0",
37
37
  description: "๐ŸŒฟ AI-powered git commit tool โ€” auto-group changed files, generate messages, run pre-commit checks",
38
38
  type: "module",
39
39
  bin: { "cmint": "./dist/bin.mjs" },
@@ -135,13 +135,19 @@ const PROVIDER_CONFIGS = {
135
135
  mistral: {
136
136
  baseURL: "https://api.mistral.ai/v1",
137
137
  defaultModel: "mistral-small"
138
+ },
139
+ omniroute: {
140
+ baseURL: "http://localhost:20128/v1",
141
+ defaultModel: "auto",
142
+ requiresApiKey: false
138
143
  }
139
144
  };
140
145
  const ALLOWED_PROVIDERS = Object.keys(PROVIDER_CONFIGS);
141
146
  const PROVIDER_ENV_KEYS = {
142
147
  groq: "GROQ_API_KEY",
143
148
  cerebras: "CEREBRAS_API_KEY",
144
- mistral: "MISTRAL_API_KEY"
149
+ mistral: "MISTRAL_API_KEY",
150
+ omniroute: "OMNIROUTE_API_KEY"
145
151
  };
146
152
  function formatProviderName(provider) {
147
153
  return provider.charAt(0).toUpperCase() + provider.slice(1);
@@ -165,7 +171,7 @@ function createFetchClient(baseURL, apiKey, timeout) {
165
171
  method: "POST",
166
172
  headers: {
167
173
  "Content-Type": "application/json",
168
- Authorization: `Bearer ${apiKey}`
174
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
169
175
  },
170
176
  body: JSON.stringify(params),
171
177
  signal: controller.signal
@@ -249,6 +255,10 @@ async function getProviderApiKey(provider) {
249
255
  return config[configKey];
250
256
  }
251
257
  debug("getProviderApiKey(%s): not found", provider);
258
+ if (PROVIDER_CONFIGS[provider]?.requiresApiKey === false) {
259
+ debug("getProviderApiKey(%s): optional key not set, continuing without auth", provider);
260
+ return "";
261
+ }
252
262
  throw new Error(`Please set your ${formatProviderName(provider)} API key via \`cmint config set ${envVar}=<your token>\``);
253
263
  }
254
264
  /** Check if a model name is the default for a provider OTHER than the given one. */
@@ -4081,7 +4091,8 @@ async function handleStaging(changedFiles, flags) {
4081
4091
  }
4082
4092
  return {
4083
4093
  changedFiles: currentFiles,
4084
- skipStaging
4094
+ skipStaging,
4095
+ stagedAll: stageAllFlag
4085
4096
  };
4086
4097
  }
4087
4098
  /** Run user-defined pre-commit checks from cmint config */
@@ -4129,6 +4140,7 @@ async function commitCommand(flags, version) {
4129
4140
  let changedFiles = await getChangedFiles();
4130
4141
  debug("Changed files:", changedFiles.length);
4131
4142
  const s = spinner();
4143
+ let stagedAll = true;
4132
4144
  try {
4133
4145
  if (flags.single) {
4134
4146
  debug("Single-commit mode: staging all files");
@@ -4144,6 +4156,7 @@ async function commitCommand(flags, version) {
4144
4156
  const result = await handleStaging(changedFiles, flags);
4145
4157
  if (!result) return;
4146
4158
  changedFiles = result.changedFiles;
4159
+ stagedAll = result.stagedAll;
4147
4160
  }
4148
4161
  } catch (err) {
4149
4162
  s.stop(red("Staging failed."));
@@ -4152,106 +4165,115 @@ async function commitCommand(flags, version) {
4152
4165
  outro(red(`Failed to stage files: ${msg}`));
4153
4166
  process.exit(1);
4154
4167
  }
4155
- changedFiles = await getChangedFiles();
4156
- if (!flags.noCheck && await getRunChecks()) await runPreCommitChecks(changedFiles, false);
4157
- const diffResult = await getStagedDiff();
4158
- if (!diffResult) {
4159
- debug("No staged changes found after staging");
4160
- outro(red("No staged changes found."));
4161
- process.exit(1);
4162
- }
4163
- if ("excludedFiles" in diffResult) {
4164
- debug("All staged files are excluded:", diffResult.excludedFiles);
4165
- const message = buildExcludedFilesMessage(diffResult.excludedFiles);
4166
- log.info(diffResult.excludedFiles.map((f) => ` ${f}`).join("\n"));
4167
- await saveCachedCommit(repoRoot, message);
4168
- s.start("Running pre-commit hooks...");
4169
- const result = await commitWithRecovery(message, s, await getHead());
4170
- if (result === "committed") {
4171
- outro(green("Done."));
4172
- return;
4168
+ while (true) {
4169
+ changedFiles = await getChangedFiles();
4170
+ if (!flags.noCheck && await getRunChecks()) await runPreCommitChecks(changedFiles, false);
4171
+ const diffResult = await getStagedDiff();
4172
+ if (!diffResult) {
4173
+ debug("No staged changes found after staging");
4174
+ outro(red("No staged changes found."));
4175
+ process.exit(1);
4173
4176
  }
4174
- if (result === "cancelled") process.exit(1);
4175
- return;
4176
- }
4177
- debug("Staged files:", diffResult.files);
4178
- debug("Diff length:", diffResult.diff.length, "chars");
4179
- log.info(diffResult.files.map((f) => ` ${f}`).join("\n"));
4180
- let message;
4181
- if (flags.message) {
4182
- debug("Using provided message:", flags.message);
4183
- message = flags.message;
4184
- } else {
4185
- const config = await readConfig();
4186
- const provider = isValidProvider(config.provider ?? "groq") ? config.provider : "groq";
4187
- try {
4188
- await getProviderApiKey(provider);
4189
- debug("API key found");
4190
- } catch {
4191
- debug("No API key found, prompting user");
4192
- const { text: promptText } = await import("@clack/prompts");
4193
- const configKey = PROVIDER_ENV_KEYS[provider];
4194
- const key = await promptText({
4195
- message: `Enter your ${formatProviderName(provider)} API key:`,
4196
- placeholder: provider === "groq" ? "gsk_..." : "...",
4197
- validate: (v) => v?.trim() ? void 0 : "API key is required"
4198
- });
4199
- if (isCancel(key)) {
4200
- outro(dim("Cancelled."));
4201
- return;
4177
+ let commitResult;
4178
+ if ("excludedFiles" in diffResult) {
4179
+ debug("All staged files are excluded:", diffResult.excludedFiles);
4180
+ const message = buildExcludedFilesMessage(diffResult.excludedFiles);
4181
+ log.info(diffResult.excludedFiles.map((f) => ` ${f}`).join("\n"));
4182
+ await saveCachedCommit(repoRoot, message);
4183
+ s.start("Running pre-commit hooks...");
4184
+ commitResult = await commitWithRecovery(message, s, await getHead());
4185
+ } else {
4186
+ debug("Staged files:", diffResult.files);
4187
+ debug("Diff length:", diffResult.diff.length, "chars");
4188
+ log.info(diffResult.files.map((f) => ` ${f}`).join("\n"));
4189
+ let message;
4190
+ if (flags.message) {
4191
+ debug("Using provided message:", flags.message);
4192
+ message = flags.message;
4193
+ } else {
4194
+ const config = await readConfig();
4195
+ const provider = isValidProvider(config.provider ?? "groq") ? config.provider : "groq";
4196
+ try {
4197
+ await getProviderApiKey(provider);
4198
+ debug("API key found");
4199
+ } catch {
4200
+ debug("No API key found, prompting user");
4201
+ const { text: promptText } = await import("@clack/prompts");
4202
+ const configKey = PROVIDER_ENV_KEYS[provider];
4203
+ const key = await promptText({
4204
+ message: `Enter your ${formatProviderName(provider)} API key:`,
4205
+ placeholder: provider === "groq" ? "gsk_..." : "...",
4206
+ validate: (v) => v?.trim() ? void 0 : "API key is required"
4207
+ });
4208
+ if (isCancel(key)) {
4209
+ outro(dim("Cancelled."));
4210
+ return;
4211
+ }
4212
+ await setConfigValue(configKey, String(key).trim());
4213
+ debug("API key saved to config");
4214
+ }
4215
+ s.start("Generating commit message...");
4216
+ try {
4217
+ const genStart = Date.now();
4218
+ message = await generateMessage(diffResult.diff, flags.hint);
4219
+ debug("generateMessage took %d ms", Date.now() - genStart);
4220
+ debug("Generated message:", message);
4221
+ } catch (err) {
4222
+ s.stop(red("Failed to generate message."));
4223
+ debug("Message generation failed:", err instanceof Error ? err.message : String(err));
4224
+ outro(red(err instanceof Error ? err.message : String(err)));
4225
+ return;
4226
+ }
4227
+ s.stop("Message generated");
4228
+ }
4229
+ if (flags.single || await getAutoAccept()) {
4230
+ debug("Auto-accept ON: skipping review step");
4231
+ log.info(message);
4232
+ } else {
4233
+ const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
4234
+ const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
4235
+ debug("Regenerating with combined hint:", combinedHint);
4236
+ s.start("Regenerating commit message...");
4237
+ try {
4238
+ const newMessage = await generateMessage(diffResult.diff, combinedHint);
4239
+ s.stop("Message regenerated");
4240
+ return newMessage;
4241
+ } catch (err) {
4242
+ s.stop(red("Regeneration failed"));
4243
+ throw err;
4244
+ }
4245
+ } });
4246
+ if (reviewed === null) {
4247
+ outro(dim("Cancelled."));
4248
+ return;
4249
+ }
4250
+ message = reviewed;
4202
4251
  }
4203
- await setConfigValue(configKey, String(key).trim());
4204
- debug("API key saved to config");
4252
+ await saveCachedCommit(repoRoot, message);
4253
+ debug("Message cached for repo:", repoRoot);
4254
+ s.start("Running pre-commit hooks...");
4255
+ const headBefore = await getHead();
4256
+ debug("HEAD before commit:", headBefore);
4257
+ commitResult = await commitWithRecovery(message, s, headBefore);
4258
+ debug("Commit result:", commitResult);
4205
4259
  }
4206
- s.start("Generating commit message...");
4207
- try {
4208
- const genStart = Date.now();
4209
- message = await generateMessage(diffResult.diff, flags.hint);
4210
- debug("generateMessage took %d ms", Date.now() - genStart);
4211
- debug("Generated message:", message);
4212
- } catch (err) {
4213
- s.stop(red("Failed to generate message."));
4214
- debug("Message generation failed:", err instanceof Error ? err.message : String(err));
4215
- outro(red(err instanceof Error ? err.message : String(err)));
4260
+ if (commitResult === "cancelled") process.exit(1);
4261
+ if (stagedAll) {
4262
+ outro(green("Done."));
4216
4263
  return;
4217
4264
  }
4218
- s.stop("Message generated");
4219
- }
4220
- if (flags.single || await getAutoAccept()) {
4221
- debug("Auto-accept ON: skipping review step");
4222
- log.info(message);
4223
- } else {
4224
- const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
4225
- const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
4226
- debug("Regenerating with combined hint:", combinedHint);
4227
- s.start("Regenerating commit message...");
4228
- try {
4229
- const newMessage = await generateMessage(diffResult.diff, combinedHint);
4230
- s.stop("Message regenerated");
4231
- return newMessage;
4232
- } catch (err) {
4233
- s.stop(red("Regeneration failed"));
4234
- throw err;
4235
- }
4236
- } });
4237
- if (reviewed === null) {
4238
- outro(dim("Cancelled."));
4265
+ const remaining = await getChangedFiles();
4266
+ if (remaining.length === 0) {
4267
+ outro(green("Done."));
4239
4268
  return;
4240
4269
  }
4241
- message = reviewed;
4270
+ debug("Uncommitted files remain after commit:", remaining.length);
4271
+ log.info(`${remaining.length} file${remaining.length !== 1 ? "s" : ""} remaining โ€” continuing with the staging menu`);
4272
+ const stagingResult = await handleStaging(remaining, flags);
4273
+ if (!stagingResult) return;
4274
+ changedFiles = stagingResult.changedFiles;
4275
+ stagedAll = stagingResult.stagedAll;
4242
4276
  }
4243
- await saveCachedCommit(repoRoot, message);
4244
- debug("Message cached for repo:", repoRoot);
4245
- s.start("Running pre-commit hooks...");
4246
- const headBefore = await getHead();
4247
- debug("HEAD before commit:", headBefore);
4248
- const result = await commitWithRecovery(message, s, headBefore);
4249
- debug("Commit result:", result);
4250
- if (result === "committed") {
4251
- outro(green("Done."));
4252
- return;
4253
- }
4254
- if (result === "cancelled") process.exit(1);
4255
4277
  }
4256
4278
  //#endregion
4257
4279
  //#region src/cli.ts
@@ -4516,9 +4538,10 @@ function buildConfigDisplay(config) {
4516
4538
  const provider = isValidProvider(config.provider ?? "groq") ? config.provider : "groq";
4517
4539
  const apiKey = config[PROVIDER_ENV_KEYS[provider]];
4518
4540
  const effectiveModel = getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel);
4541
+ const keyLabel = apiKey ?? (PROVIDER_CONFIGS[provider].requiresApiKey === false ? dim("(optional)") : void 0);
4519
4542
  return [
4520
4543
  `Provider: ${bold(formatProviderName(provider))}`,
4521
- `API Key: ${maskKey(apiKey)}`,
4544
+ `API Key: ${maskKey(keyLabel)}`,
4522
4545
  `Model: ${effectiveModel}`,
4523
4546
  `Locale: ${config.locale ?? "en"}`,
4524
4547
  `Max Length: ${config["max-length"] ?? "100"}`,
@@ -4548,20 +4571,29 @@ async function promptProvider() {
4548
4571
  label: "Mistral",
4549
4572
  value: "mistral",
4550
4573
  hint: PROVIDER_CONFIGS.mistral.defaultModel
4574
+ },
4575
+ {
4576
+ label: "OmniRoute",
4577
+ value: "omniroute",
4578
+ hint: `local gateway ยท ${PROVIDER_CONFIGS.omniroute.defaultModel}`
4551
4579
  }
4552
4580
  ]
4553
4581
  });
4554
4582
  }
4555
4583
  async function promptApiKey(provider) {
4556
4584
  const keyName = PROVIDER_ENV_KEYS[provider];
4585
+ const requiresKey = PROVIDER_CONFIGS[provider].requiresApiKey !== false;
4557
4586
  const result = await p$1.text({
4558
- message: `${formatProviderName(provider)} API key:`,
4559
- placeholder: "Paste your API key",
4560
- validate: (v) => !v?.trim() ? "API key cannot be empty" : void 0
4587
+ message: `${formatProviderName(provider)} API key${requiresKey ? "" : " (optional)"}:`,
4588
+ placeholder: requiresKey ? "Paste your API key" : "Leave empty for none",
4589
+ validate: (v) => !v?.trim() && requiresKey ? "API key cannot be empty" : void 0
4561
4590
  });
4562
4591
  if (p$1.isCancel(result)) return result;
4563
- await writeConfig({ [keyName]: result.toString().trim() });
4564
- debug("config: %s set", keyName);
4592
+ const trimmed = result.toString().trim();
4593
+ if (trimmed) {
4594
+ await writeConfig({ [keyName]: trimmed });
4595
+ debug("config: %s set", keyName);
4596
+ }
4565
4597
  return result;
4566
4598
  }
4567
4599
  async function promptTextSetting(label, configKey, currentValue, validate) {
@@ -4594,7 +4626,7 @@ function getSettingHandlers(config) {
4594
4626
  });
4595
4627
  debug("config: provider set to %s, model set to %s", newProvider, newDefaultModel);
4596
4628
  const keyName = PROVIDER_ENV_KEYS[newProvider];
4597
- if (!(await readConfig())[keyName]) {
4629
+ if (!(await readConfig())[keyName] && PROVIDER_CONFIGS[newProvider].requiresApiKey !== false) {
4598
4630
  const keyResult = await promptApiKey(newProvider);
4599
4631
  if (p$1.isCancel(keyResult)) return keyResult;
4600
4632
  }