██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ████████╗
██╔══██╗ ██╔════╝ ██╔══██╗ ██╔═══██╗ ╚════██╗ ██╔══██╗ ██╔═══██╗ ██╔════╝ ╚══██╔══╝
██████╔╝ █████╗ ██████╔╝ ██║ ██║ █████╔╝ ██████╔╝ ██║ ██║ ███████╗ ██║
██╔══██╗ ██╔══╝ ██╔═══╝ ██║ ██║ ██╔═══╝ ██╔═══╝ ██║ ██║ ╚════██║ ██║
██║ ██║ ███████╗ ██║ ╚██████╔╝ ███████╗ ██║ ╚██████╔╝ ███████║ ██║
╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
Turn a git diff, tag range, or pull request into a release blog post, changelog, or launch thread — with one AI call.
You shipped the release. Now you have to write it up — again. repo2post reads
what actually changed — commits, file stats, and the real code diff between
two refs (or a GitHub PR) — and drafts the write-up in the format you need: a
blog post, a Keep-a-Changelog entry, GitHub release notes, a launch thread, or a
technical breakdown. Because it sees the diff, the output stays accurate even
when your commit messages are just wip and fix. It is grounded — the
prompt forbids inventing features that aren't in the changes — and
model-agnostic: pass any provider/model string and it routes through the
Vercel AI Gateway.
$ repo2post --from v1.0.0 --to v1.1.0 --style changelog --project envjoy
repo2post changelog from v1.0.0..v1.1.0 (12 commits) via anthropic/claude-sonnet-4.5…
## v1.1.0
### Added
- `--generate` now accepts `--force` to overwrite an existing example (a1b2c3d)
### Fixed
- Inline `#` comments are stripped the way dotenv does (e4f5a6b)
- Duplicate keys now fail `--check` instead of being silently ignored (c7d8e9f)
export AI_GATEWAY_API_KEY=... # or a provider key, e.g. ANTHROPIC_API_KEY
# Default: previous tag → HEAD, as a blog post
npx @vtx-labs/repo2post
# A specific range as a changelog, written to a file
npx @vtx-labs/repo2post --from v1.0.0 --to v1.1.0 -s changelog -o CHANGELOG-1.1.0.md
# A launch thread from a GitHub pull request
npx @vtx-labs/repo2post --pr VTX-Labs/repo2post#1 -s thread
Or add it to a project: pnpm add -D @vtx-labs/repo2post
| Style | Output |
|---|---|
blog |
Narrative release post — headline, intro, themed sections (default) |
changelog |
Keep a Changelog entry — Added / Changed / Fixed / Removed |
release-notes |
GitHub release notes — highlights + what's changed |
thread |
X/Twitter launch thread — numbered, punchy, no hashtag spam |
technical |
Engineer-facing breakdown — what changed, why, migration notes |
repo2post [options]
Source
--from <ref> Start ref, exclusive (default: previous tag, else root)
--to <ref> End ref, inclusive (default: HEAD)
--pr <owner/repo#n> Use a GitHub pull request instead of a local range
-C, --cwd <dir> Repository directory (default: current directory)
--max-commits <n> Cap commits read from the range (default: 200)
Diff (sent by default so output is grounded even with vague commit messages)
--no-diff Send only commit messages + file stats, not the diff
--max-diff-lines <n> Per-file diff line cap (default: 200)
--max-diff-total <n> Total diff line cap (default: 1500)
Output
-s, --style <style> blog | changelog | release-notes | thread | technical
-m, --model <id> provider/model id (default: anthropic/claude-sonnet-4.5)
--project <name> Project name to anchor the writing
--guidance <text> Extra instructions to weave into the prompt
--temperature <n> Sampling temperature 0–2 (default: 0.4)
-o, --out <file> Write to a file instead of stdout
Other
--list-models Print the curated model ids and exit
-h, --help Show help
-v, --version Show version
| Exit code | Meaning |
|---|---|
0 |
Generated successfully |
1 |
No commits in the range — nothing to write |
2 |
Usage error, not a git repo, or a bad ref |
3 |
Generation failed (model / API error) |
4 |
No AI credentials found in the environment |
The generated content goes to stdout; progress and token usage go to
stderr, so you can pipe the result straight into a file or gh:
repo2post -s release-notes | gh release create v1.1.0 --notes-file -
Models are plain provider/model strings. In AI SDK 6 such a string routes
through the Vercel AI Gateway automatically, so a single AI_GATEWAY_API_KEY
unlocks every major provider. A direct provider key (ANTHROPIC_API_KEY,
OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, …) works too.
Run repo2post --list-models for a curated set — but any gateway model id is
accepted.
import { collectChanges, generatePost } from "@vtx-labs/repo2post";
const changes = await collectChanges({ from: "v1.0.0", to: "v1.1.0" });
const post = await generatePost(
{ changes, style: "changelog", project: "my-lib" },
{ model: "anthropic/claude-sonnet-4.5" },
);
console.log(post.content);
| Export | Description |
|---|---|
collectChanges(options?) |
Read a git range + shaped diff (read-only) → ChangeSet |
collectPullRequest(ref, token?) |
Read a GitHub PR (incl. file patches) → ChangeSet |
parsePrRef(input) |
Parse owner/repo#123 or a PR URL |
shapeDiff(patch, options?) |
Skip sensitive/generated/binary files, truncate → ShapedDiff |
classifyPath(path) |
Why a path would be excluded from the diff (or null) |
buildPrompt(input) |
Build the grounded system + user prompt for a change set + style |
generatePost(input, options?) |
Run the AI generation → { content, model, usage, … } |
allStyles() / MODELS |
The available styles and curated models |
Pass includeDiff: false to either collector (or generatePost) for the
messages-only behavior; tune maxLinesPerFile / maxTotalLines to control how
much diff is sent.
generatePost accepts an injected generateText (matching the AI SDK shape),
so you can unit-test the whole pipeline with no network and no API key.
git diff --numstat, and the actual patch between
two refs (all read-only), or fetch the same from a GitHub PR. A PR becomes the
same ChangeSet..env, *.pem,
credentials), generated (lockfiles, dist/, node_modules/, *.min.js),
or binary; cap each remaining file's hunk and the total size, and record
anything omitted so the prompt stays honest about coverage.generateText call to the chosen model, via the AI Gateway.What's sent to the model. Commit messages, file statistics, and the shaped code diff (use
--no-diffto send only messages + stats). repo2post never writes to your repository, and it automatically excludes secret files, lockfiles, generated/build output, and binaries from the diff. It's still your code going to a model provider — review the output, and use--no-difffor closed-source repos where even excerpts shouldn't leave the machine.