BREAKING: the plugin is `kettle`, not `tea`, and its commands are `/kettle:*`. It also now needs a binary on PATH that it did not need before; the README and every skill say how to get one and what a missing one looks like. The plugin was 3800 lines of Python doing what a compiled binary does better, and the name pointed at a tool that no longer takes part: `tea` is Gitea's CLI, and since the transport moved into the binary nothing here shells out to it for issues at all. A plugin named after it was going to keep suggesting otherwise. Deleted: 19 scripts, the 14-file unittest suite, and the tea-guard hook. The guard blocked any `tea` invocation that would run under a login the model picked instead of the operator; the binary holds its own credentials and reads the pinned login out of the project's own config, so that failure is no longer expressible and there is nothing left to police. agents-sync stays — it is about AGENTS.md symlinks and has nothing to do with any of this. What the plugin keeps is what only a plugin can carry: the rules an operator states and a binary cannot enforce. `init` still refuses to run inside a linked worktree and still may not be model-invoked, because which directory is the project is a statement a person makes. The issue format reference stays here and stays the source of truth. The runner subagent is still for batches and still may not decide what an issue says. The command reference in the issue, sync and project skills is GENERATED from the binary's own command registry, between markers, so a flag that changed cannot ship with a skill that recommends the old one. `kettle gen skills --check` exits non-zero when they drift. The generator owns the region and nothing outside it: the frontmatter description, which is what decides whether a skill loads at all, stays hand-written. `use` survives and is the one place `tea` is still named — for releases, webhooks and actions, which kettle does not cover. Its instruction to write `--login "$GITEA_LOGIN"` and let the hook substitute the pin was true until this commit and is now rewritten: `tea` keeps its own configuration, kettle keeps its own, and configuring one configures nothing in the other. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.3 KiB
name: use
description: Reference docs for the tea CLI — Gitea's own command-line client, and the way to reach every Gitea entity the kettle binary does not cover. Load when the user asks about pull requests, releases, milestones, labels, repos, branches, actions, webhooks, notifications or times, to look up the right tea command and flags. tea keeps its own configuration and its own logins, entirely separate from kettle's. Issues are NOT handled here: /kettle:issue works on them offline and /kettle:sync moves them to and from the tracker.
/kettle:use — tea CLI reference
Reference material for tea, Gitea's official command-line client. Use these
docs to look up commands, flags, filters and output fields before running tea
via Bash.
kettle covers issues and nothing else. Everything else Gitea has — pulls,
releases, milestones, labels, repos, branches, actions, webhooks, notifications,
times — is reached through tea, and this skill is how.
Issues are somewhere else
Do not reach for tea issues or tea api …/issues/… to read or create an
issue. Two skills own that, and they keep the payload out of your context:
| Skill | Scope |
|---|---|
/kettle:issue |
issues as units of work — create, read, grep, validate, tick, dependency graph. Offline. |
/kettle:sync |
moving issues between the local store and the tracker — pull, push, comment, close, evict. |
Login: tea has its own configuration, and it is not kettle's
Two tools, two credential stores, no connection between them:
| tool | where its logins live | how they are managed |
|---|---|---|
tea |
$XDG_CONFIG_HOME/tea |
tea logins list, tea logins add (interactive), tea logins default |
kettle |
~/.config/kettle/logins.yaml + <project>/.kettle/config.yaml |
kettle auth, kettle init --login (/kettle:auth) |
Configuring one configures nothing in the other. /kettle:auth does not give
tea a credential, and tea logins add does not give kettle one. A project
whose kettle commands work fine can still have no tea login at all, and the
error you get will be about the login tea chose for itself.
There is no $GITEA_LOGIN placeholder and no hook that substitutes one. The
PreToolUse guard that used to rewrite it was deleted along with the Python
scripts; writing --login "$GITEA_LOGIN" now passes an empty variable to tea
and fails in a way that reads like a tea bug. If you find that spelling
anywhere, it is stale.
How to name a login honestly:
- Inside a checkout,
teaauto-detects owner, repo and login from the git remote. That is usually right and usually enough — run the command without--login. - When the machine holds more than one login, or you are outside a checkout,
pass
--login <name>with a name out oftea logins list. Which one is the operator's call: ask withAskUserQuestionrather than picking the one that looks likely. A wrong identity writes to a real tracker under somebody else's account. no gitea login detected, falling back to login '…'is a hard failure, not a warning. Stop, do not act on the result, surface the line.- Never mutate login state: no
tea logins add/edit/delete/default, notea logout.tea logins listis the only login command that is yours to run, and adding a login is interactive — the operator does it in their own terminal.
How to use
- Identify the entity in the request: pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, notifications, etc.
- Find the matching command in the index below.
- Run it via Bash, e.g.
tea pulls list --repo owner/repo --state open.
tea auto-detects owner/repo from $PWD inside a git repo; otherwise pass
--repo owner/repo (or -r).
--repo takes a slug — except where a checkout is required
A few commands touch local git, not just the API, and for those --repo must
be a path to a checkout; a slug is rejected:
Error: local repository required: execute from a repo dir, or specify a path with --repo
The message reads like the flag is missing even when it was passed. Confirmed
for pulls create, pulls checkout and pulls clean (tea 0.14.x). Everything
that is only an API call — pulls list, milestones, releases, times,
labels, issues — takes the slug from any directory.
Three working forms for pulls create:
# 1. cwd inside the checkout, no --repo at all
tea pulls create --head feat/x --base main --title "…" --description "…"
# 2. from anywhere, --repo as a PATH (this is also the git-worktree answer:
# point it at the main checkout)
tea pulls create --repo /path/to/checkout \
--head feat/x --base main --title "…" --description "…"
# 3. no checkout in reach — POST it, where owner/repo is a slug again
tea api -X POST -d @tmp/pull/x.json repos/{owner}/{repo}/pulls
Index
- tea CLI overview — global flags, common options, output formats
- ENTITIES — issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comment
- HELPERS — open, notifications, clone, api
- MISC — whoami, admin
- SETUP — logins, logout, ssh-keys
The canonical issue format lives in
../issue/references/format.md — it describes
local files, not tea commands.
Rich payloads — write to $PWD/tmp/ first, then tea api
Entity subcommands (tea comment, tea pulls create, tea releases create, …)
are built for humans at a TTY. With a large or formatted body they can hang
silently — an empty-looking positional arg triggers the $EDITOR fallback, or a
scope/confirm prompt waits on a TTY that doesn't exist. The harness eventually
kills the process (e.g. exit 144 = 128 + SIGURG on macOS).
Rule: for any non-trivial body (multi-line, or containing markdown / code
fences / backticks / pipes / tables), bypass entity commands. Save the full
request payload to $PWD/tmp/ first, then POST via tea api.
Issues and issue comments are already wrapped — use /kettle:sync rather than
hand-rolling their JSON. .kettle/payload/ is kettle's own scratchpad and is
written by kettle only; do not put hand-made bodies there. The procedure below
covers everything else.
Procedure
- Ensure the target dir exists:
mkdir -p tmp/{kind}where{kind}ispull,release, etc. - Write the complete request body as JSON to
$PWD/tmp/{kind}/<slug>.json. One file = one request. Use a quoted heredoc to avoid shell expansion:Newlines inside the body must be encoded asmkdir -p tmp/release cat > tmp/release/v0-2-0.json <<'EOF' {"tag_name": "v0.2.0", "name": "v0.2.0", "body": "## Changes\n\nMulti-line markdown with `code`."} EOF\nin the JSON string. If composing programmatically, pipe throughjq -Rs '{body: .}' < body.md > tmp/release/v0-2-0.json. - POST with
tea api, passing the file with-d @<path>:tea api -X POST -d @tmp/release/v0-2-0.json repos/{owner}/{repo}/releases - Keep the file.
tmp/should be gitignored; the saved payload is useful for retries, edits (PATCH), and debugging failed posts.
Common endpoints
| Action | Method + endpoint |
|---|---|
| Create PR | POST repos/{owner}/{repo}/pulls |
| Edit PR body or title | PATCH repos/{owner}/{repo}/issues/{n} |
| Comment on a PR | POST repos/{owner}/{repo}/issues/{n}/comments |
| Edit comment | PATCH repos/{owner}/{repo}/issues/comments/{id} |
| Create release | POST repos/{owner}/{repo}/releases |
| Create milestone | POST repos/{owner}/{repo}/milestones |
Short single-line bodies (e.g. tea comment 42 "lgtm") are still fine via
entity commands.
Tips
- Pass
-o jsonfor structured output when parsing programmatically — on entity commands only. Ontea api,-ois a file name:-o jsonwrites the response body to a file calledjsonand leaves stdout empty. The response is already JSON, so there is nothing to format; use-for stdout, or leave the flag off. - Use
--fields, -fto narrow columns. - Pagination:
--page, -p <n>and--limit, --lm <n>(defaults 1 / 30). - A
teacommand that fails on identity is a login problem in tea's own config, never in kettle's —tea logins list, and the operator decides.