{"_id":"@anishhs/ledger","_rev":"4-2a0fbf497a8ea3961e2f003f4f9c9be4","name":"@anishhs/ledger","dist-tags":{"beta":"1.0.0-beta.1","latest":"1.1.0"},"versions":{"1.0.0-beta.0":{"name":"@anishhs/ledger","version":"1.0.0-beta.0","keywords":["release-notes","changelog","ai","git","cli"],"author":{"url":"https://anishhs.com","name":"Anish Shekh","email":"anishsh701@gmail.com"},"license":"MIT","_id":"@anishhs/ledger@1.0.0-beta.0","maintainers":[{"name":"arc22-dev","email":"anishsh701@gmail.com"}],"homepage":"https://github.com/anishhs-gh/ledger#readme","bugs":{"url":"https://github.com/anishhs-gh/ledger/issues"},"bin":{"ledger":"dist/cli.js"},"dist":{"shasum":"02dd2666712a2b5d8589819aa612e38cc59a0f24","tarball":"https://registry.npmjs.org/@anishhs/ledger/-/ledger-1.0.0-beta.0.tgz","fileCount":5,"integrity":"sha512-hRAG06EostehGQdYXG4Jf9CCxzbxcv+CjnwMlg882DX8KOkaBSlu7hPKv9mlh0XhQ3IShYnhy4YT098WA3Njow==","signatures":[{"sig":"MEUCIQD+dGoT4X37K+548/Y7Yoohh9Lcu0zcNTJ9k37vyFWOhwIgZN+GupraCjY64Pf/Lma3OB/392cIbvhxGcg0DKrS2Eg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anishhs%2fledger@1.0.0-beta.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":54584},"engines":{"node":">=18"},"gitHead":"a5a4f24603590324c7bb56017b1e4eeae2bf1bbe","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"arc22-dev","email":"anishsh701@gmail.com"},"repository":{"url":"git+https://github.com/anishhs-gh/ledger.git","type":"git"},"_npmVersion":"10.8.2","description":"AI-powered release notes generator — analyses git history and diffs to produce audience-tailored notes. Works locally and in any CI.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"openai":"^4.52.0","js-yaml":"^4.1.0","commander":"^12.0.0","simple-git":"^3.22.0","@anthropic-ai/sdk":"^0.27.0","@google/generative-ai":"^0.21.0","@aws-sdk/client-bedrock-runtime":"^3.1068.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.0","eslint":"^9.9.0","vitest":"^2.0.0","@eslint/js":"^9.9.0","typescript":"^5.5.0","@types/node":"^20.0.0","@types/js-yaml":"^4.0.9","typescript-eslint":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ledger_1.0.0-beta.0_1782736585283_0.8182416828739083","host":"s3://npm-registry-packages-npm-production"}},"1.0.0-beta.1":{"name":"@anishhs/ledger","version":"1.0.0-beta.1","keywords":["release-notes","changelog","ai","git","cli"],"author":{"url":"https://anishhs.com","name":"Anish Shekh","email":"anishsh701@gmail.com"},"license":"MIT","_id":"@anishhs/ledger@1.0.0-beta.1","maintainers":[{"name":"arc22-dev","email":"anishsh701@gmail.com"}],"homepage":"https://github.com/anishhs-gh/ledger#readme","bugs":{"url":"https://github.com/anishhs-gh/ledger/issues"},"bin":{"ledger":"dist/cli.js"},"dist":{"shasum":"b515d36d44c144733e948464c04c7bbc304b3847","tarball":"https://registry.npmjs.org/@anishhs/ledger/-/ledger-1.0.0-beta.1.tgz","fileCount":5,"integrity":"sha512-IOqpT0DIWIZ6HGhvjhDS8F662B7e7fIo/rLZIEVF4DaaABEAHXwdI2StMGt+MmCIvJXve6paQnoU89C5FcxQgA==","signatures":[{"sig":"MEUCIH6JTv0dAsb+e5jczZ1MBEGYMJK4B8ODiN1C7qc8dAPfAiEAnHCEeXuFDektB39AXq/G7zHs9aKcRspRHOqfEpZhSaE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anishhs%2fledger@1.0.0-beta.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":59792},"engines":{"node":">=20"},"gitHead":"b4038700afd770703cfa89569bc0536b8d3ad696","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"arc22-dev","email":"anishsh701@gmail.com"},"repository":{"url":"git+https://github.com/anishhs-gh/ledger.git","type":"git"},"_npmVersion":"10.8.2","description":"AI-powered release notes generator — analyses git history and diffs to produce audience-tailored notes. Works locally and in any CI.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"openai":"^6.45.0","js-yaml":"^5.2.0","commander":"^14.0.0","simple-git":"^3.36.0","@google/genai":"^2.10.0","@anthropic-ai/sdk":"^0.109.0","@aws-sdk/client-bedrock-runtime":"^3.1077.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsup":"^8.5.1","eslint":"^10.6.0","vitest":"^4.1.9","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^20.0.0","typescript-eslint":"^8.62.1"},"_npmOperationalInternal":{"tmp":"tmp/ledger_1.0.0-beta.1_1782899871154_0.9375374478921272","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@anishhs/ledger","version":"1.0.0","keywords":["release-notes","release-notes-generator","changelog","changelog-generator","ai","llm","git","git-history","git-diff","cli","npx","ci","github-actions","automation","devtools","byok","openai","anthropic","gemini","bedrock","ollama","openrouter"],"author":{"url":"https://anishhs.com","name":"Anish Shekh","email":"anishsh701@gmail.com"},"license":"MIT","_id":"@anishhs/ledger@1.0.0","maintainers":[{"name":"arc22-dev","email":"anishsh701@gmail.com"}],"homepage":"https://github.com/anishhs-gh/ledger#readme","bugs":{"url":"https://github.com/anishhs-gh/ledger/issues"},"bin":{"ledger":"dist/cli.js"},"dist":{"shasum":"83c6e43bb49eabd2c21e7351bce3055a21e442a7","tarball":"https://registry.npmjs.org/@anishhs/ledger/-/ledger-1.0.0.tgz","fileCount":5,"integrity":"sha512-EllUtlf/dPWa1y5cAEol2ynTdKNqmN1oQ8MXfXMk2gGwr4QTVmhiSLYrMVDebpibfwVmgKnGN1CrspmelOeWpA==","signatures":[{"sig":"MEUCID5awqVcVekqDRftxRHa193RT8pmghKaflvqBgd3RdyTAiEAmwvjuUBnOE8n0n1VEKRMYQNrk8AEJdTCbrZO6BdQgQY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anishhs%2fledger@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":76924},"engines":{"node":">=20"},"gitHead":"cda62ac2d7ea88160f017a85cfa91c8f60bdf71a","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"arc22-dev","email":"anishsh701@gmail.com"},"repository":{"url":"git+https://github.com/anishhs-gh/ledger.git","type":"git"},"_npmVersion":"10.8.2","description":"AI-powered release notes generator — analyses git history and diffs to produce audience-tailored notes. Works locally and in any CI.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"js-yaml":"^5.2.0","commander":"^14.0.0","simple-git":"^3.36.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^10.6.0","vitest":"^4.1.9","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^20.0.0","typescript-eslint":"^8.62.1"},"_npmOperationalInternal":{"tmp":"tmp/ledger_1.0.0_1783437600294_0.7342074799864948","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@anishhs/ledger","version":"1.1.0","description":"AI-powered release notes generator — analyses git history and diffs to produce audience-tailored notes. Works locally and in any CI.","bin":{"ledger":"dist/cli.js"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","lint":"eslint src","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build"},"keywords":["release-notes","release-notes-generator","changelog","changelog-generator","ai","llm","git","git-history","git-diff","cli","npx","ci","github-actions","automation","devtools","byok","openai","anthropic","gemini","bedrock","ollama","openrouter"],"author":{"name":"Anish Shekh","email":"anishsh701@gmail.com","url":"https://anishhs.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/anishhs-gh/ledger.git"},"bugs":{"url":"https://github.com/anishhs-gh/ledger/issues"},"homepage":"https://github.com/anishhs-gh/ledger#readme","publishConfig":{"access":"public","provenance":true},"dependencies":{"commander":"^14.0.0","js-yaml":"^5.4.1","simple-git":"^3.36.0"},"devDependencies":{"@eslint/js":"^10.0.1","@types/node":"^20.0.0","eslint":"^10.6.0","tsup":"^8.5.1","typescript":"^6.0.3","typescript-eslint":"^8.62.1","vitest":"^4.1.9"},"engines":{"node":">=20"},"_id":"@anishhs/ledger@1.1.0","gitHead":"a23cbecc2427b5a2ffa7b7e4dcd4866795328876","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-keWkEmIa9fT89vI5sDuYiKOxFUYLEsq6FXSgrQ9goPvEu6Muvx6LUoMAnnV37MrlZKWuBWvBuQQ+YwZFYo4ynA==","shasum":"8d89ed159db8fc49eb6ea33ee046915c903ccced","tarball":"https://registry.npmjs.org/@anishhs/ledger/-/ledger-1.1.0.tgz","fileCount":5,"unpackedSize":79772,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anishhs%2fledger@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBG3bjQERaxtzRrDrLrtKV8YtKWpGxO8FRrTzz1uh4gjAiEA4L1VSSvtYup2jb6TXqPpWRkvwUKfpkXOMnSg9ytaBas="}]},"_npmUser":{"name":"arc22-dev","email":"anishsh701@gmail.com"},"directories":{},"maintainers":[{"name":"arc22-dev","email":"anishsh701@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ledger_1.1.0_1787822315926_0.08116386725870695"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-29T12:36:25.169Z","modified":"2026-08-27T09:18:36.344Z","1.0.0-beta.0":"2026-06-29T12:36:25.434Z","1.0.0-beta.1":"2026-07-01T09:57:51.278Z","1.0.0":"2026-07-07T15:20:00.430Z","1.1.0":"2026-08-27T09:18:36.055Z"},"bugs":{"url":"https://github.com/anishhs-gh/ledger/issues"},"author":{"name":"Anish Shekh","email":"anishsh701@gmail.com","url":"https://anishhs.com"},"license":"MIT","homepage":"https://github.com/anishhs-gh/ledger#readme","keywords":["release-notes","release-notes-generator","changelog","changelog-generator","ai","llm","git","git-history","git-diff","cli","npx","ci","github-actions","automation","devtools","byok","openai","anthropic","gemini","bedrock","ollama","openrouter"],"repository":{"type":"git","url":"git+https://github.com/anishhs-gh/ledger.git"},"description":"AI-powered release notes generator — analyses git history and diffs to produce audience-tailored notes. Works locally and in any CI.","maintainers":[{"name":"arc22-dev","email":"anishsh701@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/anishhs-gh/ledger/master/ledger.svg\" alt=\"ledger\" width=\"64\" />\n</p>\n\n<h1 align=\"center\">ledger</h1>\n\n[![npm](https://img.shields.io/npm/v/@anishhs/ledger.svg)](https://www.npmjs.com/package/@anishhs/ledger)\n[![CI](https://github.com/anishhs-gh/ledger/actions/workflows/ci.yml/badge.svg)](https://github.com/anishhs-gh/ledger/actions/workflows/ci.yml)\n[![license: MIT](https://img.shields.io/npm/l/@anishhs/ledger.svg)](./LICENSE)\n[![node](https://img.shields.io/node/v/@anishhs/ledger.svg)](https://nodejs.org)\n\n**AI-powered release notes, generated from your git history — locally or in any CI.**\n\n`ledger` analyses your commits **and the actual code diffs**, reduces them into a focused\ncontext, and asks the AI provider of your choice to write release notes tailored to a\nspecific audience (engineering, business, or QA). Bring your own API key; no vendor lock-in.\n\n```bash\nnpx @anishhs/ledger generate --since-last-tag\n```\n\n> [!TIP]\n> **~3.5 MB install — 16× smaller than the beta.** The public beta (`1.0.0-beta.1`) bundled a\n> vendor SDK for every provider and weighed ~58 MB. Stable `1.0.0` is **SDK-free**: every provider\n> talks to its API over plain `fetch`, dropping the install to ~3.5 MB. Since `ledger` is meant to\n> be run with `npx`, that means far faster cold starts and much less bandwidth per run — which is\n> why it's worth calling out.\n\n---\n\n## Why\n\nMost changelog tools just reformat commit messages. `ledger` reads the diff too, so the\nnotes describe *what actually changed* — not just what someone typed in a commit subject.\n\n- **Provider-agnostic (BYOK):** OpenAI, Anthropic, Gemini, OpenRouter, Ollama, Bedrock.\n- **CI-first, local-friendly:** the same command works on your laptop and inside GitHub\n  Actions, GitLab CI, Jenkins, or any runner — auto-detecting the environment and range.\n- **Robust:** per-request timeouts, automatic retry with backoff (honouring a provider's\n  `Retry-After` hint), and a documented exit-code contract so pipelines behave predictably.\n- **Cheap to preview:** `--dry-run` assembles the context and estimates tokens without\n  spending anything.\n\n## How it works\n\nFor a commit range, `ledger`:\n\n1. **Collects** the commits *and their actual code diffs* with `git`.\n2. **Reduces** them into a focused context, capping per-file diff size (`maxDiffLines`) so large\n   PRs stay within token budgets.\n3. **Prompts** your chosen AI provider — one non-streaming request — to write notes for your\n   chosen **audience** (engineering / business / QA).\n4. **Writes** the result as Markdown (default) or JSON, to stdout or a file.\n\nNothing leaves your machine except that single prompt to the provider you configured\n(bring your own key). `ledger` stores nothing and calls no other service.\n\n## Install\n\n**Requirements:** Node ≥ 20, `git` on `PATH`, and an API key for your chosen provider\n(none for local Ollama).\n\nNo install needed — run it with `npx`:\n\n```bash\nnpx @anishhs/ledger generate --since-last-tag\n```\n\nOr install globally:\n\n```bash\nnpm install -g @anishhs/ledger\nledger generate --since-last-tag\n```\n\n## Quickstart\n\n```bash\n# 1. Scaffold a config file\nledger init\n\n# 2. Set the API key for your chosen provider\nexport OPENAI_API_KEY=sk-...\n\n# 3. Generate notes for everything since the last git tag\nledger generate --since-last-tag > RELEASE_NOTES.md\n```\n\n## Usage\n\n```bash\nledger generate [options]\n```\n\n### Selecting a range\n\n| Flag | Description |\n| --- | --- |\n| `--since-last-tag` | From the last git tag to `HEAD` (default when no range is given). On a **first release** — no tags yet, or the only tag is the one you just cut on `HEAD` — it covers the entire history instead, root commit included |\n| `--from <ref>` | Start from a tag, branch, or SHA |\n| `--to <ref>` | End ref (default: `HEAD`) |\n| `--last <n>` | Include the last N commits (clamped to available history — asking for more than exist just includes everything back to the first commit) |\n\nIn CI, if you pass **no** range flag, `ledger` derives one automatically:\na tag build uses *previous tag → this tag*; a PR/MR build uses *base branch → HEAD*.\nOn the **first** tag build there is no previous tag, so the range covers the whole\nhistory — the root commit and everything it introduced are included, not skipped.\n\n### Output & behaviour\n\n| Flag | Description |\n| --- | --- |\n| `--audience <mode>` | `engineering` (default), `business`, or `qa` |\n| `--output <format>` | `markdown` (default) or `json` |\n| `-o, --output-file <path>` | Write notes to a file. Optional — omit it and notes go to **stdout**. When set, notes are **not** echoed to stdout (add `--stdout` if you want both) |\n| `--stdout` | Also echo the notes to stdout when writing to `--output-file` |\n| `--append` | Append to `--output-file` instead of overwriting (newest at the bottom) |\n| `--prepend` | Prepend to `--output-file` (newest on top, inserted below a leading `#` title) — ideal for a `CHANGELOG.md` |\n| `--max-tokens <n>` | Max tokens for the AI response (default 4096) |\n| `--timeout <ms>` | Per-request timeout (default 60000) |\n| `--quiet` | Suppress progress on stderr (errors still shown) |\n| `--dry-run` | Assemble context + estimate tokens, **without** calling the AI |\n| `--fail-on-empty` | Exit non-zero when there are no changes in the range |\n| `--no-summary` | Don't write to the CI step summary even when one is detected |\n| `--provider <name>` / `--model <name>` | Override the configured provider/model |\n| `--config <path>` | Path to a config file |\n\nWithout `--output-file`, notes are written to **stdout** and all progress/logging goes to\n**stderr**, so `ledger generate > NOTES.md` is always clean. With `--output-file`, the file is\nthe output and stdout stays quiet unless you add `--stdout`.\n\n### JSON output (for programmatic use)\n\n`--output json` emits a structured object instead of Markdown — handy when another tool consumes\nthe notes (posting a PR comment, feeding a release dashboard, etc.). `content` holds the AI-written\nbody; the rest is metadata `ledger` computed:\n\n```jsonc\n{\n  \"title\": \"Release Notes\",\n  \"date\": \"2026-07-07\",\n  \"range\": \"v1.2.0..HEAD\",\n  \"audience\": \"engineering\",\n  \"commits\": 12,\n  \"filesChanged\": 34,\n  \"content\": \"# Release Notes\\n\\n## New Features\\n...\",   // the AI-generated markdown body\n  \"repo\": \"owner/repo\",        // present in CI when detected\n  \"ref\": \"refs/tags/v1.3.0\",   // present in CI when detected\n  \"runUrl\": \"https://...\"      // present in CI when detected\n}\n```\n\n```bash\n# e.g. pull just the body out with jq\nledger generate --since-last-tag --output json | jq -r .content\n```\n\n## Configuration\n\nRun `ledger init` to scaffold **`ledger.config.yaml`** — one small YAML file that holds all of\n`ledger`'s settings (provider, model, token limits, …). **Commit it to your repo.** It's the single\nsource of truth used both on your laptop and in CI, so you never have to repeat provider/model flags\nanywhere. Everything in it is optional except `provider`.\n\n```yaml\nprovider: openai      # openai | anthropic | gemini | openrouter | ollama | bedrock | openai-compatible\nmodel: gpt-4o         # optional — a sensible default is used per provider (required for openai-compatible)\n# audience: engineering  # default audience when --audience isn't passed\n# maxDiffLines: 100   # cap per-file diff lines sent to the AI (reduces tokens on big PRs)\n# maxTokens: 4096     # max tokens for the AI's response (default 4096)\n# timeout: 60000      # per-request timeout (ms)\n# maxRetries: 3       # retry attempts on transient provider errors (429/5xx/network)\n```\n\nOnly the **API key** stays out of the file — keep it in an environment variable (locally) or a\nsecret (in CI). See [Providers & keys](#providers--keys) for the variable each provider reads.\n\n### Accepted config file names\n\n`ledger` looks in the current directory for the **first** of these that exists (so `.yaml` wins over\n`.yml` if you have both), or point at any path with `--config <path>`:\n\n| File | Format |\n| --- | --- |\n| `ledger.config.yaml` | YAML _(what `ledger init` creates)_ |\n| `ledger.config.yml` | YAML |\n| `.ledger.yaml` | YAML (dotfile) |\n| `.ledger.yml` | YAML (dotfile) |\n| `ledger.config.json` | JSON |\n\n`.yaml` and `.yml` are treated identically — pick whichever your repo prefers. A JSON config uses the\nsame keys:\n\n```json\n{\n  \"provider\": \"openai\",\n  \"model\": \"gpt-4o\",\n  \"maxTokens\": 4096\n}\n```\n\n```bash\n# Or keep it anywhere and pass the path explicitly:\nledger generate --since-last-tag --config config/ledger.json\n```\n\n**Precedence (highest first):** `LEDGER_PROVIDER` / `LEDGER_MODEL` env vars → CLI flags →\nconfig file → built-in defaults. So the committed file sets your defaults, and a flag lets you\noverride one run without editing it. (An **empty** env var — e.g. an unset `vars.LEDGER_MODEL` in\nCI — is ignored, so it falls back to the config file rather than blanking the value.)\n\n### Updating a CHANGELOG in place\n\n`--output-file` overwrites by default. Use `--prepend` to drop the new notes at the top of an\nexisting changelog (below its `#` title), or `--append` to add them at the bottom:\n\n```bash\nledger generate --since-last-tag -o CHANGELOG.md --prepend\n```\n\nA fresh file is created if it doesn't exist yet. (`--append`/`--prepend` need `--output-file` and\nmarkdown output.)\n\n### Providers & keys\n\n| Provider | Env var | Notes |\n| --- | --- | --- |\n| OpenAI | `OPENAI_API_KEY` | |\n| Anthropic | `ANTHROPIC_API_KEY` | |\n| Gemini | `GEMINI_API_KEY` | |\n| OpenRouter | `OPENROUTER_API_KEY` | |\n| Ollama | — | local; set `OLLAMA_BASE_URL` to override `http://localhost:11434/v1` |\n| Bedrock | `BEDROCK_API_KEY` *or* AWS IAM (`AWS_REGION`, `AWS_ACCESS_KEY_ID`, …) | `AWS_REGION` defaults to `us-east-1`. Reasoning models (e.g. `openai.gpt-oss-*`) are supported. |\n| **OpenAI-compatible** | `LEDGER_API_KEY` (or your own via `apiKeyEnv`) | **Any other service** — set `baseURL`. See below. |\n\n### Use any OpenAI-compatible provider\n\nNot locked to the six above. Most inference services — **Groq, Together, Fireworks, DeepSeek,\nMistral, xAI, Perplexity, Azure OpenAI**, or a self-hosted **vLLM / LiteLLM / LocalAI** server —\nexpose the OpenAI Chat Completions API. Point `ledger` at any of them with the `openai-compatible`\nprovider: give it a **base URL**, a **model**, and (usually) an **API key**.\n\n```bash\nexport LEDGER_API_KEY=gsk_...\nledger generate --since-last-tag \\\n  --provider openai-compatible \\\n  --base-url https://api.groq.com/openai/v1 \\\n  --model llama-3.3-70b-versatile\n```\n\nOr in `ledger.config.yaml`:\n\n```yaml\nprovider: openai-compatible\nbaseURL: https://api.groq.com/openai/v1   # or LEDGER_BASE_URL / --base-url\nmodel: llama-3.3-70b-versatile\n# apiKeyEnv: GROQ_API_KEY   # optional — read the key from a different env var (default LEDGER_API_KEY)\n# headers:                  # optional — extra request headers\n#   X-Title: my-app\n```\n\nThe key is read from `LEDGER_API_KEY` unless you name another var via `apiKeyEnv`. A purely local\nserver that needs no key works without one.\n\n## Use in CI\n\n`ledger` auto-detects the runner (GitHub Actions, GitLab CI, Jenkins, CircleCI, Buildkite,\nor a generic `CI`) and, when you pass no range flag, derives one: a **tag build** uses\n*previous tag → this tag*; a **PR/MR build** uses *base branch → HEAD*. On GitHub it also\nappends the notes to the run's **step summary**.\n\n> Two requirements on every runner: **full git history + tags** (CI often shallow-clones —\n> see below) and a **provider API key** from your secret store.\n\n**The recommended setup: commit `ledger.config.yaml`, and let CI just run `ledger generate`.**\nBecause your provider and model live in that file (see [Configuration](#configuration)), the CI job\ncarries no ledger settings at all — it only supplies the API key as a secret. Switching providers or\nmodels later is a one-line edit to the committed file, with nothing to change across your pipelines.\nThe snippets below assume a `ledger.config.yaml` is checked in.\n\n### GitHub Actions (composite action)\n\n> **The action is optional** — it's just a convenience wrapper around `npx @anishhs/ledger`. If you\n> prefer not to depend on it, skip straight to the plain `npx` step [below](#prefer-raw-npx) — it\n> does exactly the same thing. Everything `ledger` does (CI detection, range derivation, step\n> summary) lives in the CLI, not the action.\n\n```yaml\nname: Release notes\non:\n  push:\n    tags: ['v*']\njobs:\n  notes:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0 # full history + tags so the range can be derived\n      - uses: anishhs-gh/ledger@v1\n        with:\n          api-key: ${{ secrets.OPENAI_API_KEY }}   # the only secret; provider/model come from ledger.config.yaml\n          output-file: RELEASE_NOTES.md\n```\n\n> Pin the action to the floating `anishhs-gh/ledger@v1` tag (or a specific release, e.g.\n> `anishhs-gh/ledger@v1.1.0`) to control when you pick up updates.\n\n**Action inputs** — all optional; anything you omit is read from the committed `ledger.config.yaml`.\nSet them here only to override the file for this workflow:\n\n| Input | Default | Description |\n| --- | --- | --- |\n| `api-key` | — | Provider key; mapped to the correct `*_API_KEY` var, or `LEDGER_API_KEY` for `openai-compatible` (pass a secret) |\n| `provider` | from config | `openai` \\| `anthropic` \\| `gemini` \\| `openrouter` \\| `ollama` \\| `bedrock` \\| `openai-compatible` |\n| `model` | from config | Model name |\n| `base-url` | from config | OpenAI-compatible API base URL (for `openai-compatible`) |\n| `audience` | `engineering` | `engineering` \\| `business` \\| `qa` |\n| `output` | `markdown` | `markdown` \\| `json` |\n| `output-file` | — | Write notes to this file |\n| `args` | — | Extra raw flags, e.g. `--since-last-tag --fail-on-empty` |\n| `package` | `@anishhs/ledger` | npm spec to run (pin a version if you like, e.g. `@anishhs/ledger@1.1.0`) |\n\nOutput: `file` — the path written (equals `output-file`).\n\n<a id=\"prefer-raw-npx\"></a>\nPrefer raw `npx` (no action dependency)? That works too — still just the key plus the committed config:\n\n```yaml\n      - run: npx @anishhs/ledger generate -o RELEASE_NOTES.md\n        env:\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\n```\n\n### Attach the notes to a GitHub Release\n\nThe most common end-to-end flow: on a version tag, generate the notes and publish them as the\nrelease body. Generation is **best-effort** — if the AI call fails, fall back to GitHub's\nauto-generated notes so a release is never blocked. (This is exactly what `ledger`'s own\n[`publish.yml`](./.github/workflows/publish.yml) does.)\n\n```yaml\nname: Release\non:\n  push:\n    tags: ['v*']\npermissions:\n  contents: write   # required to create the release\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0                     # full history + tags\n\n      - name: Generate notes (best-effort)\n        id: notes\n        env:\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}   # provider/model from ledger.config.yaml\n        run: |\n          set -uo pipefail\n          file=\"$RUNNER_TEMP/NOTES.md\"\n          if npx --yes @anishhs/ledger generate --since-last-tag -o \"$file\" --quiet && [ -s \"$file\" ]; then\n            echo \"file=$file\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"::warning::ledger failed — falling back to GitHub auto-generated notes.\"\n          fi\n\n      - name: Create the release\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          if [ -n \"${{ steps.notes.outputs.file }}\" ]; then\n            gh release create \"${{ github.ref_name }}\" --notes-file \"${{ steps.notes.outputs.file }}\"\n          else\n            gh release create \"${{ github.ref_name }}\" --generate-notes\n          fi\n```\n\nThe two keys to the fallback: `set -uo pipefail` (**not** `-e`) so a failed generation doesn't\nabort the step, and the `&& [ -s \"$file\" ]` check so an *empty* file also triggers the fallback.\n\n### GitLab CI\n\n```yaml\nrelease-notes:\n  image: node:20\n  rules:\n    - if: $CI_COMMIT_TAG\n  variables:\n    GIT_DEPTH: \"0\"   # full history + tags\n  script:\n    - npx --yes @anishhs/ledger generate -o RELEASE_NOTES.md   # provider/model from ledger.config.yaml\n  artifacts:\n    paths: [RELEASE_NOTES.md]\n```\n\nSet `OPENAI_API_KEY` (or your provider's key) as a masked CI/CD variable.\n\n### Jenkins\n\n```groovy\nenvironment { OPENAI_API_KEY = credentials('openai-api-key') }\nsteps {\n  sh 'npx --yes @anishhs/ledger generate -o RELEASE_NOTES.md'   // provider/model from ledger.config.yaml\n}\n```\n\n### Any other runner\n\n```bash\ngit fetch --tags --unshallow || true       # ensure history + tags\nexport OPENAI_API_KEY=...                   # or your provider's key\nnpx @anishhs/ledger generate --since-last-tag -o RELEASE_NOTES.md   # config from ledger.config.yaml\n```\n\nCopy-paste templates for each platform live in [`examples/`](./examples) — each reads its\nsettings from a committed `ledger.config.yaml`.\n\n## Exit codes\n\n| Code | Meaning |\n| --- | --- |\n| `0` | Success (also empty range, unless `--fail-on-empty`) |\n| `1` | Runtime/provider error |\n| `2` | Usage or configuration error (missing/unknown provider, bad ref, auth failure) |\n| `3` | No changes found **and** `--fail-on-empty` was set |\n\n## Troubleshooting\n\n| Symptom | Cause & fix |\n| --- | --- |\n| **Empty notes / \"no changes\"**, or a git \"unknown revision\" error on `--from` / `--since-last-tag` | CI did a **shallow clone**, so tags and history aren't present. Fetch them: `actions/checkout@v4` with `fetch-depth: 0`, GitLab `GIT_DEPTH: \"0\"`, or `git fetch --tags --unshallow`. |\n| **`Authentication failed …` (exit 2)** | The provider's key env var isn't set, or is wrong for the selected provider. Check the [Providers & keys](#providers--keys) table — e.g. `anthropic` reads `ANTHROPIC_API_KEY`, not `OPENAI_API_KEY`. |\n| **`Rate limited … (exit 1)`** | `ledger` already retries with backoff and honours the provider's `Retry-After`. If it still exhausts, lower run frequency, raise `maxRetries`, or switch to a higher-tier key. |\n| **Notes look truncated / empty response** | The model hit the token cap — raise `--max-tokens` (models with reasoning on by default, like Claude Sonnet 5, spend part of the budget thinking). |\n| **`Request too large` / HTTP 413** | The prompt **plus the reserved `--max-tokens` output** exceeded the provider's per-request or per-minute token limit — common on free tiers (e.g. Groq's 12k tokens/minute). Narrow the range, lower `--max-tokens`, set a smaller `maxDiffLines`, or use a higher-tier key / larger-limit provider. Run `--dry-run` first — it now prints the **total** tokens requested (prompt + reserved output). |\n| **`openai-compatible` errors about a missing baseURL/model** | That provider needs both a `baseURL` and a `model` — set them in `ledger.config.yaml`, via `LEDGER_BASE_URL` / `--base-url`, or `--model`. |\n| **Model-not-found (404 / invalid model)** | The `model` string must be exactly what your provider expects (e.g. Bedrock model IDs like `openai.gpt-oss-20b-1:0`). Check the provider's model list. |\n\nTip: run with `--dry-run` first — it assembles the context and prints the token estimate without\ncalling the AI (and without spending anything), so you can confirm the range and size are right.\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm run lint\nnpm test          # vitest\nnpm run build     # tsup → dist/cli.js\n```\n\n## Contributing\n\nBug reports and focused PRs are welcome. See\n[CONTRIBUTING.md](./CONTRIBUTING.md) for the dev setup and the gitflow branch/PR workflow.\n\n## Changelog\n\nSee [CHANGELOG.md](./CHANGELOG.md).\n\n## Author\n\n**Anish Shekh**\n&nbsp;·&nbsp; [Website](https://anishhs.com)\n&nbsp;·&nbsp; [GitHub](https://github.com/anishhs-gh)\n&nbsp;·&nbsp; [LinkedIn](https://linkedin.com/in/anishsh)\n\n## License\n\nMIT © [Anish Shekh](https://github.com/anishhs-gh)\n","readmeFilename":"README.md"}