commit-agent-cli 0.1.6 → 0.2.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/LICENSE +1 -1
- package/README.md +7 -10
- package/dist/index.js +308 -79
- package/package.json +3 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# commit-agent-cli
|
|
2
2
|
|
|
3
|
-
> AI-powered git commit message generator using Claude 4.5 and LangGraph
|
|
3
|
+
> AI-powered git commit message generator using Claude Sonnet 4.5 / Opus 4.5 or Google Gemini and LangGraph
|
|
4
4
|
|
|
5
5
|
Generate intelligent, context-aware commit messages by simply typing `commit`.
|
|
6
6
|
|
|
@@ -20,17 +20,14 @@ commit
|
|
|
20
20
|
## First-Time Setup
|
|
21
21
|
|
|
22
22
|
You'll be prompted to:
|
|
23
|
-
1.
|
|
24
|
-
2. Choose
|
|
23
|
+
1. Select your AI Provider (Anthropic Claude or Google Gemini)
|
|
24
|
+
2. Choose your preferred model
|
|
25
|
+
3. Enter your API Key ([Anthropic Console](https://console.anthropic.com) or [Google AI Studio](https://aistudio.google.com/app/apikey))
|
|
26
|
+
4. Choose commit message preferences (conventional commits, verbosity)
|
|
25
27
|
|
|
26
28
|
## Features
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
- 🕵️ Autonomous codebase exploration
|
|
30
|
-
- 👁️ Transparent agent actions
|
|
31
|
-
- ⚙️ Customizable commit styles
|
|
32
|
-
- 🔒 Secure local storage
|
|
33
|
-
- 🔔 Auto update notifications
|
|
30
|
+
Powered by Claude Sonnet 4.5 / Opus 4.5 or Google Gemini, this tool autonomously explores your codebase to generate intelligent commit messages with full transparency into its reasoning process. Supports customizable commit styles with secure local configuration storage and the ability to switch between AI providers at any time.
|
|
34
31
|
|
|
35
32
|
## Documentation
|
|
36
33
|
|
|
@@ -39,7 +36,7 @@ You'll be prompted to:
|
|
|
39
36
|
## Requirements
|
|
40
37
|
|
|
41
38
|
- Node.js 18+
|
|
42
|
-
- Anthropic API key
|
|
39
|
+
- Anthropic API key OR Google AI API key
|
|
43
40
|
|
|
44
41
|
## License
|
|
45
42
|
|
package/dist/index.js
CHANGED
|
@@ -157,6 +157,7 @@ async function isGitRepository() {
|
|
|
157
157
|
|
|
158
158
|
// src/agent.ts
|
|
159
159
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
160
|
+
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
160
161
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
161
162
|
import { StateGraph, MessagesAnnotation } from "@langchain/langgraph";
|
|
162
163
|
import { ToolNode } from "@langchain/langgraph/prebuilt";
|
|
@@ -323,15 +324,29 @@ var tools = [
|
|
|
323
324
|
|
|
324
325
|
// src/agent.ts
|
|
325
326
|
var app = null;
|
|
326
|
-
|
|
327
|
+
var currentConfig = null;
|
|
328
|
+
function getApp(config) {
|
|
329
|
+
if (app && currentConfig && (currentConfig.provider !== config.provider || currentConfig.model !== config.model)) {
|
|
330
|
+
app = null;
|
|
331
|
+
}
|
|
327
332
|
if (app) return app;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
currentConfig = config;
|
|
334
|
+
let model;
|
|
335
|
+
if (config.provider === "anthropic") {
|
|
336
|
+
model = new ChatAnthropic({
|
|
337
|
+
model: config.model,
|
|
338
|
+
temperature: 0,
|
|
339
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
340
|
+
}).bindTools(tools);
|
|
341
|
+
} else {
|
|
342
|
+
model = new ChatGoogleGenerativeAI({
|
|
343
|
+
model: config.model,
|
|
344
|
+
temperature: 0,
|
|
345
|
+
apiKey: process.env.GOOGLE_API_KEY,
|
|
346
|
+
thinkingBudget: 0
|
|
347
|
+
// Disable thinking to prevent quota issues
|
|
348
|
+
}).bindTools(tools);
|
|
349
|
+
}
|
|
335
350
|
const toolNode = new ToolNode(tools);
|
|
336
351
|
async function agent(state) {
|
|
337
352
|
const { messages } = state;
|
|
@@ -349,9 +364,13 @@ function getApp() {
|
|
|
349
364
|
app = workflow.compile();
|
|
350
365
|
return app;
|
|
351
366
|
}
|
|
352
|
-
async function generateCommitMessage(diff, preferences, userFeedback) {
|
|
353
|
-
const conventionalGuide = preferences.useConventionalCommits ? "Use conventional commit format with prefixes like feat:, fix:, chore:, docs:, style:, refactor:, test:, etc." : "Do NOT use conventional commit prefixes. Write natural commit messages.";
|
|
367
|
+
async function generateCommitMessage(diff, preferences, config, userFeedback) {
|
|
368
|
+
const conventionalGuide = preferences.useConventionalCommits ? "Use conventional commit format with prefixes like feat:, fix:, chore:, docs:, style:, refactor:, test:, etc." : "Do NOT use conventional commit prefixes (no feat:, fix:, etc.). Write natural, plain commit messages without any prefixes.";
|
|
354
369
|
const styleGuide = preferences.commitMessageStyle === "descriptive" ? 'Be descriptive and detailed. Explain the "why" behind changes when relevant. Multi-line messages are encouraged.' : "Be concise and to the point. Keep it short, ideally one line.";
|
|
370
|
+
const customGuideSection = preferences.customGuideline ? `
|
|
371
|
+
|
|
372
|
+
CUSTOM GUIDELINES (MUST FOLLOW):
|
|
373
|
+
${preferences.customGuideline}` : "";
|
|
355
374
|
const systemPrompt = `You are an expert developer. Your task is to generate a commit message for the provided git diff.
|
|
356
375
|
|
|
357
376
|
The diff provided is OPTIMIZED for token efficiency - it includes file changes, stats, and minimal context.
|
|
@@ -376,9 +395,10 @@ Commit Message Rules:
|
|
|
376
395
|
3. Focus on WHAT changed (clear from diff) and WHY if obvious from context
|
|
377
396
|
4. OUTPUT FORMAT: Your response must be ONLY the commit message. No explanations.
|
|
378
397
|
5. Do NOT use markdown code blocks or formatting
|
|
379
|
-
6. If multi-line, use proper git commit format (subject line, blank line, body)
|
|
398
|
+
6. If multi-line, use proper git commit format (subject line, blank line, body)${customGuideSection}
|
|
380
399
|
|
|
381
400
|
CRITICAL: Your ENTIRE response should be the commit message itself, nothing else.
|
|
401
|
+
IMPORTANT: Follow the conventional commit rule strictly - if told NOT to use prefixes, do NOT use them at all.
|
|
382
402
|
|
|
383
403
|
DEFAULT ACTION: Read the diff, generate the message, done. NO TOOLS.
|
|
384
404
|
`;
|
|
@@ -391,7 +411,7 @@ ${diff}${userFeedback ? `
|
|
|
391
411
|
User feedback on previous attempt: ${userFeedback}
|
|
392
412
|
Please adjust the commit message based on this feedback.` : ""}`)
|
|
393
413
|
];
|
|
394
|
-
const graph = getApp();
|
|
414
|
+
const graph = getApp(config);
|
|
395
415
|
const result = await graph.invoke({ messages });
|
|
396
416
|
const lastMsg = result.messages[result.messages.length - 1];
|
|
397
417
|
let content = lastMsg.content;
|
|
@@ -438,91 +458,264 @@ var notifier = updateNotifier({
|
|
|
438
458
|
updateCheckInterval: 1e3 * 60 * 60
|
|
439
459
|
// Check once per hour (was 24h)
|
|
440
460
|
});
|
|
441
|
-
if (notifier.update) {
|
|
461
|
+
if (notifier.update && notifier.update.current !== notifier.update.latest) {
|
|
442
462
|
const currentVersion = notifier.update.current;
|
|
443
463
|
const latestVersion = notifier.update.latest;
|
|
464
|
+
const boxWidth = 67;
|
|
465
|
+
const padLine = (content, width) => {
|
|
466
|
+
const visibleLength = content.replace(/\u001b\[[0-9;]*m/g, "").length;
|
|
467
|
+
const padding = width - visibleLength;
|
|
468
|
+
return content + " ".repeat(Math.max(0, padding));
|
|
469
|
+
};
|
|
444
470
|
console.log("");
|
|
445
471
|
console.log(import_picocolors.default.yellow("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
446
|
-
console.log(import_picocolors.default.yellow("\u2502") + "
|
|
447
|
-
console.log(import_picocolors.default.yellow("\u2502") + " " + import_picocolors.default.bold("New version of commit-cli is available!")
|
|
448
|
-
console.log(import_picocolors.default.yellow("\u2502") + "
|
|
449
|
-
console.log(import_picocolors.default.yellow("\u2502") + " Current: " + import_picocolors.default.dim(currentVersion) + " \u2192 Latest: " + import_picocolors.default.green(import_picocolors.default.bold(latestVersion))
|
|
450
|
-
console.log(import_picocolors.default.yellow("\u2502") + "
|
|
451
|
-
console.log(import_picocolors.default.yellow("\u2502") + " " + import_picocolors.default.dim("Update after you finish by running:")
|
|
452
|
-
console.log(import_picocolors.default.yellow("\u2502") + " " + import_picocolors.default.cyan(import_picocolors.default.bold(`npm install -g ${packageJson.name}@latest`))
|
|
453
|
-
console.log(import_picocolors.default.yellow("\u2502") + "
|
|
472
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine("", boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
473
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine(" " + import_picocolors.default.bold("New version of commit-cli is available!"), boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
474
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine("", boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
475
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine(" Current: " + import_picocolors.default.dim(currentVersion) + " \u2192 Latest: " + import_picocolors.default.green(import_picocolors.default.bold(latestVersion)), boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
476
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine("", boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
477
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine(" " + import_picocolors.default.dim("Update after you finish by running:"), boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
478
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine(" " + import_picocolors.default.cyan(import_picocolors.default.bold(`npm install -g ${packageJson.name}@latest`)), boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
479
|
+
console.log(import_picocolors.default.yellow("\u2502") + padLine("", boxWidth) + import_picocolors.default.yellow("\u2502"));
|
|
454
480
|
console.log(import_picocolors.default.yellow("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
455
481
|
console.log("");
|
|
456
482
|
}
|
|
457
483
|
notifier.notify({ defer: false, isGlobal: true });
|
|
458
484
|
var CONFIG_PATH = join2(homedir(), ".commit-cli.json");
|
|
459
|
-
|
|
485
|
+
var ANTHROPIC_MODELS = {
|
|
486
|
+
"claude-sonnet-4-20250514": "Claude Sonnet 4.5",
|
|
487
|
+
"claude-opus-4-20250514": "Claude Opus 4.5"
|
|
488
|
+
};
|
|
489
|
+
var GOOGLE_MODELS = {
|
|
490
|
+
"gemini-3-flash-preview": "Gemini 3.0 Flash Preview",
|
|
491
|
+
"gemini-3-pro-preview": "Gemini 3.0 Pro Preview"
|
|
492
|
+
};
|
|
493
|
+
async function getStoredConfig() {
|
|
460
494
|
try {
|
|
461
495
|
const data = await readFile2(CONFIG_PATH, "utf-8");
|
|
462
|
-
return JSON.parse(data)
|
|
496
|
+
return JSON.parse(data);
|
|
463
497
|
} catch {
|
|
464
498
|
return null;
|
|
465
499
|
}
|
|
466
500
|
}
|
|
467
|
-
async function
|
|
501
|
+
async function storeConfig(config) {
|
|
468
502
|
try {
|
|
469
|
-
await writeFile(CONFIG_PATH, JSON.stringify(
|
|
503
|
+
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
470
504
|
} catch (err) {
|
|
471
505
|
}
|
|
472
506
|
}
|
|
473
|
-
async function
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
507
|
+
async function setupProviderAndModel() {
|
|
508
|
+
const providerChoice = await select({
|
|
509
|
+
message: "Select AI Provider:",
|
|
510
|
+
options: [
|
|
511
|
+
{ value: "anthropic", label: "Anthropic (Claude)" },
|
|
512
|
+
{ value: "google", label: "Google (Gemini)" }
|
|
513
|
+
],
|
|
514
|
+
initialValue: "anthropic"
|
|
515
|
+
});
|
|
516
|
+
if (isCancel(providerChoice)) {
|
|
517
|
+
cancel("Operation cancelled.");
|
|
518
|
+
process.exit(0);
|
|
480
519
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
520
|
+
const provider = providerChoice;
|
|
521
|
+
let model;
|
|
522
|
+
if (provider === "anthropic") {
|
|
523
|
+
const modelChoice = await select({
|
|
524
|
+
message: "Select Anthropic Model:",
|
|
525
|
+
options: [
|
|
526
|
+
{ value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4.5 (Recommended)" },
|
|
527
|
+
{ value: "claude-opus-4-20250514", label: "Claude Opus 4.5 (Most Capable)" }
|
|
528
|
+
],
|
|
529
|
+
initialValue: "claude-sonnet-4-20250514"
|
|
530
|
+
});
|
|
531
|
+
if (isCancel(modelChoice)) {
|
|
532
|
+
cancel("Operation cancelled.");
|
|
533
|
+
process.exit(0);
|
|
489
534
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
535
|
+
model = modelChoice;
|
|
536
|
+
} else {
|
|
537
|
+
const modelChoice = await select({
|
|
538
|
+
message: "Select Google Model:",
|
|
539
|
+
options: [
|
|
540
|
+
{ value: "gemini-3-flash-preview", label: "Gemini 3.0 Flash Preview (Fast)" },
|
|
541
|
+
{ value: "gemini-3-pro-preview", label: "Gemini 3.0 Pro Preview (Most Capable)" }
|
|
542
|
+
],
|
|
543
|
+
initialValue: "gemini-3-flash-preview"
|
|
544
|
+
});
|
|
545
|
+
if (isCancel(modelChoice)) {
|
|
546
|
+
cancel("Operation cancelled.");
|
|
547
|
+
process.exit(0);
|
|
548
|
+
}
|
|
549
|
+
model = modelChoice;
|
|
493
550
|
}
|
|
551
|
+
return { provider, model };
|
|
494
552
|
}
|
|
495
|
-
async function
|
|
496
|
-
|
|
497
|
-
let apiKey = process.env.ANTHROPIC_API_KEY;
|
|
498
|
-
if (!apiKey) {
|
|
499
|
-
apiKey = await getStoredKey() || void 0;
|
|
500
|
-
}
|
|
501
|
-
if (!apiKey) {
|
|
553
|
+
async function promptForApiKey(provider) {
|
|
554
|
+
if (provider === "anthropic") {
|
|
502
555
|
const key = await text({
|
|
503
|
-
message: "Enter your Anthropic API Key (sk-...):",
|
|
556
|
+
message: "Enter your Anthropic API Key (sk-ant-...):",
|
|
504
557
|
placeholder: "sk-ant-api...",
|
|
505
558
|
validate: (value) => {
|
|
506
559
|
if (!value) return "API Key is required";
|
|
507
|
-
if (!value.startsWith("sk-")) return "Invalid API Key format (should start with sk-)";
|
|
560
|
+
if (!value.startsWith("sk-ant-")) return "Invalid API Key format (should start with sk-ant-)";
|
|
508
561
|
}
|
|
509
562
|
});
|
|
510
563
|
if (isCancel(key)) {
|
|
511
564
|
cancel("Operation cancelled.");
|
|
512
565
|
process.exit(0);
|
|
513
566
|
}
|
|
514
|
-
|
|
515
|
-
|
|
567
|
+
return key;
|
|
568
|
+
} else {
|
|
569
|
+
const key = await text({
|
|
570
|
+
message: "Enter your Google AI API Key:",
|
|
571
|
+
placeholder: "AIza...",
|
|
572
|
+
validate: (value) => {
|
|
573
|
+
if (!value) return "API Key is required";
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
if (isCancel(key)) {
|
|
577
|
+
cancel("Operation cancelled.");
|
|
578
|
+
process.exit(0);
|
|
579
|
+
}
|
|
580
|
+
return key;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function showSettingsMenu(currentConfig2) {
|
|
584
|
+
const providerLabel = currentConfig2.provider === "anthropic" ? "Anthropic (Claude)" : "Google (Gemini)";
|
|
585
|
+
const modelName = currentConfig2.provider === "anthropic" ? ANTHROPIC_MODELS[currentConfig2.model] || currentConfig2.model : GOOGLE_MODELS[currentConfig2.model] || currentConfig2.model;
|
|
586
|
+
note(`Current: ${providerLabel} - ${modelName}`, "Current Configuration");
|
|
587
|
+
const settingChoice = await select({
|
|
588
|
+
message: "What would you like to change?",
|
|
589
|
+
options: [
|
|
590
|
+
{ value: "provider", label: "Change AI Provider & Model" },
|
|
591
|
+
{ value: "apikey", label: "Update API Key" },
|
|
592
|
+
{ value: "preferences", label: "Change Commit Preferences" },
|
|
593
|
+
{ value: "cancel", label: "Cancel" }
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
if (isCancel(settingChoice) || settingChoice === "cancel") {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const newConfig = { ...currentConfig2 };
|
|
600
|
+
if (settingChoice === "provider") {
|
|
601
|
+
const modelConfig = await setupProviderAndModel();
|
|
602
|
+
newConfig.provider = modelConfig.provider;
|
|
603
|
+
newConfig.model = modelConfig.model;
|
|
604
|
+
const keyField = modelConfig.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
605
|
+
if (!newConfig[keyField]) {
|
|
606
|
+
const apiKey = await promptForApiKey(modelConfig.provider);
|
|
607
|
+
newConfig[keyField] = apiKey;
|
|
608
|
+
}
|
|
609
|
+
await storeConfig(newConfig);
|
|
610
|
+
return newConfig;
|
|
611
|
+
} else if (settingChoice === "apikey") {
|
|
612
|
+
const apiKey = await promptForApiKey(currentConfig2.provider);
|
|
613
|
+
const keyField = currentConfig2.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
614
|
+
newConfig[keyField] = apiKey;
|
|
615
|
+
await storeConfig(newConfig);
|
|
616
|
+
return newConfig;
|
|
617
|
+
} else if (settingChoice === "preferences") {
|
|
618
|
+
const useConventional = await select({
|
|
619
|
+
message: "Use conventional commit prefixes (feat:, fix:, chore:, etc.)?",
|
|
620
|
+
options: [
|
|
621
|
+
{ value: true, label: "Yes" },
|
|
622
|
+
{ value: false, label: "No" }
|
|
623
|
+
],
|
|
624
|
+
initialValue: currentConfig2.preferences?.useConventionalCommits ?? true
|
|
625
|
+
});
|
|
626
|
+
if (isCancel(useConventional)) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
const styleChoice = await select({
|
|
630
|
+
message: "Prefer descriptive commit messages?",
|
|
631
|
+
options: [
|
|
632
|
+
{ value: true, label: "Descriptive (detailed explanations)" },
|
|
633
|
+
{ value: false, label: "Concise (short and to the point)" }
|
|
634
|
+
],
|
|
635
|
+
initialValue: currentConfig2.preferences?.commitMessageStyle === "descriptive"
|
|
636
|
+
});
|
|
637
|
+
if (isCancel(styleChoice)) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const addCustomGuideline = await select({
|
|
641
|
+
message: "Add custom commit message guideline?",
|
|
642
|
+
options: [
|
|
643
|
+
{ value: true, label: "Yes, add custom guideline" },
|
|
644
|
+
{ value: false, label: "No, use defaults" }
|
|
645
|
+
],
|
|
646
|
+
initialValue: false
|
|
647
|
+
});
|
|
648
|
+
if (isCancel(addCustomGuideline)) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
let customGuideline = currentConfig2.preferences?.customGuideline;
|
|
652
|
+
if (addCustomGuideline) {
|
|
653
|
+
const guidelineInput = await text({
|
|
654
|
+
message: "Enter your custom commit message guideline:",
|
|
655
|
+
placeholder: "e.g., Always mention ticket number, use imperative mood...",
|
|
656
|
+
initialValue: currentConfig2.preferences?.customGuideline || ""
|
|
657
|
+
});
|
|
658
|
+
if (isCancel(guidelineInput)) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
customGuideline = guidelineInput.trim() || void 0;
|
|
662
|
+
} else {
|
|
663
|
+
customGuideline = void 0;
|
|
664
|
+
}
|
|
665
|
+
newConfig.preferences = {
|
|
666
|
+
useConventionalCommits: useConventional,
|
|
667
|
+
commitMessageStyle: styleChoice ? "descriptive" : "concise",
|
|
668
|
+
customGuideline
|
|
669
|
+
};
|
|
670
|
+
await storeConfig(newConfig);
|
|
671
|
+
return newConfig;
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
async function main() {
|
|
676
|
+
intro(import_picocolors.default.bgBlue(import_picocolors.default.white(" commit-cli ")));
|
|
677
|
+
let config = await getStoredConfig();
|
|
678
|
+
if (config && config.ANTHROPIC_API_KEY && !config.provider) {
|
|
679
|
+
note("Upgrading your configuration to support multiple AI providers...", "Configuration Update");
|
|
680
|
+
config.provider = "anthropic";
|
|
681
|
+
config.model = "claude-sonnet-4-20250514";
|
|
682
|
+
await storeConfig(config);
|
|
683
|
+
note("Configuration upgraded! You can now switch providers anytime using the settings menu.", "Upgrade Complete");
|
|
684
|
+
}
|
|
685
|
+
if (!config || !config.provider || !config.model) {
|
|
686
|
+
note("Welcome! Let's set up your AI provider and preferences", "First Time Setup");
|
|
687
|
+
const modelConfig = await setupProviderAndModel();
|
|
688
|
+
const apiKey2 = await promptForApiKey(modelConfig.provider);
|
|
689
|
+
config = {
|
|
690
|
+
provider: modelConfig.provider,
|
|
691
|
+
model: modelConfig.model,
|
|
692
|
+
...modelConfig.provider === "anthropic" ? { ANTHROPIC_API_KEY: apiKey2 } : { GOOGLE_API_KEY: apiKey2 }
|
|
693
|
+
};
|
|
694
|
+
await storeConfig(config);
|
|
695
|
+
}
|
|
696
|
+
const apiKeyField = config.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
697
|
+
const envKeyField = config.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
698
|
+
let apiKey = process.env[envKeyField];
|
|
699
|
+
if (!apiKey) {
|
|
700
|
+
apiKey = config[apiKeyField] || void 0;
|
|
701
|
+
}
|
|
702
|
+
if (!apiKey) {
|
|
703
|
+
apiKey = await promptForApiKey(config.provider);
|
|
704
|
+
config[apiKeyField] = apiKey;
|
|
705
|
+
await storeConfig(config);
|
|
706
|
+
}
|
|
707
|
+
if (config.provider === "anthropic") {
|
|
708
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
709
|
+
} else {
|
|
710
|
+
process.env.GOOGLE_API_KEY = apiKey;
|
|
516
711
|
}
|
|
517
|
-
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
518
712
|
const isRepo = await isGitRepository();
|
|
519
713
|
if (!isRepo) {
|
|
520
714
|
cancel("Current directory is not a git repository.");
|
|
521
715
|
process.exit(1);
|
|
522
716
|
}
|
|
523
|
-
let preferences =
|
|
717
|
+
let preferences = config.preferences;
|
|
524
718
|
if (!preferences) {
|
|
525
|
-
note("Let's set up your commit message preferences (one-time setup)", "First Time Setup");
|
|
526
719
|
const useConventional = await select({
|
|
527
720
|
message: "Use conventional commit prefixes (feat:, fix:, chore:, etc.)?",
|
|
528
721
|
options: [
|
|
@@ -547,12 +740,38 @@ async function main() {
|
|
|
547
740
|
cancel("Operation cancelled.");
|
|
548
741
|
process.exit(0);
|
|
549
742
|
}
|
|
743
|
+
const addCustomGuideline = await select({
|
|
744
|
+
message: "Add custom commit message guideline? (optional)",
|
|
745
|
+
options: [
|
|
746
|
+
{ value: false, label: "No, use defaults" },
|
|
747
|
+
{ value: true, label: "Yes, add custom guideline" }
|
|
748
|
+
],
|
|
749
|
+
initialValue: false
|
|
750
|
+
});
|
|
751
|
+
if (isCancel(addCustomGuideline)) {
|
|
752
|
+
cancel("Operation cancelled.");
|
|
753
|
+
process.exit(0);
|
|
754
|
+
}
|
|
755
|
+
let customGuideline;
|
|
756
|
+
if (addCustomGuideline) {
|
|
757
|
+
const guidelineInput = await text({
|
|
758
|
+
message: "Enter your custom commit message guideline:",
|
|
759
|
+
placeholder: "e.g., Always mention ticket number, use imperative mood..."
|
|
760
|
+
});
|
|
761
|
+
if (isCancel(guidelineInput)) {
|
|
762
|
+
cancel("Operation cancelled.");
|
|
763
|
+
process.exit(0);
|
|
764
|
+
}
|
|
765
|
+
customGuideline = guidelineInput.trim() || void 0;
|
|
766
|
+
}
|
|
550
767
|
preferences = {
|
|
551
768
|
useConventionalCommits: useConventional,
|
|
552
|
-
commitMessageStyle: styleChoice ? "descriptive" : "concise"
|
|
769
|
+
commitMessageStyle: styleChoice ? "descriptive" : "concise",
|
|
770
|
+
customGuideline
|
|
553
771
|
};
|
|
554
|
-
|
|
555
|
-
|
|
772
|
+
config.preferences = preferences;
|
|
773
|
+
await storeConfig(config);
|
|
774
|
+
note(`Preferences saved! You can change these anytime in the settings menu.`, "Setup Complete");
|
|
556
775
|
}
|
|
557
776
|
const s = spinner();
|
|
558
777
|
s.start("Analyzing staged changes...");
|
|
@@ -569,11 +788,11 @@ async function main() {
|
|
|
569
788
|
while (!confirmed) {
|
|
570
789
|
s.start("Generating commit message (Agent is exploring)...");
|
|
571
790
|
try {
|
|
572
|
-
commitMessage = await generateCommitMessage(diff, preferences, userFeedback);
|
|
791
|
+
commitMessage = await generateCommitMessage(diff, preferences, config, userFeedback);
|
|
573
792
|
userFeedback = void 0;
|
|
574
793
|
} catch (error) {
|
|
575
794
|
s.stop("Generation failed.");
|
|
576
|
-
if (error.message?.includes("401") || error.message?.includes("authentication_error") || error.message?.includes("invalid x-api-key") || error.message?.includes("invalid api key")) {
|
|
795
|
+
if (error.message?.includes("401") || error.message?.includes("authentication_error") || error.message?.includes("invalid x-api-key") || error.message?.includes("invalid api key") || error.message?.includes("API key not valid")) {
|
|
577
796
|
cancel("Invalid API Key detected.");
|
|
578
797
|
const retryWithNewKey = await select({
|
|
579
798
|
message: "Would you like to enter a new API key?",
|
|
@@ -587,21 +806,16 @@ async function main() {
|
|
|
587
806
|
cancel("Operation cancelled.");
|
|
588
807
|
process.exit(0);
|
|
589
808
|
}
|
|
590
|
-
const newKey = await
|
|
591
|
-
message: "Enter your Anthropic API Key (sk-...):",
|
|
592
|
-
placeholder: "sk-ant-api...",
|
|
593
|
-
validate: (value) => {
|
|
594
|
-
if (!value) return "API Key is required";
|
|
595
|
-
if (!value.startsWith("sk-")) return "Invalid API Key format (should start with sk-)";
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
if (isCancel(newKey)) {
|
|
599
|
-
cancel("Operation cancelled.");
|
|
600
|
-
process.exit(0);
|
|
601
|
-
}
|
|
809
|
+
const newKey = await promptForApiKey(config.provider);
|
|
602
810
|
apiKey = newKey;
|
|
603
|
-
|
|
604
|
-
|
|
811
|
+
const keyField = config.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
812
|
+
config[keyField] = apiKey;
|
|
813
|
+
await storeConfig(config);
|
|
814
|
+
if (config.provider === "anthropic") {
|
|
815
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
816
|
+
} else {
|
|
817
|
+
process.env.GOOGLE_API_KEY = apiKey;
|
|
818
|
+
}
|
|
605
819
|
note("API Key updated. Retrying...", "Key Updated");
|
|
606
820
|
continue;
|
|
607
821
|
}
|
|
@@ -645,16 +859,31 @@ async function main() {
|
|
|
645
859
|
const action = await select({
|
|
646
860
|
message: "Do you want to use this message?",
|
|
647
861
|
options: [
|
|
648
|
-
{ value:
|
|
649
|
-
{ value:
|
|
862
|
+
{ value: "commit", label: "Yes, commit" },
|
|
863
|
+
{ value: "regenerate", label: "No, regenerate" },
|
|
864
|
+
{ value: "settings", label: "Change settings" }
|
|
650
865
|
]
|
|
651
866
|
});
|
|
652
867
|
if (isCancel(action)) {
|
|
653
868
|
cancel("Operation cancelled.");
|
|
654
869
|
process.exit(0);
|
|
655
870
|
}
|
|
656
|
-
if (action) {
|
|
871
|
+
if (action === "commit") {
|
|
657
872
|
confirmed = true;
|
|
873
|
+
} else if (action === "settings") {
|
|
874
|
+
const settingsResult = await showSettingsMenu(config);
|
|
875
|
+
if (settingsResult) {
|
|
876
|
+
config = settingsResult;
|
|
877
|
+
preferences = config.preferences || preferences;
|
|
878
|
+
if (config.provider === "anthropic") {
|
|
879
|
+
process.env.ANTHROPIC_API_KEY = config.ANTHROPIC_API_KEY || "";
|
|
880
|
+
delete process.env.GOOGLE_API_KEY;
|
|
881
|
+
} else {
|
|
882
|
+
process.env.GOOGLE_API_KEY = config.GOOGLE_API_KEY || "";
|
|
883
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
884
|
+
}
|
|
885
|
+
note("Settings updated! Regenerating with new configuration...", "Updated");
|
|
886
|
+
}
|
|
658
887
|
} else {
|
|
659
888
|
const nextStep = await select({
|
|
660
889
|
message: "Try again?",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commit-agent-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI-powered git commit CLI using LangGraph and Claude 4.5",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AI-powered git commit CLI using LangGraph and Claude 4.5 or Gemini",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"@clack/prompts": "^0.7.0",
|
|
39
39
|
"@langchain/anthropic": "^0.3.0",
|
|
40
40
|
"@langchain/core": "^0.3.0",
|
|
41
|
+
"@langchain/google-genai": "^0.1.2",
|
|
41
42
|
"@langchain/langgraph": "^0.2.0",
|
|
42
43
|
"ai": "^3.3.33",
|
|
43
44
|
"commit-agent-cli": "^0.1.0",
|