Quickstart
L80 is a small CLI that publishes a benchmark or a set of findings as a plain, shareable page. You (or your agent) write JSON. The server validates it and renders it from a template it controls. Three steps to the first page: install, save a token, publish.
Install
$ curl -fsSL https://launch80.com/install.sh | shmacOS and Linux, Intel or ARM. The script verifies a checksum and writes one binary to ~/.local/bin. If that directory is not on your PATH, the script prints the line to add. The whole script is on one page if you want to read it first.
Other install locations, and updating
Pass --prefix to the script to install somewhere other than ~/.local/bin. The script also links a lowercase l80 beside the binary when nothing else is there.
$ L80 updateGet a publish token
Tokens are issued by hand right now. Ask in Discord and you will usually have one the same day. Nothing publishes without it. When it arrives, save it to disk:
$ mkdir -p ~/.config/L80 && umask 077 && printf '{"token":"%s"}' '<TOKEN>' > ~/.config/L80/credentials.json$ L80 doctorUse an environment variable instead of the file
L80_TOKEN takes precedence over the credentials file. Useful in CI or a container. It is still a secret: set it in the environment, not in a script you commit.
Publish
One command, depending on what you have. Each prints a URL on its last line.
$ L80 betterbench --endpoint http://localhost:8080/v1 --model qwen3-32b$ L80 betterbench --results results.json$ L80 publish report.json --template generic.report.v1A full BetterBench run takes a while: 20 passes per category plus prefill and concurrency sweeps. --quick is a 5-pass smoke run, and the page says so. The results file stays in the working directory. The endpoint URL and hostname are stripped before upload and the server refuses a file that still carries them.
Preview before publishing
--dry-run validates locally and reports without publishing. For L80 betterbench, add --payload-out p.json to write the exact payload that would be sent.
$ L80 betterbench --results results.json --dry-run --payload-out payload.json$ L80 publish report.json --dry-runNaming the template
--template on the command line and "$template" in the JSON both work. Use both and they must agree, or the command stops with E_TEMPLATE_CONFLICT rather than guessing. L80 betterbench defaults to bench.betterbench.v1; pass --template bench.report.v1 for the summary layout from the same file.
BetterBench
L80 betterbench either runs BetterBench against an endpoint or reads a results.json you already have, then publishes the result. Every flag it accepts is below. Anything after -- is passed to betterbench run untouched.
--title and --summary set the heading and the paragraph under it. --section "Heading=Body" adds a prose section and repeats. --note key=value records a fact about the run and shows it as a chip, for example --note engine="vLLM 0.9.3". It also repeats.
| flag | does |
|---|---|
| --endpoint <url> | OpenAI-compatible base URL to benchmark, e.g. http://host:8080/v1. |
| --model <name> | Model name to request. |
| --results <file> | Publish this existing results.json instead of running. |
| --quick | Short smoke run (BetterBench --quick). The page says so. |
| --passes N | Measured passes per category. Default is BetterBench's 20. |
| --warmup N | Warmup passes per category. Default is BetterBench's. |
| --decode, --prefill, --concurrency | Run only the named phases. Combinable. |
| --no-prefill, --no-concurrency | Skip a sweep. |
| --out <path> | Where results.json is written. Default ./betterbench-<model>-<time>.json. |
| --template <id> | bench.betterbench.v1 (default) or bench.report.v1. |
| --title, --summary | Page heading and the paragraph under it. |
| --section "H=Body" | Extra prose section. Repeatable. |
| --note k=v | Fact about the run, shown as a chip. Repeatable. |
| --engine, --hardware | Labels. bench.report.v1 only. |
| --dry-run | Run or read, prepare the payload, do not publish. |
| --payload-out <path> | Also write the exact payload to this path. |
| --json | Machine-readable output. |
| --api-base <url> | Server override, same as L80_API_BASE. |
| -- <args> | Everything after -- goes to betterbench run verbatim. |
From a coding agent
Everything above except the token is a shell command, so an agent can do it. Pick the variant that matches what you have, paste it into Claude Code, Cursor, or whatever runs your terminal, and come back for the link. The prompt tells the agent to stop and ask you if the token is missing.
Set up the L80 CLI so you can publish results to launch80, then publish a report. 1. Install it: curl -fsSL https://launch80.com/install.sh | sh If ~/.local/bin is not on PATH, add it, then confirm `L80 version` works. 2. If you are Claude Code, install the skill: L80 skills link --target claude-code Any other agent: run `L80 skills print` and follow that file instead. 3. Run `L80 auth status`. If it reports no token, STOP and tell me: I will save one to ~/.config/L80/credentials.json myself. Never ask me to paste a token into this chat, and never write one to a file for me. 4. Run `L80 doctor` and fix anything it flags. 5. Benchmark my server and publish the run: L80 betterbench --endpoint <URL> --model <MODEL> Ask me for the endpoint URL and model name if I have not given them. Use --quick only if I ask for a smoke test. 6. Give me the URL from the `published:` line and repeat every caveat the tool printed, especially anything about under-sampled percentiles.
Install the skill into Claude Code (optional)
The skill is a short markdown file telling an agent which template to pick, what the JSON looks like, and how to read an error. The CLI works without it; it only makes the agent better at the first try. Claude Code is the only linked target today.
$ L80 skills link --target claude-codeFor any other agent, print it and paste it where that tool reads instructions from:
$ L80 skills print--force replaces an existing installation. --dev --from ./skills symlinks a checkout of the skills repo so edits are live. The full text of the skill is on the verify page.
Templates
Three layouts. The choice matters because the wrong one loses information rather than just looking plainer. Each id links to a real published example.
| template | use it for | say to your agent |
|---|---|---|
| bench.betterbench.v1 | A BetterBench run or its results.json. Produced only by L80 betterbench. Every chart and table is derived on the server from the raw samples, so the page matches the HTML report BetterBench writes beside the file. | “run betterbench against my server and publish it to launch80” |
| bench.report.v1 | Per-category percentile data from any other harness. Tables with p50 and p99 side by side and under-sampled percentiles marked. | “publish this benchmark run to launch80” |
| generic.report.v1 | Everything else: findings, status, test results. Title, summary, a row of metrics, prose sections. | “publish these findings to launch80” |
Payload rules that apply to every template
- Plain text only. No links, images, or markup anywhere in a payload.
- Unknown fields are rejected, not dropped, so a page never diverges from what you sent.
- Metric values are strings. You format the number; the renderer never touches it.
L80 templates listshows the ids the server accepts, and the example pages each show a complete valid payload.
What a published page is
- Public. Anyone with the link can read it.
- Immutable. To change something, publish again and share the new link.
- Unindexed. Search engines are told to stay out. Sharing the link is unaffected.
- Self-contained. No scripts, fonts, or trackers load.
- Labelled. Every page carries a visible “self-reported, not verified by launch80” badge.
- Kept 5 days on the free tier, then removed. Republish to keep a result around.
L80 history lists the URLs your key has published, from any machine. With no token or no network it falls back to the local record on this machine.
Commands
| command | does |
|---|---|
| L80 betterbench --endpoint <url> --model <m> | Run BetterBench against an OpenAI-compatible server and publish the run. |
| L80 betterbench --results <results.json> | Publish an existing BetterBench results file. |
| L80 publish <file.json> [--template <id>] | Publish a hand-written payload. Prints the URL. |
| L80 templates list | The template ids the server accepts right now. |
| L80 history | The share URLs this key has published, newest first. |
| L80 auth status | Where the CLI is looking for a token, masked. |
| L80 doctor | Check connectivity, config, and credential in one shot. |
| L80 skills link --target claude-code | Install the agent skill into Claude Code. Optional. |
| L80 skills print | Print the agent skill to stdout, for any other agent. |
| L80 update | Install the latest release in place. |
| L80 version | Print the version. |
Every network-facing command accepts --json for machine-readable output and --api-base to override the server. Flags may come before or after the file argument. The betterbench flags have their own section.
Flags for publish, history, skills link, and update
| flag | does |
|---|---|
| publish --template <id> | Template id. Or put "$template" in the JSON. |
| publish --dry-run | Validate locally and report, without publishing. |
| history --limit N | How many to list, newest first. Default 50, max 200. |
| history --all | Keep paging until the whole history is listed. |
| history --local | Read only the local record of publishes from this machine. |
| skills link --force | Replace an existing installation. |
| skills link --dev [--from <dir>] | Symlink a source checkout so edits are live. |
| skills link --name <skill> | Which bundled skill. Default l80-test-report. |
| skills print [name] | Which bundled skill to print. Default l80-test-report. |
| update --check | Report whether an update is available without installing. |
Environment and files
Resolution order for the server is flag, then variable, then default. For the token it is variable, then file.
| variable | does |
|---|---|
| L80_TOKEN | Publish token. Overrides the credentials file. |
| L80_API_BASE | Server to publish to. Default https://launch80.com. The --api-base flag overrides it. |
| L80_BETTERBENCH | Path to a betterbench binary. Otherwise the one on PATH is used, or uvx bootstraps it from GitHub. |
| XDG_CONFIG_HOME | Moves the config directory. Default ~/.config. |
| CLAUDE_CONFIG_DIR | Where skills link installs. Default ~/.claude. |
| file | what it is |
|---|---|
| ~/.config/L80/credentials.json | {"token": "..."}. Create it at mode 0600. |
| ~/.config/L80/history.jsonl | Local record of every publish made from this machine. Read by L80 history --local. |
| ~/.claude/skills/l80-test-report/ | The agent skill, after skills link. |
| ./betterbench-<model>-<time>.json | The results file a run writes. --out chooses another path. |
Limits
| what | limit |
|---|---|
| Payload, report templates | 64 KB |
| Payload, bench.betterbench.v1 | 4 MB |
| Metrics | 12 |
| Categories | 12 |
| Sections | 10 |
| Section body | 4,000 characters |
| Publishes per key, free tier | 10 per rolling day |
| Page retention, free tier | 5 days |
Every token is on the free tier today. A paid tier with a higher daily limit and longer retention is coming; the numbers will be posted here and in Discord when it opens. If you need more before then, ask in Discord.
Errors
Every error prints a code, a sentence, and a remedy naming the next action, and the process exits with a code an agent can branch on.
| code | exit | meaning |
|---|---|---|
| E_TOKEN_MISSING | 5 | No credential found. L80 auth status shows where the CLI is looking. |
| E_UNAUTHORIZED | 5 | The token is wrong or was rotated. Ask in Discord for a new one. |
| E_SCHEMA_INVALID | 4 | Each listed path is the exact field to fix. |
| E_JSON_INVALID | 4 | The file is not valid JSON. |
| E_PAYLOAD_TOO_LARGE | 9 | Over the template limit. Shorten and publish again. |
| E_TEMPLATE_UNKNOWN | 10 | No template with that id. L80 templates list shows the current ones. |
| E_TEMPLATE_CONFLICT | 2 | --template and the payload's $template disagree. Pick one. |
| E_QUOTA_EXCEEDED | 6 | Daily limit for this key. Retry-After says when the window reopens. Do not retry in a loop. |
| E_RATE_LIMITED | 6 | Too many requests in a short burst from one address. Wait a moment. |
| E_NETWORK | 7 | Could not reach the server. L80 doctor tests the connection. |
| E_INPUT_INVALID | 3 | A local input problem: missing file, unreadable results.json, bad flag value. |
| E_USAGE | 2 | Wrong command or flags. L80 help lists them. |
Exit codes
| exit | meaning |
|---|---|
| 0 | success |
| 1 | internal error |
| 2 | usage |
| 3 | local input invalid |
| 4 | payload failed validation |
| 5 | authentication |
| 6 | rate limited or over quota |
| 7 | network |
| 8 | server error |
| 9 | payload too large |
| 10 | unknown template |
Help
Tokens, questions, and new template proposals all go through Discord. The lab notes cover the reasoning behind the templates.