Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# OpenAI API Key
# Get yours at: https://platform.openai.com/api-keys
OPENAI_API_KEY=sk-your-api-key-here

# Gemini API Key
# Get yours at: https://aistudio.google.com/u/1/api-keys
GEMINI_API_KEY=gemini-api-key
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,17 @@
"LICENSE"
],
"dependencies": {
"openai": "^4.20.0",
"commander": "^11.1.0",
"@google/generative-ai": "^0.24.1",
"chalk": "^5.3.0",
"ora": "^7.0.1",
"node-fetch": "^3.3.2"
"commander": "^11.1.0",
"node-fetch": "^3.3.2",
"openai": "^4.20.0",
"ora": "^7.0.1"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.2",
"tsx": "^4.6.2"
"tsx": "^4.6.2",
"typescript": "^5.3.2"
},
"publishConfig": {
"access": "public"
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 33 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ async function installCommitHooks(): Promise<void> {
console.log(chalk.gray(' npm install -g @anthropic-ai/claude-code'));
console.log(chalk.gray(' claude login'));
console.log(chalk.gray(' git config hooks.commitProvider claude-code'));
console.log(chalk.gray('\n # Option D: Gemini (sends data to Google API)'));
if (isWindows) {
console.log(chalk.gray(' set GEMINI_API_KEY="your-api-key"'));
} else {
console.log(chalk.gray(' export GEMINI_API_KEY="your-api-key"'));
}
console.log(chalk.gray(' git config hooks.commitProvider gemini'));

console.log('\n3. Optional customizations:');
console.log(chalk.gray(' git config hooks.commitTemplate "type(scope): message"'));
Expand All @@ -148,9 +155,9 @@ program
.name('git-rewrite-commits')
.description('AI-powered git commit message rewriter using OpenAI or Ollama')
.version(packageJson.version)
.option('--provider <provider>', 'AI provider to use: "openai", "ollama", or "claude-code"', 'openai')
.option('-k, --api-key <key>', 'OpenAI API key (defaults to OPENAI_API_KEY env var)')
.option('-m, --model <model>', 'AI model to use (default: gpt-3.5-turbo for OpenAI, llama3.2 for Ollama, haiku for Claude Code)')
.option('--provider <provider>', 'AI provider to use: "openai", "ollama", "claude-code", or "gemini"', 'openai')
.option('-k, --api-key <key>', 'API key for the selected provider (defaults to OPENAI_API_KEY or GEMINI_API_KEY env var)')
.option('-m, --model <model>', 'AI model to use (default: gpt-3.5-turbo for OpenAI, llama3.2 for Ollama, haiku for Claude Code, gemini-2.5-flash-lite for Gemini)')
.option('--ollama-url <url>', 'Ollama server URL', 'http://localhost:11434')
.option('-b, --branch <branch>', 'Branch to rewrite (defaults to current branch)')
.option('-d, --dry-run', 'Show what would be changed without modifying repository')
Expand All @@ -176,9 +183,8 @@ program

// Check for API key if using OpenAI
const provider = options.provider || 'openai';
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;

if (provider === 'openai' && !apiKey) {

if (provider === 'openai' && !options.apiKey && !process.env.OPENAI_API_KEY) {
console.error(chalk.red('\n❌ Error: OpenAI API key is required!'));
console.error(chalk.yellow('\nPlease provide it using one of these methods:'));
console.error(chalk.cyan(' 1. Set environment variable: export OPENAI_API_KEY="your-api-key"'));
Expand All @@ -187,6 +193,15 @@ program
console.error(chalk.blue('\n💡 Tip: Use --provider ollama to use local models with Ollama instead'));
process.exit(1);
}

if (provider === 'gemini' && !options.apiKey && !process.env.GEMINI_API_KEY) {
console.error(chalk.red('\n❌ Error: Gemini API key is required!'));
console.error(chalk.yellow('\nPlease provide it using one of these methods:'));
console.error(chalk.cyan(' 1. Set environment variable: export GEMINI_API_KEY="your-api-key"'));
console.error(chalk.cyan(' 2. Pass as argument: git-rewrite-commits --provider gemini --api-key "your-api-key"'));
console.error(chalk.dim('\nGet your API key at: https://aistudio.google.com/app/apikey'));
process.exit(1);
}

// Only show informational messages when NOT in quiet mode
if (provider === 'ollama' && !options.quiet) {
Expand All @@ -199,9 +214,14 @@ program
console.log(chalk.gray(' Make sure Claude Code is installed and authenticated: claude login'));
}

if (provider === 'gemini' && !options.quiet) {
console.log(chalk.blue('ℹ️ Using Gemini provider'));
console.log(chalk.gray(' Make sure GEMINI_API_KEY is set or pass --api-key'));
}

const rewriter = new GitCommitRewriter({
provider: provider,
apiKey,
apiKey: options.apiKey || (provider === 'gemini' ? process.env.GEMINI_API_KEY : process.env.OPENAI_API_KEY),
model: options.model,
ollamaUrl: options.ollamaUrl,
branch: options.branch,
Expand Down Expand Up @@ -280,10 +300,14 @@ ${chalk.bold('Examples:')}
${chalk.gray('# Use Ollama with local models')}
$ git-rewrite-commits --provider ollama --model llama3.2

${chalk.gray('# Use Claude Code CLI (no API key needed, uses Claude subscription)')}
${chalk.gray('# Use Claude Code CLI (no API key needed, uses Claude subscription)')}
$ git-rewrite-commits --provider claude-code
$ git-rewrite-commits --provider claude-code --model opus

${chalk.gray('# Use Gemini (gemini-2.5-flash-lite by default)')}
$ git-rewrite-commits --provider gemini
$ git-rewrite-commits --provider gemini --model gemini-2.5-flash-lite --api-key "your-key"

${chalk.gray('# Custom prompt for specific requirements')}
$ git-rewrite-commits --prompt "generate humorous but professional messages"

Expand All @@ -292,6 +316,7 @@ ${chalk.bold('Examples:')}

${chalk.bold('Environment Variables:')}
OPENAI_API_KEY Your OpenAI API key (required when using OpenAI provider)
GEMINI_API_KEY Your Gemini API key (required when using Gemini provider)

${chalk.bold('Important Notes:')}
${chalk.yellow('⚠️ This tool rewrites git history!')}
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ora from 'ora';
import { AIProvider, createProvider } from './providers/index.js';

export interface RewriteOptions {
provider?: 'openai' | 'ollama' | 'claude-code';
provider?: 'openai' | 'ollama' | 'claude-code' | 'gemini';
apiKey?: string;
model?: string;
ollamaUrl?: string;
Expand Down Expand Up @@ -41,6 +41,7 @@ export class GitCommitRewriter {
const model = options.model || (
provider === 'ollama' ? 'llama3.2' :
provider === 'claude-code' ? 'haiku' :
provider === 'gemini' ? 'gemini-2.5-flash-lite' :
'gpt-3.5-turbo'
);

Expand Down Expand Up @@ -74,7 +75,7 @@ export class GitCommitRewriter {

private execCommand(command: string): string {
try {
return execSync(command, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
return execSync(command, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
} catch (error: any) {
throw new Error(`Command failed: ${command}\n${error.message}`);
}
Expand Down
39 changes: 39 additions & 0 deletions src/providers/gemini.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { GoogleGenerativeAI } from '@google/generative-ai';
import { AIProvider } from './types.js';

export class GeminiProvider implements AIProvider {
private genAI: GoogleGenerativeAI;
private model: string;

constructor(apiKey: string, model: string = 'gemini-2.5-flash-lite') {
if (!apiKey) {
throw new Error('Gemini API key is required');
}
this.genAI = new GoogleGenerativeAI(apiKey);
this.model = model;
}

async generateCommitMessage(prompt: string, systemPrompt: string): Promise<string> {
const generativeModel = this.genAI.getGenerativeModel({
model: this.model,
systemInstruction: systemPrompt,
generationConfig: {
temperature: 0.3,
maxOutputTokens: 200,
},
});

const result = await generativeModel.generateContent(prompt);
const message = result.response.text().trim();

if (!message) {
throw new Error('No commit message generated');
}

return message;
}

getName(): string {
return `Gemini (${this.model})`;
}
}
8 changes: 8 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { AIProvider, ProviderOptions } from './types.js';
import { OpenAIProvider } from './openai.js';
import { OllamaProvider } from './ollama.js';
import { ClaudeCodeProvider } from './claude-code.js';
import { GeminiProvider } from './gemini.js';

export { AIProvider } from './types.js';
export { OpenAIProvider } from './openai.js';
export { OllamaProvider } from './ollama.js';
export { ClaudeCodeProvider } from './claude-code.js';
export { GeminiProvider } from './gemini.js';

export function createProvider(options: ProviderOptions): AIProvider {
const providerType = options.provider || 'openai';
Expand All @@ -15,6 +17,12 @@ export function createProvider(options: ProviderOptions): AIProvider {
return new OllamaProvider(options.model || 'llama3.2', options.ollamaUrl);
} else if (providerType === 'claude-code') {
return new ClaudeCodeProvider(options.model || 'haiku');
} else if (providerType === 'gemini') {
const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error('Gemini API key is required. Set GEMINI_API_KEY environment variable or pass it as an option.');
}
return new GeminiProvider(apiKey, options.model || 'gemini-2.5-flash-lite');
} else {
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
if (!apiKey) {
Expand Down
2 changes: 1 addition & 1 deletion src/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface AIProvider {
}

export interface ProviderOptions {
provider?: 'openai' | 'ollama' | 'claude-code';
provider?: 'openai' | 'ollama' | 'claude-code' | 'gemini';
apiKey?: string;
model?: string;
ollamaUrl?: string;
Expand Down