Automatically generate beautiful, AI-powered changelogs for your GitHub repositories. Supports multiple LLM providers and notification channels.
- Cinematic landing page + dark-branded login: OAuth flow returns straight into the app (
/login) — no page flashes, CSRF-protected with OAuthstate - Multiple LLM Providers: OpenAI, Anthropic, Groq, OpenRouter, Ollama (local), or commit-based mode (free, no API key)
- GitHub Integration: OAuth login, sync repos, auto-create webhooks for tag-based changelogs
- Selective Repo Management: Choose which repos to enable changelogs for — the rest stay synced but inactive
- Public Changelog Pages: Opt-in vanity URLs at
/owner/repo— share one link, no login required - Email Subscribers: Double opt-in subscribe box on public pages; releases are sent through your own SMTP/SendGrid config (no platform-wide sender)
- Embeddable Widget: Drop a single
<script>tag on any site/portfolio to render a repo's latest releases - Manual Range Trigger: Generate a changelog for an explicit
From → Toref range (validated before queueing) - Notifications: Deliver changelogs via SMTP email, SendGrid, or Slack webhooks
- Dashboard: Overview of repos, changelog history, and generation status with live polling (pauses when the tab is hidden)
- Background Processing: Changelog generation runs asynchronously — no waiting
- Reliable Webhooks: Signature-verified and idempotent — GitHub delivery retries never duplicate changelog generations; repos resolve by GitHub's numeric ID and fan out to every user who registered them
- Session Hardening: JWTs validated on app load; expired sessions redirect cleanly back to login
- Fully Responsive: Mobile bottom navigation, thumb-sized tap targets, and wrapping markdown so changelogs read well on any screen
- CI: GitHub Actions runs backend tests + frontend typecheck/build on every push and PR
| Layer | Tech |
|---|---|
| Backend | Python 3.12+ / FastAPI / SQLAlchemy (async) |
| Database | SQLite (dev) / PostgreSQL (prod) |
| Frontend | React + Vite + Tailwind (source in frontend/src/, built output in frontend/dist/) |
| Auth | GitHub OAuth + JWT |
- Python 3.12+
- Node.js (only if rebuilding the frontend — the built version is included)
git clone <repo-url>
cd auto-changelog
# Backend virtual environment
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Environment config
cp .env.example .env- Go to https://github.com/settings/developers
- Click "New OAuth App"
- Set Authorization callback URL to
http://localhost:8003/login(for a deployed server, usehttp(s)://YOUR_SERVER:8003/login— the callback must match the origin the app is served from) - Copy
Client IDandClient Secretintobackend/.env:GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret
cd backend
./venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8003Open http://localhost:8003 in your browser.
- Click "Sync from GitHub"
- Select repos you want to enable → Click "Import Selected"
- If you don't configure an LLM provider, the commit-based mode (free) is used automatically — it parses conventional commit messages
- Click a repo name → "Generate Now" for the default incremental range, or "Range…" and enter explicit
From/Torefs (e.g.v1.0.0→v1.1.0)
Share a repo's release notes with anyone — no account needed.
- Open a repo's detail page (Repositories → click a repo).
- In the "Public changelog page" card, click Enable.
- Share the shown link, e.g.
https://YOUR-SERVER/owner/repo.
Details:
- Opt-in, default OFF — registering a repo never publishes it; Disable hides the page again (it returns 404, not 403, so unpublished pages don't leak their existence).
- Only completed changelogs are served, through a dedicated schema: no IDs, statuses, error messages, or notification state ever leave the server.
- Works for private repos too — the page is explicit opt-in — but everything on an enabled page is world-readable.
When a public page is enabled, it shows a "Get release notes by email" box:
- Double opt-in: subscribing sends a confirmation email; nothing is mailed until the link is clicked. Separate tokens protect confirm vs. unsubscribe links.
- Sends via your own config: confirmation and release emails go through your active SMTP/SendGrid config (Notifications tab) — there is no platform-wide sender. If no email-capable config exists, subscribing returns a clear 400 and fan-out is recorded as
skipped(Slack-only configs can't address email). - Links in those emails use
PUBLIC_BASE_URL, falling back toWEBHOOK_BASE_URL.
Paste onto any site (portfolio, blog, docs) — one tag per repo:
<script src="https://YOUR-SERVER/widget.js" data-repo="owner/repo" async></script>- Renders the repo's latest releases (default 5 — override with
data-limit="10", max 50) inside a shadow DOM, so your site's CSS and the widget's styles can't interfere with each other. - Read-only: it fetches the public endpoint (open CORS on
/api/public/*only) and links to your vanity page for "View all & subscribe". - Requires the repo's public page to be enabled; otherwise it shows "Changelog unavailable".
- The repo detail page shows the ready-made snippet with a Copy button next to the public-page card.
cd backend
source venv/bin/activate
pytest tests/ -vThe built app ships in frontend/dist/. Only rebuild if you change frontend/src/ or frontend/public/:
cd frontend
npm install
npm run build # tsc + vite build → outputs to frontend/dist/Configured through the UI at LLM Config tab. Add one or more providers:
| Provider | Default Model | Requires API Key |
|---|---|---|
| Commit-based (free) | — | No |
| OpenAI | gpt-4o | Yes |
| Anthropic | claude-sonnet-4-20250514 | Yes |
| Groq | openai/gpt-oss-120b | Yes |
| OpenRouter | anthropic/claude-3.5-sonnet | Yes |
| Ollama | llama3.3 (localhost:11434) | No |
Configured through the Notifications tab. Options:
- SMTP — Any SMTP server (Gmail, Outlook, etc.)
- SendGrid — SendGrid email API
- Slack — Webhook URL + channel
When you enable a repo, the backend creates a webhook pointing to:
http://YOUR_SERVER_IP:8003/api/webhook
Changelog generation is queued automatically on:
- Tag creation (
createevent) — one changelog per tag - Release published (
releaseevent) - Push to the default branch — keyed by the HEAD commit SHA
Deliveries are HMAC-SHA256 signature-verified (set GITHUB_WEBHOOK_SECRET) and idempotent — if a changelog for the same tag is already pending, processing, or completed, retried deliveries are ignored instead of generating duplicates (failed ones re-queue automatically).
| Variable | Required | Description |
|---|---|---|
DEBUG |
No | Enable debug mode (true/false) |
SECRET_KEY |
Yes (prod) | JWT signing key |
DATABASE_URL |
No | Default: SQLite (sqlite+aiosqlite:///./data/app.db) |
GITHUB_CLIENT_ID |
Yes | GitHub OAuth App client ID |
GITHUB_CLIENT_SECRET |
Yes | GitHub OAuth App client secret |
GITHUB_WEBHOOK_SECRET |
No | Secret for webhook HMAC verification |
JWT_EXPIRE_MINUTES |
No | Token expiry (default: 1440 = 24h) |
ENCRYPTION_KEY |
No | Fernet key for API key encryption at rest |
CORS_ORIGINS |
No | Comma-separated allowed origins |
WEBHOOK_BASE_URL |
No | Public URL for GitHub webhook callbacks (e.g. http://YOUR_IP:8003) |
PUBLIC_BASE_URL |
No | Origin for confirm/unsubscribe links in subscriber emails (falls back to WEBHOOK_BASE_URL) |
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/github |
Exchange OAuth code for JWT |
| GET | /api/auth/me |
Get current user profile |
| Method | Path | Description |
|---|---|---|
| GET | /api/repos/ |
List imported repos |
| POST | /api/repos/sync |
Save all GitHub repos to DB (inactive) |
| POST | /api/repos/preview |
Preview all GitHub repos (no save) |
| POST | /api/repos/import |
Import selected repos as active |
| PATCH | /api/repos/{id}/toggle |
Toggle repo active state |
| PATCH | /api/repos/{id}/public |
Enable/disable the public vanity page |
| DELETE | /api/repos/{id} |
Remove a repo |
| Method | Path | Description |
|---|---|---|
| GET | /api/changelogs/configs |
List LLM configs |
| POST | /api/changelogs/configs |
Create LLM config |
| PATCH | /api/changelogs/configs/{id} |
Update LLM config |
| DELETE | /api/changelogs/configs/{id} |
Delete LLM config |
| POST | /api/changelogs/generate |
Trigger generation (optional from_tag/to_tag — both or neither) |
| GET | /api/changelogs/ |
List changelogs (paginated; includes total) |
| GET | /api/changelogs/stats/summary |
Full totals for dashboards (ignores pagination) |
| GET | /api/changelogs/{id} |
Get changelog detail |
| POST | /api/changelogs/{id}/publish-release |
Publish changelog as a GitHub Release |
| Method | Path | Description |
|---|---|---|
| GET | /api/notify/configs |
List notification configs |
| POST | /api/notify/configs |
Create notification config |
| PATCH | /api/notify/configs/{id} |
Update notification config |
| DELETE | /api/notify/configs/{id} |
Delete notification config |
| Method | Path | Description |
|---|---|---|
| POST | /api/webhook |
Receive GitHub webhook events |
| Method | Path | Description |
|---|---|---|
| GET | /api/public/{owner}/{repo} |
Public changelog page data (opt-in repos, completed only) |
| POST | /api/public/{owner}/{repo}/subscribe |
Start double opt-in subscription |
| GET | /api/public/confirm/{token} |
Confirm a subscription |
| GET | /api/public/unsubscribe/{token} |
Unsubscribe |
| GET | /widget.js |
Embeddable widget script |
auto-changelog/
├── backend/
│ ├── app/
│ │ ├── api/ # Route handlers (auth, repos, changelogs, etc.)
│ │ ├── core/ # Dependencies, security, errors
│ │ ├── models/ # SQLAlchemy ORM models
│ │ ├── schemas/ # Pydantic request/response schemas
│ │ ├── services/ # Business logic (auth, github, git_ops, llm, notify)
│ │ ├── tasks/ # Background worker
│ │ ├── config.py # Settings from env
│ │ ├── database.py # SQLAlchemy engine + session
│ │ └── main.py # FastAPI app
│ ├── data/ # SQLite DB + cloned repos
│ ├── tests/
│ ├── .env.example
│ └── requirements.txt
├── frontend/
│ ├── src/ # React source (pages, components, lib)
│ ├── public/
│ │ ├── landing.html # Cinematic landing page (copied to dist on build)
│ │ └── widget.js # Embeddable changelog widget (copied to dist on build)
│ ├── dist/ # Built React app (served by backend)
│ │ ├── assets/
│ │ ├── index.html
│ │ ├── landing.html
│ │ └── widget.js
│ ├── index.html # Source HTML (for rebuilding)
│ └── package.json
├── .github/workflows/ # CI: backend pytest + frontend build on push/PR
├── .gitignore
└── README.md
MIT