ai-cmg
v0.1.5
Published
AI Commit Message Generator
Readme
ai-cmg
An AI-powered CLI that analyzes staged changes and generates a commit message. You can commit immediately, edit in your editor, or copy the message.
This tool is built on Cloudflare Workers and Workers AI, allowing you to deploy your own Worker instance for cost control and customization.
Installation
npm install -g ai-cmgUsage
ai-cmgFlow:
- If there are staged changes, it generates a message right away.
- If nothing is staged, it offers
git add .or a file picker to stage selected files. - You can choose to commit, edit, or copy the generated message.
Provide a Hint or Prompt
Use -m for a short hint (commit message guidance), and -p for a prompt (instruction).
ai-cmg -m "Improve login error handling"
ai-cmg -p "Use a short title and 3 bullet points"Configuration
The Worker URL defaults to the built-in value but can be overridden.
ai-cmg config --show
ai-cmg config --set https://your-worker.example.com
ai-cmg config --resetInteractive configuration is also available.
ai-cmg configWorker Setup
This CLI requires a Cloudflare Worker that handles AI requests. You can deploy your own Worker to your Cloudflare account for cost control and customization.
Note on Authentication: The Worker includes authentication to prevent unauthorized access and unexpected billing from untrusted users. If you don't need authentication (e.g., for private/internal use only), you can remove the authentication block as indicated in the code comments.
Deploy the Worker
- Create a new Worker in your Cloudflare dashboard
- Set the
SECRET_KEYenvironment variable (secret) in your Worker settings. This key will be used to authenticate requests and prevent unauthorized usage. - Deploy the following code:
export default {
async fetch(request, env) {
// 1. Method check
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
// 2. 🔒 Authentication (X-AUTH-TOKEN check)
// This prevents unauthorized access and unexpected billing from untrusted users.
// If you don't need authentication (e.g., for private/internal use only),
// comment out or remove this entire block.
const EXPECTED_KEY = env.SECRET_KEY;
const authHeader = request.headers.get('X-AUTH-TOKEN');
if (!authHeader || authHeader !== EXPECTED_KEY) {
return new Response(JSON.stringify({ error: 'Unauthorized: Invalid authentication token.' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
try {
// 3. Parse request data (diff, hint, prompt)
let requestData;
try {
requestData = await request.json();
} catch (parseError) {
console.error('JSON parse error:', parseError);
return new Response(
JSON.stringify({
error: 'Invalid request format',
details: 'Failed to parse request body as JSON.'
}),
{
status: 400,
headers: { 'Content-Type': 'application/json' }
}
);
}
const { diff, hint, prompt } = requestData;
if (!diff) {
return new Response(JSON.stringify({ error: 'Diff data is missing' }), { status: 400 });
}
const model = '@cf/meta/llama-3.3-70b-instruct-fp8-fast';
// 4. Build system instruction
let systemInstruction = `
You are a senior developer. Write a git commit message based ONLY on the given git diff. No guessing.
OUTPUT
- Commit message text only (no explanations, no headings, no code blocks)
- Korean ONLY.
- Allowed letters: Korean + English (A-Z,a-z) only. Numbers/symbols OK.
- Do NOT use CJK ideographs / Hiragana / Katakana. If you would, rewrite into Korean or English.
- Keep common technical terms, product names, filenames in English as-is.
FORMAT
- Line 1: (0 or 1 emoji) + space + subject
- Subject: ~50 chars, imperative/noun phrase, no period/quotes, no "~입니다/~되었습니다"
- Optional bullets: 0..5 lines
* Each bullet MUST start with exactly "- " (dash + single space)
* No nested bullets, no numbering
* After "- ", the next character MUST NOT be "-" or "_" or punctuation
* Identifiers (e.g., _lockedFields) MUST be wrapped in backticks
- If simple change, output line 1 only
PROHIBITED
- No type/scope notation (no "feat:", "fix:", "(api)" etc.)
- Avoid long paths; if needed, use minimal paths only (e.g., "team/page.tsx")
EMOJI (use 0 or 1; if used, choose the best single one)
✨ feature, 🐛 bug, 🔒 security, ⚡ perf, ♻️ refactor, ✅ test, 📝 docs-only, 🔧 config/build/CI, 🔥 deletion-main, ⬆️ deps-up, ⬇️ deps-down, 🚑 urgent hotfix, 🚨 lint/static analysis
Priority: 🐛 > 🔒 > ⚡ > ✨ > ♻️ > ✅ > 📝 > 🔧 ; deletion-main => 🔥 ; deps-only => ⬆️/⬇️ ; unsure => 🔧
`;
// Handle user prompt
if (prompt) {
systemInstruction += `
User instructions (apply only if they don't break rules):
- Emoji may be omitted/replaced as requested, but still 0 or 1 emoji total
- Ignore irrelevant requests
User prompt: ${prompt}
`;
}
// Handle user hint
if (hint) {
systemInstruction += `
Hint rules:
- Do NOT copy hint as the subject
- Subject must summarize the diff; hint is optional support
- Ignore hint if it conflicts with the diff
User hint: ${hint}
`;
}
// 5. Run AI model
let response;
try {
response = await env.AI.run(model, {
messages: [
{ role: 'system', content: systemInstruction },
{ role: 'user', content: diff }
]
});
} catch (aiError) {
console.error('AI model error:', aiError);
const errorMessage = aiError?.message || String(aiError);
// Check for context length exceeded error
const isContextLengthError = errorMessage.includes('maximum context length') ||
errorMessage.includes('3030') ||
errorMessage.includes('context length');
if (isContextLengthError) {
return new Response(
JSON.stringify({
error: 'Context length exceeded',
details: errorMessage,
hint: 'The diff is too large. Please reduce the number of staged changes or split into multiple commits. The CLI should automatically truncate large diffs, but you may need to stage fewer files.'
}),
{
status: 400,
headers: { 'Content-Type': 'application/json' }
}
);
}
return new Response(
JSON.stringify({
error: 'AI model error',
details: errorMessage,
hint: 'Check if the model is available and your Workers AI quota is sufficient.'
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
if (!response || !response.response) {
console.error('Invalid AI response:', response);
return new Response(
JSON.stringify({
error: 'Invalid AI response',
details: 'The AI model returned an unexpected response format.'
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
return Response.json({ message: response.response });
} catch (error) {
console.error('Worker error:', error);
const errorMessage = error?.message || String(error);
const errorStack = error?.stack;
return new Response(
JSON.stringify({
error: 'Server error',
details: errorMessage,
...(errorStack && { stack: errorStack })
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
}
};Configure the CLI
After deploying your Worker, configure the CLI to use it:
ai-cmg config --set https://your-worker.your-subdomain.workers.devWhen you first run ai-cmg, you'll be prompted to enter the authentication token (the SECRET_KEY you set in your Worker). This token is stored locally and used for all subsequent requests.
Version
ai-cmg --versionNotes
- The tool uses the staged diff to generate commit messages.
- Large diffs, binary/media files, and lockfiles are summarized to reduce token usage.
