ai-cmg
v0.1.6
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 (Hangul) + English (A-Z,a-z) only. Numbers/symbols OK.
- ABSOLUTELY NO Chinese characters (漢字/한자). Write everything in pure Hangul or English.
Examples of FORBIDDEN output: 復舊, 修正, 追加, 削除, 變更, 機能, 設定
Correct alternatives: 복구, 수정, 추가, 삭제, 변경, 기능, 설정
- Do NOT use CJK ideographs / Hiragana / Katakana under ANY circumstances.
- 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 RULES (use 0 or 1; choose based on the PRIMARY purpose of the ENTIRE diff)
ALLOWED GITMOJI LIST (ONLY these are permitted):
🎨 — Improve structure / format of the code
⚡️ — Improve performance
🔥 — Remove code or files
🐛 — Fix a bug
🚑 — Critical hotfix
✨ — Introduce new features
📝 — Add or update documentation ONLY (no code logic changes)
💄 — Add or update UI and style files
🎉 — Begin a project
✅ — Add or update tests
🔒 — Fix security issues
🔖 — Release / Version tags
💚 — Fix CI build
📌 — Pin dependencies to specific versions
👷 — Add or update CI build system
📈 — Add or update analytics or tracking code
♻️ — Refactor code
➕ — Add a dependency
➖ — Remove a dependency
🔧 — Add or update configuration files
🔨 — Add or update development scripts
🌐 — Internationalization and localization
⏪ — Revert changes
🔀 — Merge branches
📦 — Add or update compiled files or packages
👽 — Update code due to external API changes
🚚 — Move or rename resources (files, paths, routes)
📄 — Add or update license
💡 — Add or update comments in source code
🗃 — Database related changes
🔊 — Add or update logs
🙈 — Add or update .gitignore file
EMOJI SELECTION — judge by the OVERALL intent of the commit, not individual files:
- Fixing a bug/error/crash → 🐛 (even if new files were created as part of the fix)
- Fixing a critical production issue → 🚑
- Fixing security vulnerability → 🔒
- Improving performance → ⚡️
- Adding a new feature → ✨
- Improving code structure/formatting without behavior change → 🎨
- Refactoring code (restructuring) → ♻️
- Only tests changed → ✅
- ONLY documentation/comments changed (zero code logic) → 📝
- ONLY comments in source code changed → 💡
- UI/style only changes → 💄
- Config files only → 🔧
- Primarily deleting code/files → 🔥
- Adding dependency → ➕ / Removing dependency → ➖
- CI build fix → 💚 / CI system update → 👷
- If unsure → omit emoji entirely (no emoji is better than wrong emoji)
CRITICAL RULES:
- Do NOT use any emoji outside the allowed list above.
- 📝 is ONLY for commits where EVERY changed file is documentation (README, docs/, etc.). If ANY code logic changed, do NOT use 📝.
- Judge by the overall PURPOSE of the diff, not individual files. A bug fix that creates a new helper file is still 🐛, NOT ✨.
- When the diff clearly shows error/bug correction (e.g., fixing wrong logic, handling exceptions, fixing typos in code), always use 🐛.
`;
// 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 }
],
max_tokens: 512,
temperature: 0.4,
top_p: 0.9,
top_k: 30,
repetition_penalty: 1.1,
frequency_penalty: 0.3,
});
} 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) {
console.error('Invalid AI response: response is null/undefined');
return new Response(
JSON.stringify({
error: 'Invalid AI response',
details: 'The AI model returned null or undefined.'
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Compatible response format handling across different model families
// - Llama and other legacy models: response.response (plain string)
// - OpenAI-compatible models (e.g. GLM): response.choices[0].message.content
const message =
response.response ??
response.choices?.[0]?.message?.content ??
null;
if (!message) {
console.error('Invalid AI response:', JSON.stringify(response));
return new Response(
JSON.stringify({
error: 'Invalid AI response',
details: 'Could not extract message from AI response.',
raw: response
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
return Response.json({ message });
} 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.
