npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-cmg

Usage

ai-cmg

Flow:

  • 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 --reset

Interactive configuration is also available.

ai-cmg config

Worker 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

  1. Create a new Worker in your Cloudflare dashboard
  2. Set the SECRET_KEY environment variable (secret) in your Worker settings. This key will be used to authenticate requests and prevent unauthorized usage.
  3. 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.dev

When 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 --version

Notes

  • The tool uses the staged diff to generate commit messages.
  • Large diffs, binary/media files, and lockfiles are summarized to reduce token usage.