Metadata-Version: 2.4
Name: repo-roast
Version: 0.5.0
Summary: Read a GitHub profile through the GitHub API, then roast its coding habits.
License-Expression: MIT
Project-URL: Homepage, https://github.com/Amayyas/repo-roast
Project-URL: Issues, https://github.com/Amayyas/repo-roast/issues
Project-URL: Source, https://github.com/Amayyas/repo-roast
Keywords: github,cli,developer-tools,roast
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyGithub>=2.3
Requires-Dist: openai>=1.40
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: python-dotenv>=1.0
Requires-Dist: platformdirs>=4.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Dynamic: license-file

# repo-roast

[![CI](https://github.com/Amayyas/repo-roast/actions/workflows/ci.yml/badge.svg)](https://github.com/Amayyas/repo-roast/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Amayyas/repo-roast/badge)](https://scorecard.dev/viewer/?uri=github.com/Amayyas/repo-roast)

A terminal CLI that reads a GitHub profile through the **GitHub REST API** —
repos, language breakdown, stars, abandoned projects, and a sample of recent
commit messages — then asks an LLM to roast the developer's coding habits.

It shows its receipts: every roast is preceded by an evidence table, and the
model is instructed to only joke about facts that are actually in the data.

```
$ repo-roast roast torvalds --spice hot
```

## Install

```bash
pip install -e .
```

## Configure

```bash
cp .env.example .env
```

Then fill in:

| Variable | What it is |
| --- | --- |
| `GITHUB_TOKEN` | A [personal access token](https://github.com/settings/tokens). `public_repo` scope is enough for public data; add `repo` to include your own private repos. |
| `LLM_API_KEY` | A free [Groq](https://console.groq.com/) key (no credit card; starts with `gsk_`). |
| `LLM_BASE_URL` | Defaults to `https://api.groq.com/openai/v1`. |
| `LLM_MODEL` | Defaults to `llama-3.3-70b-versatile`. |

## Usage

```bash
repo-roast roast                        # roast yourself (the authenticated user)
repo-roast roast torvalds               # roast someone else
repo-roast roast torvalds --spice hot   # roast them harder
repo-roast roast torvalds --dry-run     # evidence + the exact prompt, no LLM call
repo-roast compare torvalds gvanrossum  # roast battle: two profiles, one verdict
repo-roast repo psf/requests            # roast a repository, not a person
repo-roast card torvalds -o card.svg    # a shareable SVG card, for a README
repo-roast --help                       # the commands
repo-roast roast --help                 # the flags below
```

> **Breaking change in 0.2.0.** The tool now takes a sub-command: `repo-roast
> torvalds` became `repo-roast roast torvalds`. This made room for `compare`
> and `repo` below, without `compare` being ambiguous with a user who happens
> to be called *compare*. The old form prints the new one rather than a bare
> "No such command".

### Flags

`--spice`, `--model`, `--commits`, `--dry-run` and `--no-cache` mean the same
thing on all four commands; `--evidence` and `--format` apply to every command
except `card`, whose only output is the SVG file itself. `--version` and
`--clear-cache` belong to the top level (`repo-roast --version`, not
`repo-roast roast --version`).

| Flag | Default | Meaning |
| --- | --- | --- |
| `username` (positional, `roast`, `card`) | authenticated user | Which GitHub user to roast. |
| `username_a` / `username_b` (positional, `compare`) | — | The two GitHub users to pit against each other. |
| `full_name` (positional, `repo`) | — | A repository, as `owner/name`. |
| `--spice` / `-s` | `medium` | `mild`, `medium`, or `hot`. |
| `--model` / `-m` | `$LLM_MODEL` or `llama-3.3-70b-versatile` | Model name to send to the provider. |
| `--repos` / `-r` (`roast`, `compare`, `card`) | `5` | How many recently-pushed repos to sample commit messages from. |
| `--prs` (`repo`) | `30` | Recent pull requests to sample. |
| `--issues` (`repo`) | `30` | Recent open issues to sample. |
| `--commits` / `-c` | `8` | Commits to sample (per repository, for `roast`/`compare`/`card`; from the target repo, for `repo`). 1–50. |
| `--evidence` / `--no-evidence` | on | Show the stats table. |
| `--format` / `-f` | `text` | `text`, `json`, or `markdown`. |
| `--output` / `-o` (`card`) | `<login>-card.svg` | Where to write the SVG. |
| `--theme` (`card`) | `dark` | `dark` or `light`. |
| `--dry-run` | off | Gather stats, print the evidence table and the exact prompt, then exit — **no LLM call and no `LLM_API_KEY` required**. |
| `--no-cache` | off | Skip the on-disk cache for this run. |
| `--version` | off | Print the installed version and exit (top level). |
| `--clear-cache` | off | Delete all cached responses, then exit (top level). |

`--dry-run` is the quickest way to check the GitHub half on its own.

`repo` samples pull requests, issues, the repository's file tree, and a code
search for `TODO`/`FIXME` — all bounded, the same way `roast`'s commit sampling
is. It deliberately does not compute mean-time-to-review (would need one extra
API call per sampled PR) or "commits pushed at odd hours" (GitHub normalises
commit timestamps to UTC server-side, so the author's real local hour isn't
recoverable from the API at all — reporting a UTC hour as if it were local
would be presenting a fact that isn't actually in the data).

`card` asks the LLM for one sentence instead of a full roast — there is only
room for one on a badge — then renders it into a self-contained SVG (no
external fonts, no network requests) with the same evidence table any other
command would show. It is XML, and both the display name and the roast
sentence can contain `&`, `<`, `>`, or quotes (a quoted commit message, an
attacker-chosen GitHub display name); every dynamic value is escaped before
it reaches the document, so none of that can break the markup. Drop the file
straight into a README:

```markdown
![repo-roast](card.svg)
```

### Scripting it

`--format json` prints one document to stdout and nothing else:

```bash
repo-roast roast torvalds -f json | jq '.stats.total_stars'
repo-roast roast torvalds -f json --dry-run | jq -r '.prompt.user'
repo-roast compare torvalds gvanrossum -f json | jq -r '.verdict'
repo-roast repo psf/requests -f json | jq -r '.repo.oldest_open_issue_days'
```

Progress spinners and error messages go to **stderr**, so a pipe receives either
a valid document or nothing at all — never half of one. Failures still exit
non-zero, with the message on stderr where it belongs.

`--format markdown` prints the evidence table(s) and the roast or verdict as
Markdown, ready to paste into an issue or a README.

## Provider

The default backend is **Groq**: free, no credit card, and OpenAI-compatible.
`repo-roast` talks to it with the official `openai` SDK pointed at a custom base
URL, so any OpenAI-compatible endpoint works — switching providers is just three
environment variables.

| Provider | `LLM_BASE_URL` | Example `LLM_MODEL` |
| --- | --- | --- |
| Groq (default) | `https://api.groq.com/openai/v1` | `llama-3.3-70b-versatile` |
| Google Gemini | `https://generativelanguage.googleapis.com/v1beta/openai/` | `gemini-2.0-flash` |
| Mistral | `https://api.mistral.ai/v1` | `mistral-small-latest` |
| OpenRouter | `https://openrouter.ai/api/v1` | `meta-llama/llama-3.3-70b-instruct:free` |
| Cerebras | `https://api.cerebras.ai/v1` | `llama-3.3-70b` |

Model strings change over time — if a call 404s, check the provider's current
model list.

## Running it with Docker

No local Python needed. Images are published to the GitHub Container Registry
on every release, for `linux/amd64` and `linux/arm64`:

```bash
docker run --rm -e GITHUB_TOKEN=ghp_... -e LLM_API_KEY=gsk_... \
  ghcr.io/amayyas/repo-roast roast torvalds --spice hot
```

Environment variables, not a mounted `.env`: simpler, and there is no file to
accidentally bake into a container. `LLM_BASE_URL` and `LLM_MODEL` work the
same way if you are pointing at a different provider.

Tags: `latest` tracks the newest release, `vX.Y.Z` pins a specific one — same
versions as PyPI.

## How it stays polite to the API

Repo metadata (languages, stars, descriptions, push dates) comes from the single
repo listing that PyGithub already paginates. The **only** per-repo calls are for
commit messages, and they are bounded on both axes: the `--repos` most recently
pushed originals, up to 8 commits each.

**Every run is cached on disk for 15 minutes**, keyed on the target and the
sampling flags — `roast torvalds --repos 5` and `roast torvalds --repos 10` are
different cache entries, since they're genuinely different data. Re-running the
same command while iterating on `--spice` or `--format` costs no API calls at
all. Two exceptions: `--no-cache` skips the cache for one run, and roasting
yourself (`roast` with no username) is never cached — the cache key has no way
to tell two different tokens' "yourself" apart, and handing one token's profile
back to another would be a real bug. The cache never stores a token, only
GitHub's public responses. `repo-roast --clear-cache` deletes it.

**Before spending any quota, repo-roast checks how much is left** — checking
itself costs nothing. Below a safety margin it refuses to start, with a clear
message instead of burning through what little remains and failing partway
through with a raw 403.

## Ethical use

repo-roast generates jokes about **named, real people** from their public
GitHub activity. That comes with rules, not just a disclaimer:

- Roast the code and the habits — commit hygiene, abandoned repos, a
  suspicious `TODO` — never the person. No appearance, no identity, no
  protected characteristic. The system prompt enforces this on every call, and
  it's the first hard rule in `roast.py`.
- Don't use the output to harass, dogpile, or target someone who didn't ask
  for it. A roast run against a stranger without their knowledge is not the
  friendly-jab use case this tool is built for. `card` raises the stakes here:
  the SVG is meant to be embedded somewhere public, so only put one in your
  own README, about yourself.
- This isn't only a prompt-level promise. [SECURITY.md](SECURITY.md#prompt-injection-the-threat-this-tool-is-actually-exposed-to)
  documents the structural defense that keeps a booby-trapped repo from
  turning the tool into a weapon against whoever is being roasted.

`repo-roast repo` sidesteps the "named person" concern differently: it targets
a codebase's pull requests, issues, and commits, not an individual. The data it
hands to the model never includes a contributor's name, so the model has
nothing to single anyone out with in the first place.

This project follows a [Code of Conduct](CODE_OF_CONDUCT.md); the same spirit
applies to how the tool itself gets used.

## Support

Questions, bugs, or a roast that missed? See [SUPPORT.md](SUPPORT.md), or
reach out directly at **amayyas.aouadene@epitech.eu**.

Found a security issue? See [SECURITY.md](SECURITY.md) instead of opening a
public issue.

Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md). This project
follows a [Code of Conduct](CODE_OF_CONDUCT.md).

## License

[MIT](LICENSE) © Amayyas Aouadene
