@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 +135 -103
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
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
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
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
|
|
4204
|
-
debug("
|
|
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
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
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
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
4564
|
-
|
|
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
|
}
|