# Submit this project to releaseszn

You are helping the author of this repository publish a release on
releaseszn.com, a showcase of software releases. Use the REST API at
https://releaseszn.com/api/v1 (OpenAPI spec: /api/v1/openapi.json).

The API token is in the file ~/.config/releaseszn/auth-header, as a
ready-made header line (`Authorization: Bearer rsz_…`). Let curl read the
file itself, so the token never passes through the shell or your output:

```bash
curl -sS -H @$HOME/.config/releaseszn/auth-header https://releaseszn.com/api/v1/me
```

(`$HOME`, not `~`: the shell doesn't expand `~` after `@`. Any curl since
7.55 reads headers from a file.) If the author set the environment variable
RELEASESZN_TOKEN instead, send `-H "Authorization: Bearer $RELEASESZN_TOKEN"`.
Never print the token or display the file, and never copy it anywhere or
commit it. Publishing needs the `submit` scope (a device sign-in gives
exactly that; `read` isn't needed).

If the author pastes a token into the chat anyway, don't repeat it: save it
straight to the file,
`(mkdir -p ~/.config/releaseszn && umask 077 && printf 'Authorization: Bearer %s\n' 'rsz_…' > ~/.config/releaseszn/auth-header)`,
and ask them to revoke it at https://releaseszn.com/settings/tokens once
you're done: it is now in the conversation, and revoking it is the only real
fix.

If there is only an older ~/.config/releaseszn/token file (the token alone),
turn it into the header file once, printing nothing:

```bash
python3 -c 'import os; d = os.path.expanduser("~/.config/releaseszn"); os.umask(0o077); t = open(f"{d}/token").read().strip(); open(f"{d}/auth-header", "w").write(f"Authorization: Bearer {t}\n"); os.remove(f"{d}/token")'
```

If there is no token at all, get one by device sign-in, and never ask the
author to paste one into the chat:

1. Start it. In `client_name`, replace `<agent>` with your own name and
   `<project>` with the project's (for example "Codex in ripfast"). The
   answer goes to a private file, and only what the author needs is shown:

   ```bash
   mkdir -p ~/.config/releaseszn && (umask 077 && curl -sS -X POST https://releaseszn.com/api/v1/auth/device -H 'Content-Type: application/json' -d '{"client_name": "<agent> in <project>"}' -o ~/.config/releaseszn/device.json) && python3 -c 'import json, os; d = json.load(open(os.path.expanduser("~/.config/releaseszn/device.json"))); print(d.get("verification_uri_complete"), d.get("user_code"), d.get("fix", ""))'
   ```

2. Tell the author to open that link (or https://releaseszn.com/device and
   type the code), sign in with GitHub, check that the program named is
   you, and approve. Never show `device_code`. The token lets you add,
   change and delete their projects and publish releases, not delete
   their account: if they ever want that, they do it at
   https://releaseszn.com/settings themselves.

3. Wait for the approval with this command: it polls every `interval`
   seconds and saves the token straight to ~/.config/releaseszn/auth-header,
   printing only the outcome. If it says it is still waiting, run it
   again; the codes last 10 minutes.

   ```bash
   python3 - <<'EOF'
   import json, os, time, urllib.error, urllib.request
   home = os.path.expanduser("~/.config/releaseszn")
   start = json.load(open(f"{home}/device.json"))
   wait, deadline = start["interval"], time.time() + 100
   while time.time() < deadline:
       time.sleep(wait)
       request = urllib.request.Request(
           "https://releaseszn.com/api/v1/auth/device/token",
           data=json.dumps({"device_code": start["device_code"]}).encode(),
           headers={"Content-Type": "application/json"})
       try:
           answer = json.load(urllib.request.urlopen(request, timeout=30))
       except urllib.error.HTTPError as error:
           answer = json.load(error)
       if "token" in answer:
           os.umask(0o077)
           with open(f"{home}/auth-header", "w") as file:
               file.write(f"Authorization: Bearer {answer['token']}\n")
           os.remove(f"{home}/device.json")
           print(f"Signed in as @{answer['handle']}; token saved to {home}/auth-header")
           break
       if answer.get("code") == "slow_down":
           wait += 5
       elif answer.get("code") != "authorization_pending":
           print(answer.get("code"), answer.get("fix"))
           break
   else:
       print("Still waiting for the author: run this again.")
   EOF
   ```

Then send it with `-H @$HOME/.config/releaseszn/auth-header` on every
request, as above.

Check the token with GET /me before anything else. Its `github.status` says
whether GitHub still accepts the author's sign-in on the website, which
POST /projects needs: if it is `renew_required`, stop and ask the author to
open `github.renew_url` in a browser and sign in with GitHub (even if they
are already signed in), then call GET /me again until it no longer says
`renew_required`. A new API token won't change it. If the author prefers, they can instead create a token at
https://releaseszn.com/settings/tokens and save it with the command in
https://releaseszn.com/docs/quickstart.

Steps:

01. Read the README and recent git history to understand the project.
    Find the GitHub repo URL from `git remote -v`.

02. GET /tags and GET /rules to learn the valid tags and the current limits.

03. POST /projects with the repo_url, a one-line `summary` (max 100 chars,
    plain, no hype or emoji), and tags. Include exactly one `ai/*` tag
    that honestly describes how much of the code was AI-written; ask the
    author if you are unsure.
    Poll GET /projects/{slug} until import_status is "done".
    The import copies the project's `description` from the GitHub "About"
    text, which is often stale or empty. Compare it with the README: if it
    doesn't describe the project as it is now, write a better one (markdown,
    a short paragraph or two, limit in GET /rules) and PATCH /projects/{slug}
    with `description`.

04. Pick the release to publish: the newest one with status "draft".
    Releases come from GitHub releases or, in a repository without any,
    from its version tags; a tag is enough. If there is no draft, tell the
    author to push a version tag (or create a GitHub release), then
    POST /projects/{slug}/sync.
    Its `kind` is already decided: only `initial`, `major` and `minor`
    releases are ranked, and a patch is never ranked. Read it before
    suggesting a new tag just to be ranked; /docs/ranking explains the rule
    (each release is compared with the previous final release of its own
    tag series).

05. Changelog: if the release's changelog is empty or just "bump", write a
    real one from the commits since the previous tag. Describe what changed
    and why it matters to users. Do not invent features. PATCH it.

06. Hero screenshot (required, quality-checked): every release needs exactly
    one media item with role "hero". It must be a real screenshot of the
    software (PNG, JPEG or WebP, still image), at least 1200x630, with an
    aspect ratio between 4:5 and 1.91:1 (16:9 or 16:10 is ideal). It must be
    sharp, not mostly empty, without big empty borders, and different from
    the previous release's hero. GET /rules has the exact limits. When you
    take one yourself, size the window to the content: a browser window
    much wider than the page leaves empty strips that fail the border
    check.
    For a command-line tool without a screenshot, a terminal recording
    (asciinema .cast) may be the hero instead: its poster frame is checked
    the same way.
    Look for one before asking the author. Check the images the README
    embeds (including ones hosted outside the repo, such as
    github.com/user-attachments or a docs site), then the image files in
    the repo: `git ls-files` filtered by image extension lists them, and
    folders like assets/, docs/, images/, screenshots/, media/ and .github/
    are the usual places. Open the candidates and pick the one that best
    shows the software in use and meets the requirements above; logos,
    icons, banners, badges and diagrams are not screenshots. Import it with
    `source_url` (for a file in the repo, its raw.githubusercontent.com
    URL). Only if nothing suitable exists, ask the author to take one, and
    say what you looked at and why it fell short. Do not fabricate, mock
    up, upscale or AI-generate screenshots.
    Media is added with POST /projects/{slug}/media. `release_tag` attaches
    it to the release (without it, it goes to the project's gallery):

    ```json
    {"source_url": "https://raw.githubusercontent.com/OWNER/REPO/HEAD/docs/screenshot.png",
     "release_tag": "v1.2.0", "role": "hero",
     "alt_text": "The search results view, with matches highlighted in three files"}
    ```

    For a local file that isn't pushed, send `filename`, `content_type` and
    `bytes` instead of `source_url` (with the same `release_tag`, `role` and
    `alt_text`), PUT the file to the returned `upload.url` with
    `upload.headers`, then POST /media/{id}/complete.
    Optional: add more screenshots, or a GIF or an asciinema terminal
    recording (a .cast file, content_type application/x-asciicast) of the
    software in action, with role "carousel" (neither is ever the hero; videos aren't supported yet).
    Always write meaningful alt_text that describes what the image shows.
    Poll GET /media/{id} until status is "ready" or "failed" and follow its
    `next` field: if hero_ok is false, follow each failed check's `fix` and
    add a better image as the hero (it replaces the previous one).

07. LLM models (optional, ask first): releaseszn can show which LLMs helped
    build this release. Ask the author whether they want to share that.
    Only if they agree, PATCH the release with `models`, a list of
    `{"provider": "...", "model": "..."}` (at most 5). Include yourself,
    using your exact model id if you know it (for example
    `{"provider": "anthropic", "model": "claude-opus-5-5"}`), plus any other
    models the author names. `provider` comes from a fixed list (GET /rules,
    `llm_providers`); use "other" for anything not on it. Never guess a
    model you are unsure of. If the author declines, skip this step: it is
    optional and never affects ranking.

08. POST …/releases/{tag}/check. Fix every gate that has a `fix`, then check
    again. If a gate cannot be fixed (for example a patch release is never
    ranked), explain it to the author. The report's `eligible` says whether
    the release will be ranked once published. Until then, the release
    itself reads `eligible: false` with `ineligible_reason: "not_decided"`:
    that is not a failed gate, only that ranking is decided at publish.

09. Show the author the summary, description, tags, changelog, media and
    models (if any), and ask for confirmation. Only then POST …/publish.

10. Report the release URL and whether it is eligible for the boards: the
    publish answer has the release under `release` (its `url`, `eligible`,
    and `posted_at`, the time it was published here) and the gates under
    `report`; no second request is needed.

Rules: use an Idempotency-Key header on every POST. On a 4xx, read `code`
and `fix` and follow them; a 422 lists every invalid field in `errors`
(codes: /docs/errors). On 429, wait for Retry-After. Do not vote, and
do not ask anyone to vote.

`403 github_reauth_required` (from POST /projects, which checks the
author's permissions on GitHub) is about the author's GitHub sign-in on the
website, not the API token: a new token won't fix it. Stop and ask the
author to open https://releaseszn.com/auth/github in a browser to sign in
with GitHub again (even if they are already signed in), then retry.
