# Recipe: opening & merging Codeberg PRs with `tea`

> **Audience:** anyone (human or agent) who has finished a green branch in
> a federation `mgf-*` repo and needs to land it as a Codeberg (Forgejo)
> pull request — without a human clicking the compare URL.
> **What this is:** the canonical CLI for federation PR operations. Every
> `mgf-*` repo lives on Codeberg/Forgejo; `gh` is GitHub-only and does not
> work against `codeberg.org`. `tea` (the Gitea/Forgejo CLI) is the
> federation's standard tool for `pr create` / `pr merge` / `pr list`.

## Why this exists (PAPER-01)

The land-the-slice loop ends in a Codeberg PR. An agent that finishes a
green branch can `git push`, but with no PR CLI on PATH it can only hand
back the `…/compare/main...<branch>` URL for a human to click — a manual
wedge in an otherwise fully-automatable loop, hit once per repo across the
whole federation. `tea` removes that wedge: the agent opens (and, when
asked, merges) the PR directly.

This is **how-to**, not a binding rule. The rule that *every change lands
through CI on a PR* is [`../standards/WORKFLOWS.md`](../standards/WORKFLOWS.md)
**WF-01**; this recipe is the mechanic for opening that PR.

## One-time setup

### 1. Install `tea`

`tea` is a single static Go binary — no runtime deps. Install to
`~/.local/bin` (already on most PATHs):

```sh
# pick the current release from https://dl.gitea.com/tea/ (1.0+ when available)
TEA_VER=0.9.2
curl -fsSL "https://dl.gitea.com/tea/${TEA_VER}/tea-${TEA_VER}-linux-amd64" \
  -o ~/.local/bin/tea
chmod +x ~/.local/bin/tea
tea --version          # confirm
```

(macOS: swap `linux-amd64` for `darwin-amd64`/`darwin-arm64`. `brew install tea`
also works.)

### 2. Add the Codeberg login

`tea` authenticates with a **Codeberg access token** — a **git-forge**
credential, distinct from the **package-publish** credential. Opening a PR
needs **repository** scope on Codeberg; it is unrelated to the devpi publish
password (the `devpi_password` from [`PACKAGE_DISTRIBUTION.md`](../standards/PACKAGE_DISTRIBUTION.md)
**PKG-03**), which authenticates `mgf-*` uploads to the devpi index and cannot
open a PR.

Issue a PR-scoped token at **Codeberg → Settings → Applications → Generate
New Token** with scopes **`write:repository`** + **`read:repository`** (add
`read:user` so `tea` can resolve your identity). Then:

```sh
tea login add \
  --name codeberg \
  --url https://codeberg.org \
  --token "$(your-token-source)"      # never paste the literal token into history
```

Verify — the login should list with USER `magogi-admin`:

```sh
tea logins list
```

> Token handling follows the federation rule (**REL-04** / **PKG-03**): keep
> it in a `chmod 600` file, read it into the command, never echo it into shell
> history, env-in-chat, or a commit. The federation's tokens live in the
> maintainer's local token store (see the project handoff), not in the repo.

### 3. `git push` access (SSH, auto-loading)

`tea` opens PRs over the API, but you still `git push` the branch over SSH.
Codeberg uses one SSH key per account; if the key isn't offered, every push
fails with `Permission denied (publickey)` — and a key added with a bare
`ssh-add` is lost on logout (and can silently drop mid-session once an SSH
`ControlMaster` connection expires). Pin it in `~/.ssh/config` so it loads
automatically and re-populates the agent on first use:

```sshconfig
# ~/.ssh/config   (chmod 600)
Host codeberg.org
    HostName codeberg.org
    User git
    IdentityFile ~/.ssh/codeberg     # wherever your Codeberg private key lives
    IdentitiesOnly yes               # offer only this key (avoids "too many keys")
    AddKeysToAgent yes               # cache it in the agent on first use
```

Verify (works even with an empty agent — `ssh-add -D` first to prove it):

```sh
ssh -T git@codeberg.org   # → "Hi there, <user>! You've successfully authenticated…"
```

**Fallback — push without SSH (token over HTTPS).** When SSH is unavailable
(no key on the box, a CI runner, a borrowed machine), push over HTTPS with a
Codeberg token, passed as an Authorization header so it never lands in the URL,
git config, or remote-error output:

```sh
CB=$(read-your-token)   # the 40-hex PAT; needs repository-write scope
AUTH="Authorization: Basic $(printf 'magogi-admin:%s' "$CB" | base64 -w0)"
git -c http.extraHeader="$AUTH" push "https://codeberg.org/magogi-admin/<repo>.git" HEAD:main
```

(Verify afterwards that no token leaked: `grep -r "$CB" .git/config` → nothing.)

## Opening a PR

Run from the **root of the repo** whose branch you pushed — `tea` discovers
the Codeberg repo + login from the git remote
(`ssh://git@codeberg.org/magogi-admin/<repo>.git`):

```sh
git push -u origin <branch>            # push the branch first

tea pr create \
  --base main \
  --head <branch> \
  --title "feat(x): land the slice" \
  --description "$(cat <<'EOF'
What this lands, why, and the gate output proving it green.

Signed-off-by: Bassam Alsanie <bassam@magogi.org>
EOF
)"
```

**Gotchas (the ones that bite when coming from `gh`):**

| `gh` habit | `tea` reality |
|---|---|
| `--body` for the PR description | it's **`--description` / `-d`** — `--body` is silently ignored |
| base branch is `main` everywhere | some federation repos branch-protect **`master`** (see WORKFLOWS.md §"branch-protection"); confirm the repo's default before `--base` |
| CLI auto-detects the repo | `tea` reads it from the git remote; outside a checkout pass `--repo magogi-admin/<repo>`, or `--remote origin` / `--login codeberg` to disambiguate |
| flags accept the current branch | `--head` defaults to the current branch — still pass it explicitly so the command is reproducible in a log |

`tea pr create` prints the new PR's URL and index on success.

## Reviewing, listing, merging

```sh
tea pr list                       # open PRs in this repo
tea pr <index>                    # show one PR in detail
tea pr <index> --comments         # with the discussion
tea pr merge <index>              # merge (default strategy); --style squash|rebase|merge
tea pr close <index>              # close without merging
```

Merging stays gated by the federation rules: **WF-01** (CI green on the PR)
and the supervisor's release discipline. The
[`federation-sync-prompt`](federation-sync-prompt.md) and
[`cohort-release-wave`](cohort-release-wave.md) recipes both end at "open a
PR; the supervisor merges" — **do not `tea pr merge` unsolicited**; that is
the supervisor's call, exactly as for a version bump.

## Related

- [`federation-sync-prompt.md`](federation-sync-prompt.md) — the sync loop
  that produces the branch this recipe turns into a PR.
- [`cohort-release-wave.md`](cohort-release-wave.md) — the supervisor-side
  ship procedure that consumes these PRs.
- [`../standards/WORKFLOWS.md`](../standards/WORKFLOWS.md) — **WF-01** (every
  change lands via CI on a PR); the rule this recipe serves.
- [`../standards/PACKAGE_DISTRIBUTION.md`](../standards/PACKAGE_DISTRIBUTION.md)
  — **PKG-03** the publish token (distinct scope from the PR token above).
