CLI tool
Everduin94/better-commits avatar
Everduin94/better-commits

better-commits: a prompt-driven commit message CLI for Node projects

A CLI for creating better commits following the conventional commits specification

2,289 stars80 forksTypeScriptMIT

At a glance

What is it?
A small TypeScript CLI that composes conventional commit messages from interactive prompts, infers the type, scope and ticket from your branch name, and runs the same way from an editor, a script or an agent.
Who is it for?
better-commits is a good fit for a team that already names branches in a structured way, because the branch inference is where its time saving comes from and a team with free-form branch names will only get the prompts. It is also a reasonable choice for agent-driven work, since the `--no-interactive` path exists precisely so a tool can compose a message without spending tokens on a questionnaire.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 41 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two commands, one job each

The installation is a single global npm install, and the README is explicit that Node.js 20 or newer is required:

sh
npm install -g better-commits

The package publishes more binaries than you would expect. `better-commits` runs the commit composer, `better-branch` creates a branch or a worktree, and `better-commits-init` writes a repository-level config file. Two shorter aliases, `bcommits` and `git-bc`, point at the same entry point as `better-commits`, which comes from the `bin` map in `package.json` where four of the five names resolve to `./dist/index.js`.

In normal use you run one of these two:

sh
better-commits # Create a new commit
better-branch # Create a new branch / worktree

`better-commits` asks a series of questions, assembles a conventional commit message from the answers, shows it to you in colour, and only commits once you confirm. The README points readers at the Conventional Commits summary for the reasoning behind each prompt rather than restating the specification, which is the right division of labour: this tool asks, it does not define the format.

Branch names as a structured input

The feature that separates this from a plain commit template is inference. The README lists it as inferring ticket, commit scope and commit type from the branch for consistent and fast commits, and the config comments spell out the three conventions it expects.

For the type, a branch shaped `user/TYPE/my-branch` is enough, which maps onto `user/feat/my-branch` giving a commit type of feat. The scope needs the ticket segment as well: `user/type/ticket-SCOPE-my-branch` produces a scope from the part after the ticket. The ticket itself comes from `user/type/TICKET-my-branch`. Three fields, three shapes, all derived from a name a developer typed once when they started the work.

This is also the tool's main failure mode. If your team writes branch names like `fix-login-bug`, nothing is inferred and every field falls back to a prompt, at which point you are typing the same answers a template would have asked for. The inference settings are per-field toggles, so you can keep the type inference and turn the rest off.

There is a second inference worth knowing about, on the ticket field specifically: `confirm_ticket` is true by default, so an inferred ticket is shown for confirmation before it is used rather than silently pasted into the title.

Config lives in two places and prefers the global one

Configuration is JSON with comments, and the filename moved recently from `.better-commits.json` to `.better-commits.jsonc`. Both names are still accepted. The first time you run the CLI it generates a default config in your `$HOME` directory, and that file is used whenever a repository-specific config cannot be found.

A repository config is created by running `better-commits-init` from the project root. The direction of precedence is the detail to notice, and it is not the one most tools use: properties such as `confirm_with_editor` and `overrides` prefer the global config over the repository one. So a per-project file cannot change those two settings, while everything else in it can.

Every property is optional and is replaced by the default at run time, which the README states as a note rather than a warning. Practically that means a config file can be as short as the fields you care about, and the repository ships its own `.better-commits.json` as the worked example to copy.

There is config validation with error messaging in the feature list, so a malformed file produces a message instead of a stack trace. That check is done with `valibot` in the development dependencies, while the repository's topic tags mention `zod`, a leftover from an earlier choice that the manifest no longer backs.

The type and scope vocabulary is yours to set

The config is where the commit grammar actually lives. The top of it controls the interactive git status step that runs before composing:

json
  "check_status": true,
  "check_status_autocomplete": true,

Then each field, commit type, commit scope and ticket, has its own block. The commit type block enables the prompt, sets the initially selected value, caps the visible list at `max_items`, and turns branch inference on or off:

json
  "commit_type": {
    "enable": true,
    "initial_value": "feat",
    "max_items": 20,
    "infer_type_from_branch": true,
    "autocomplete": true,

Each option in that list is an object with a value, a label, a hint explaining the type in words, an emoji, and a trailer. The ten types the example ships are feat, fix, docs, refactor, perf, test, build, ci and chore, plus an empty one meaning none, and every type carries a `Changelog:` trailer that names its category. That trailer is the mechanism behind two of the listed side-effects: because each type is tagged, semantic releases and changelog generation can group commits without reading the whole message.

Scope works the same way with a smaller vocabulary. The example offers app, shared, server and tools plus none, and a `custom_scope` switch decides whether a scope outside the list may be typed instead of chosen.

The emoji settings are three separate booleans and a position. You can put an emoji in the prompt label, append it to the commit message, or neither, and `emoji_commit_position` chooses between the start of the message and after the colon. Keeping the emoji in the prompt label only is the configuration that gets the visual cue for the person committing without polluting the history.

Commit text as the input to everything downstream

The README lists the consequences of consistent message formatting as a side-effect, which is the real argument for the tool beyond saving typing.

Auto-populating a pull request title and body. Automating semantic releases. Automating changelogs. And automatically linking and closing related tickets and issues, which is the feature that depends most directly on the ticket inference working, since the link cannot be built from a ticket number the tool never found.

Those four all consume the commit message, which means this project is only as good as the convention your team agrees on. If half your commits are written by hand in a different style, the semantic release notes will reflect that, and the changelog will have gaps that no configuration setting can repair.

The release pipeline for the tool itself is the same idea applied to itself. `package.json` configures semantic-release with the commit analyzer, the release notes generator, the npm and GitHub plugins, and the Git plugin with assets, and the repository's releases show the result: v1.26.1 on 2026-08-26 fixed branch ref format validation, v1.26.0 on 2026-08-14 improved install size, and v1.25.0 the same day added branch hook variables. Patch, minor and feature bumps are all decided from commit text.

Scripting it, and letting an agent drive it

The feature list calls the tool scriptable and notes it works with agents via CLI flags, and the README carries a tip explaining why that matters: the `--no-interactive` flag lets automated workflows or AI agents such as OpenCode and Claude Code generate consistent commit messages using fewer tokens.

That is a real design constraint rather than a slogan. An interactive prompt is expensive to drive programmatically, because each question costs a round trip and a parse. With the flag, the values arrive as flags instead, the message is composed from them, and the token cost drops to roughly the size of the message. The README points you at `better-commits --help` and `better-branch --help` for the available flags rather than listing them, which is the right call for a surface that changes between releases.

The `better-branch` half has its own configuration surface, described in the README as flexible workflow hooks and configurable in the same file. The v1.25.0 release added branch hook variables and v1.26.1 tightened branch ref format validation, so this area is where the recent work has been.

What you give up, and what to compare it against

The honest limitation is that the tool is opinionated about format and unhelpful about everything else. It composes commit text; it does not stage your changes intelligently, resolve conflicts, or tell you what belongs in this commit rather than the next. The interactive git status step is a convenience before the prompts, not a replacement for thinking about what you are committing.

There is also a small mismatch between the README and the manifest worth knowing if you script against internals. The feature list claims a bundle size of 34kb measured with treeshaking enabled, which is a figure for the library shape rather than for an installed CLI, and the repository topics advertise a validation library the dependencies no longer use.

The alternatives fall into two groups. Commitlint is the validator: it checks messages against a configured format but asks nothing, so it never saves you from writing the message. Commitizen provides an interactive prompt adapter over a similar idea, which is the closest functional comparison and the one to weigh if you want a different prompt library. Plain `git commit -t`, a commit template file, or a husky hook that rejects malformed messages all do less and cost less.

What better-commits adds over those is the inference from branch names and the non-interactive path for agents. If your branches follow the three-part convention and your tooling is automated, that combination is worth the extra dependency; if not, commitlint plus a template will do the same job in a fraction of the code.

Editorial conclusion

better-commits is a good fit for a team that already names branches in a structured way, because the branch inference is where its time saving comes from and a team with free-form branch names will only get the prompts. It is also a reasonable choice for agent-driven work, since the `--no-interactive` path exists precisely so a tool can compose a message without spending tokens on a questionnaire. Two things to know before adopting it. It needs Node.js 20 or newer, and it ships five command aliases including `git-bc`, so decide early which one your team will standardise on. It also changes the commit text, which means existing tooling that pattern-matches messages may see new trailers. Read the `.better-commits.jsonc` that `better-commits-init` writes before you commit to a type and scope vocabulary, then run `better-commits --help` to see what can be passed on the command line.

Frequently asked questions

What does better-commits do?

It is a Node CLI that asks a series of prompts and assembles the answers into a conventional commit message, previewing it in colour before committing. It can also infer the commit type, scope and ticket from your branch name, and supports non-interactive use through CLI flags.

How do you install better-commits and what does it require?

Run `npm install -g better-commits`. The README states that Node.js 20 or newer is required, and the package manifest sets the same floor with an engines field requiring node 20.19.0 or above.

How does better-commits work with branch names?

It reads three conventions from the branch name: `user/TYPE/my-branch` for the commit type, `user/type/ticket-SCOPE-my-branch` for the scope, and `user/type/TICKET-my-branch` for the ticket. Each inference is a separate toggle in the config, and an inferred ticket is confirmed before use by default.

Where does better-commits store its configuration?

A default config named `.better-commits.jsonc` is generated in your `$HOME` directory on first run, and `better-commits-init` writes a repository-specific config at the project root. The global config wins for properties such as `confirm_with_editor` and `overrides`, while other repository properties take precedence.

Can better-commits be used without prompts, for example by an agent?

Yes. The `--no-interactive` flag is documented as the way to use the tool from automated workflows or AI agents, passing values as CLI flags instead of answering prompts, which the README says uses fewer tokens. `better-commits --help` lists the available flags.

Official sources

  1. Everduin94/better-commits on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/everduin94-better-commits.svg)](https://hysenlabs.com/projects/everduin94-better-commits)