Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Commitly

Track billable hours across Claude Code, Codex CLI, and OpenCode — with git commit history as independent, tamper-evident evidence — and generate client-ready hour reports.

If you bill clients hourly for AI-assisted development work, self-reported timers aren't very convincing and git log only shows commit moments, not how long you actually worked. Commitly listens to each tool's session lifecycle hooks, logs activity locally to SQLite, derives real work sessions from that activity, cross-references matching git commits, and exports CSV/HTML reports (or a live dashboard) you can hand to a client.

How it works

  1. commitly init registers a lightweight hook in whichever of Claude Code, Codex CLI, and OpenCode are installed on your machine. Each hook just pipes an event to commitly hook <tool> <event> — all real logic stays in this codebase, not scattered shell scripts.
  2. Every hook invocation appends a raw event (tool, session id, cwd, timestamp) to a local SQLite database (~/.commitly/commitly.db). Nothing is paired or interpreted at write time.
  3. At report time, Commitly derives "work sessions" from that raw event stream using an idle-gap algorithm: events belonging to the same session id are grouped, and a session is considered to end either when an authoritative close event fires (Claude Code's SessionEnd, OpenCode's session.deleted) or when the gap to the next event exceeds a configurable idle threshold (default 15 minutes). This uniform algorithm is necessary because only Claude Code has a clean session-close signal — Codex CLI has no dedicated close event at all, and OpenCode's closest equivalent isn't guaranteed to fire on abrupt exit.
  4. Commitly looks up git commits made in each session's repo during that time window and attaches them as corroborating evidence.
  5. commitly report exports CSV or a self-contained, print-to-PDF-friendly HTML file. commitly dashboard runs a local web UI over the same data.

Install

bun install
bun link   # makes `commitly` resolvable on $PATH
commitly init

Without bun link, commitly init still works from a checkout — it falls back to invoking bun <path-to-cli.ts> directly in the registered hooks.

commitly init is non-destructive: it detects which tools are installed, and merges its own hook entries into each tool's existing config without touching any other hooks already registered there (verified against real-world configs with other tools' hooks already present). Re-running it is idempotent.

commitly init                              # register hooks for every detected tool, user scope
commitly init --tools claude-code          # only Claude Code
commitly init --scope project              # register in ./.claude, ./.codex, ./.opencode instead of $HOME
commitly init --force                      # replace an existing commitly hook entry (e.g. after upgrading)

Usage

commitly report                                   # this month, CSV to stdout
commitly report --from 2026-06-01 --to 2026-06-30 --format html --out june.html
commitly report --project acme --client "Acme Corp"
commitly report --with-git=false                   # skip git commit lookups (faster, no evidence column)
commitly dashboard                                 # local web UI at http://localhost:4317
commitly doctor                                    # verify hooks are registered and the DB is healthy
commitly projects add --key acme --name "Acme Website" --client "Acme Corp" --rate 150 --path-prefix /path/to/acme
commitly projects list

Projects are optional — an unconfigured repo still shows up in reports, using its folder name, just without a rate or client attached. Configure commitly projects add for anything you actually bill.

Configuration

  • ~/.commitly/commitly.db — SQLite database, append-only raw events plus a regenerable session cache.
  • ~/.commitly/projects.json — human-editable project/client/rate registry. Matched by path prefix (longest match wins) or git remote URL.
  • ~/.commitly/config.jsonidleGapMinutes (default 15) and defaultCurrency.
  • ~/.commitly/hook-errors.log — hook normalization failures are logged here rather than ever blocking your actual coding session; check commitly doctor if a session seems to be missing from a report.

Per-tool notes

  • Claude Code: hooks live in .claude/settings.json. Commitly registers SessionStart, Stop, and SessionEnd. Stop (fires after every turn) is required, not optional — without it, a long but continuous session with no other activity looks identical to two isolated moments far apart in time, and gets incorrectly split by the idle-gap algorithm.
  • Codex CLI: hooks live in hooks.json. Commitly registers SessionStart and Stop. Codex has no session-close event at all, so every Codex session boundary is decided by the idle-gap heuristic. As of Codex 0.120 there's a known bug where project-local hooks sometimes don't fire in interactive sessions — user-scope (the default) is recommended.
  • OpenCode: Commitly writes a generated plugin file to ~/.config/opencode/plugins/commitly.js, subscribed to session.created, session.idle, and session.deleted. session.deleted is treated as an authoritative close, same tier as Claude Code's SessionEnd, though it isn't guaranteed to fire on an abrupt exit.

Development

bun install
bun test          # unit + integration tests, including a real scratch git repo for git evidence
bunx tsc --noEmit
bun run src/cli.ts <command>

License

MIT

About

Track billable hours across Claude Code, Codex CLI, and OpenCode, with git commit history as evidence.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages