<img width="2172" height="724" alt="แแ
ตแบแแ
ฅแธ แแ
ฌแแ
ฉแผ แแ
ขแแ
ฅ" src="https://github.com/user-attachments/assets/7a9b2656-8268-4e12-bf57-fa68d86545d8" />
# ๐ฆ BetterThanYou
[](https://github.com/NomaDamas/BetterThanYou/releases) [](https://github.com/NomaDamas/BetterThanYou/stargazers) [](#-license) [](https://www.rust-lang.org/)
๐ **Read in another language:** [English](README.md) ยท [ํ๊ตญ์ด](README.ko.md) ยท [ไธญๆ](README.zh.md)
> โ๏ธ A CLI-first **face battle** tool. Pit any two faces โ yourself, your friends, or even a human vs a tyrannosaurus ๐ฆ โ and let local heuristics or AI vision models (OpenAI ยท Anthropic ยท Gemini ยท Grok) crown the winner.
>
> ๐ฅ๏ธ Terminal UI ยท ๐ multi-provider VLM judging
```bash
cargo install --git https://github.com/NomaDamas/BetterThanYou && better-than-you
```
Requires Rust toolchain (`brew install rust`). First install takes ~2 minutes; afterwards just run `better-than-you`.
[https://github.com/NomaDamas/BetterThanYou/issues/2#issue-4440509825](https://github.com/user-attachments/assets/ee8fecea-d482-48ba-96ec-1b1b8d3b83d3)
---
## ๐ Table of Contents
- [โจ What is BetterThanYou?](#-what-is-betterthanyou)
- [โก Quick Start](#-quick-start)
- [๐ฆ Install](#-install)
- [๐ฎ Usage](#-usage)
- [๐งฐ Subcommands](#-subcommands)
- [โ๏ธ Judge Modes](#-judge-modes)
- [๐งช How the Heuristic Judge Works](#-how-the-heuristic-judge-works)
- [๐ฏ Scoring Axes](#-scoring-axes)
- [๐ Languages](#-languages)
- [โจ๏ธ TUI Keys](#-tui-keys)
- [๐ Outputs](#-outputs)
- [๐ ๏ธ Development](#-development)
- [๐งน Disk Hygiene](#-disk-hygiene)
- [๐ License](#-license)
---
## โจ What is BetterThanYou?
BetterThanYou is a CLI + TUI **face-battle arena**. Drop two images into it, and it scores each face across **10 aesthetic axes** โ symmetry, bone structure, eye expression, photogenic impact, and friends โ before declaring a winner with a full HTML report.
### โ
Serious uses
- ๐คณ Pick the better of two selfies before posting.
- ๐จ A/B-test AI-generated portraits.
- ๐ฏ Settle "who's more photogenic in this photo" arguments.
### ๐คก Just for fun
- ๐ฆ **Human vs Tyrannosaurus rex.** Whose jawline wins? (Spoiler: the dino crushes `BONE`.)
- ๐ Your dog vs your cat. The vendetta you've been avoiding.
- ๐ง Generated wizard vs your passport photo.
- ๐ธ Frog vs influencer. We don't judge โ that's the tool's job.
The judge can be a **deterministic local heuristic** (no internet, no API key, sub-second) or a **vision-language model** (OpenAI ยท Anthropic ยท Gemini ยท Grok) for nuanced prose verdicts. Reports come out as standalone HTML and JSON, openable in your browser or your phone over LAN.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## โก Quick Start
```bash
cargo install --git https://github.com/NomaDamas/BetterThanYou
better-than-you # ๐๏ธ launch the interactive TUI
better-than-you you.png trex.png # โ๏ธ headless one-shot battle
```
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ฆ Install
### ๐ฆ Cargo
```bash
cargo install --git https://github.com/NomaDamas/BetterThanYou
```
### ๐บ Homebrew
```bash
brew install NomaDamas/better-than-you/better-than-you
```
Tap repo: [`NomaDamas/homebrew-better-than-you`](https://github.com/NomaDamas/homebrew-better-than-you).
### ๐งฐ From source
```bash
git clone https://github.com/NomaDamas/BetterThanYou
cd BetterThanYou
make install # = cargo install --path . (no project-dir pollution)
```
Both `cargo install` and `brew install` build in temp directories and leave no caches inside your local clone. Use `cargo build` only when you intend to iterate on the code (see [๐งน Disk Hygiene](#-disk-hygiene)).
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ฎ Usage
```bash
better-than-you # ๐๏ธ interactive TUI
better-than-you human.png trex.png --judge auto # โ๏ธ one-shot battle
better-than-you battle left.png right.png --judge anthropic
better-than-you open # ๐ผ๏ธ open latest report in your browser
better-than-you serve --port 8080 # ๐ฑ serve reports to your phone over LAN
```
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐งฐ Subcommands
| Command | What it does |
| --- | --- |
| `battle` | โ๏ธ Run a single face battle and write reports. |
| `report` | ๐ Re-render an HTML report from saved battle JSON. |
| `open` | ๐ผ๏ธ Open the latest or specified report in your browser. |
| `serve` | ๐ฑ Serve the reports directory over HTTP on your LAN. |
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## โ๏ธ Judge Modes
| Mode | Behavior |
| --- | --- |
| ๐ค `auto` | Uses the first configured VLM provider, then falls back to `heuristic`. |
| ๐งฎ `heuristic` | Local deterministic image scoring. No network, no API key. |
| ๐ข `openai` | OpenAI vision judging. |
| ๐ฃ `anthropic` | Anthropic Claude vision judging. |
| ๐ต `gemini` | Google Gemini vision judging. |
| โซ `grok` | xAI Grok vision judging. |
Set provider keys with `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `XAI_API_KEY`. The default model is `gpt-5.4-mini`; supported model lists live in [`src/lib.rs`](src/lib.rs) as `OPENAI_VLM_MODELS`, `ANTHROPIC_VLM_MODELS`, `GEMINI_VLM_MODELS`, and `GROK_VLM_MODELS`.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐งช How the Heuristic Judge Works
`--judge heuristic` runs a fully **local, deterministic** image-statistics pipeline. The same pair of images always produces the same scores โ no network, no model, no API key. It samples each face into a **48ร60 grid** of pixel samples (R / G / B + luminance + saturation + center weight) and derives the 10 axis scores from regional metrics:
| Axis | Primary signals (heuristic only) |
| --- | --- |
| โ๏ธ **Facial Symmetry** | Left โ right luminance mirror difference across the whole frame. Lower diff โ higher score. |
| โ **Facial Proportions** | Upper-half vs lower-half mirror balance + how centered the brightest mass is. |
| โจ **Skin Quality** | Cheek/forehead texture variance (smoother โ higher) + saturation uniformity. |
| ๐๏ธ **Eye Expression** | Eye-region (upper 28โ48% of frame) contrast + edge density. |
| โ๏ธ **Hair & Grooming** | Hair-region (top 30%) edge density + saturation consistency. |
| ๐ฆด **Bone Structure** | Jawline-region (lower 60โ90%) edge density + local contrast. |
| ๐ฅ **Expression & Charisma** | Center weight + face warmth (RโB color tilt) + face saturation + dynamic range. |
| ๐ก **Lighting & Color** | Whole-frame dynamic range + luminance/saturation deviation + color spread. |
| ๐ผ๏ธ **Background & Framing** | Center mass strength + background calmness (low outer variance) + edge strength. |
| ๐ฅ **Photogenic Impact** | Composite of center presence + palette mood + dynamic range + symmetry. |
A small per-axis hash signal (deterministic from the image content) adds ~0โ4 points of variation so two images with similar regional statistics don't tie. The result is a stable, fast (sub-second) baseline that runs even without internet access. For nuanced judgement (per-axis prose explanations, identity-specific commentary), use `--judge openai`, `--judge anthropic`, `--judge gemini`, or `--judge grok` with the corresponding API key.
The full source lives in [`src/lib.rs`](src/lib.rs) under `score_portrait`, `compute_mirror_difference`, `region_*` helpers.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ฏ Scoring Axes
| Axis key | Axis | Short | Weight |
| --- | --- | --- | --- |
| `facial_symmetry` | โ๏ธ Facial Symmetry | SYM | 1.0 |
| `facial_proportions` | โ Facial Proportions | RATIO | 1.0 |
| `skin_quality` | โจ Skin Quality | SKIN | 1.0 |
| `eye_expression` | ๐๏ธ Eye Expression | EYES | 1.1 |
| `hair_grooming` | โ๏ธ Hair & Grooming | HAIR | 0.8 |
| `bone_structure` | ๐ฆด Bone Structure | BONE | 0.9 |
| `expression_charisma` | ๐ฅ Expression & Charisma | AURA | 1.2 |
| `lighting_color` | ๐ก Lighting & Color | LIGHT | 1.0 |
| `background_framing` | ๐ผ๏ธ Background & Framing | FRAME | 0.8 |
| `photogenic_impact` | ๐ฅ Photogenic Impact | IMPACT | 1.3 |
๐๏ธ Override weights per run with `--axis-weight KEY=WEIGHT`. Stack a T-Rex's strengths to give it a fair chance:
```bash
better-than-you human.png trex.png \
--axis-weight bone_structure=2.0 \
--axis-weight photogenic_impact=1.5 \
--judge heuristic
```
You can also tune weights interactively under **Settings โ Aesthetic tuning**.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ Languages
๐บ๐ธ English, ๐ฐ๐ท ํ๊ตญ์ด, and ๐ฏ๐ต ๆฅๆฌ่ช are supported. Switch language under **Settings**.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## โจ๏ธ TUI Keys
| Key | Action |
| --- | --- |
| `o` | ๐ผ๏ธ Open report. |
| `q` | ๐ช Quit. |
### cmux drag/drop bridge
iTerm turns Finder drops into pasted file paths automatically. cmux may not
forward Finder drag-and-drop to the terminal pty the same way, so BetterThanYou
enables a runtime clipboard bridge while the battle input screen is open. In a
cmux terminal, copy an image file/path and the empty active side auto-fills; the
bridge stops when you leave the input screen.
BetterThanYou also ships a manual bridge script:
```bash
scripts/better-than-you-cmux-drop ./left.jpg ./right.jpg
```
The bridge sends the first path to the focused cmux surface, presses `Tab`, then
sends the second path. Use it from a macOS Quick Action/Droplet if you want a
Finder-driven workflow in cmux. Native โdrop directly onto the terminal paneโ
still requires cmux itself to translate dropped files into terminal paste input.
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ Outputs
Each battle drops the following into your reports directory:
- ๐ `battle-<ts>.html` โ full standalone report
- ๐งพ `battle-<ts>.json` โ raw scores & narrative for re-rendering
- ๐ `latest-battle.html` / `latest-battle.json` โ pointers to the most recent battle
- ๐ผ๏ธ Share PNG โ ready for SNS
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ ๏ธ Development
```bash
make check # ๐ง cargo check
make build # ๐จ cargo build --release
make run # โถ๏ธ cargo run --release
make clean-cache # ๐งน reclaim disk (target/, node_modules/, old reports)
make size # ๐ show project disk usage
```
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐งน Disk Hygiene
Rust projects accumulate build artifacts in `target/` (often 1+ GB). This repo is wired to keep that **out of the project directory entirely** so it stays small regardless of how often you build:
- ๐ค **End users** install via `brew install` or `cargo install --git` โ both build in temp dirs and leave nothing behind in your filesystem.
- ๐งโ๐ป **Developers using `make`**: every `make build` / `make run` / `make install` automatically sets `CARGO_TARGET_DIR=~/.cache/cargo-target/better-than-you`, so artifacts land in your home cache, not in the project. The project directory stays ~10 MB forever.
- ๐ช **Developers using `cargo` directly**: run this one-time hook so plain `cargo build` / `cargo run` also redirects:
```bash
make install-shell-hook # appends CARGO_TARGET_DIR export to ~/.zshrc
source ~/.zshrc # apply now
```
- ๐๏ธ **Release binary** is shrunk via `Cargo.toml`'s `[profile.release]` (`lto = "thin"`, `strip = "symbols"`, `codegen-units = 1`, `incremental = false`). ~16 MB โ ~10 MB on macOS arm64.
- ๐ฎ **Reclaim disk anytime**:
```bash
make clean-cache # full reclaim: target/, node_modules/, old reports
make clean # just the build cache
make size # see what's eating space
```
<div align="right"><a href="#-table-of-contents">โฌ back to top</a></div>
---
## ๐ License
MIT.