feat: reach the rest of Gitea with kettle api, and drop tea

The plugin required `tea`, Gitea's own CLI, for everything that is not an
issue: releases, pull requests, milestones, branches, actions, webhooks. That
put a second binary, a second set of logins nothing here could see, and 400
lines documenting somebody else's flags outside anything this repository can
test. One command over the transport that already existed removes all three.

Transport: `post` — the hand-rolled request the SDK cannot express, written for
the dependency endpoint — is generalized to an exported `Do`, and `post` is
three lines on top of it. Same http.Client, so the same RoundTripper files the
body under .kettle/payload/, the same `token …` header authenticates it, and a
non-2xx is the same *APIError. It does not paginate, does not reformat the
answer, and names no domain concept, so the layering test is untouched.

The endpoint rule is `tea api`'s, so an endpoint table written for that tool
still works — with one restriction it did not have: a full URL must be on this
instance. Every request carries the project's token in a header, and a URL on
another host would hand the token to whatever was typed.

Command: `kettle api <endpoint>` in a new `api` group, so the generator writes
plugins/kettle/skills/api/SKILL.md — group, directory and /kettle:api are one
word. No --repo and no --login, for the reason no sync command has them: a
cross-repository address is an address, and another instance is KETTLE_URL.
`-X DELETE` needs `--yes`; a flag typed on purpose is an operator's decision.

Scopes: a token minted for issues carries write:issue and answers 403 on the
first request outside issues, naming no scope. Gitea cannot be asked what a
token may do — its own token listing needs a password — so `auth add --scopes`
records it, `auth list` and `config` show it, and a 403 says which category it
is likely to be. Documentation only; nothing is checked against it.

skills/use — the tea reference, 239 lines of it — becomes skills/api: what to
ask for, which endpoints paginate, and how to write a body. Every mention of
`tea` as a requirement is gone from the manifests, the READMEs, the runner and
the four other skills; what survives is the back-compat with the old plugin,
which is a decision and not a debt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
naudachu
2026-08-12 14:25:20 +05:00
parent e177f46510
commit f18a633185
32 changed files with 1335 additions and 519 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "kettle",
"description": "Issues as local markdown, driven by the kettle binary: /kettle:init makes a directory a project, /kettle:issue works on issues offline (format, validation, checkboxes, dependency graph), /kettle:sync moves them to and from Gitea, /kettle:auth manages the credential a project runs under, /kettle:use is the tea CLI reference for the Gitea entities kettle does not cover, and the kettle-runner subagent executes batches on a cheap model. The command reference in each skill is generated from the binary's own command registry, so it cannot drift.",
"version": "3.0.0",
"description": "Issues as local markdown, driven by the kettle binary: /kettle:init makes a directory a project, /kettle:issue works on issues offline (format, validation, checkboxes, dependency graph), /kettle:sync moves them to and from Gitea, /kettle:auth manages the credential a project runs under, /kettle:api reaches everything else Gitea has — pull requests, releases, tags, milestones, actions, webhooks — through that same login, and the kettle-runner subagent executes batches on a cheap model. The command reference in each skill is generated from the binary's own command registry, so it cannot drift.",
"version": "3.1.0",
"author": {
"name": "naudachu"
},
+18 -11
View File
@@ -31,9 +31,9 @@ go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest
```
`cli/go.mod` says **go 1.26** — the Gitea SDK requires it, so that is the minimum
for anybody building this. There is no `vendor/`: a build resolves its modules from
the module cache or the network, and `go.sum` is what makes that safe. Why that
trade was taken is in [`cli/AGENTS.md`](../../cli/AGENTS.md).
for anybody building this. `vendor/` is committed, so what compiles is what is in
this repository's history; what that does and does not buy is in
[`cli/AGENTS.md`](../../cli/AGENTS.md).
An operator who sees `command not found: kettle` installs it and re-runs; there
is nothing to configure in this plugin either way. `kettle config` is the command
@@ -63,19 +63,24 @@ the run reports as `without a region` is one where somebody dropped the markers
it is left alone, never overwritten, and the fix is to put them back.
**Groups and skills are not the same set, and that is the one seam.** The binary
groups its commands `project`, `issue`, `sync`; the plugin's skills are `init`,
`auth`, `project`, `issue`, `sync`, `use`. The generator writes one
groups its commands `project`, `issue`, `sync`, `api`; the plugin's skills are
`init`, `auth`, `project`, `issue`, `sync`, `api`. The generator writes one
`<group>/SKILL.md`, so:
| skill | generated region | why |
|---|---|---|
| `issue`, `sync` | yes — the group of the same name | the skill and the group are the same subject |
| `issue`, `sync`, `api` | yes — the group of the same name | the skill and the group are the same subject |
| `project` | yes — `init`, `auth`, `config`, `gen` | the flag table `init` and `auth` point at |
| `init` | no | it is a *procedure* around one command, and it is operator-only |
| `auth` | no | it is a *procedure* around two, and it must not tempt a model into typing a token |
| `use` | no | it documents `tea`, which is not this binary |
The three skills with no region hold no flag tables of their own. They name a
`api` is the newest of those and the one that shows what the seam is for: the
group exists because `kettle api` is not an issue command, and the skill exists
because "which endpoint, and does it paginate" is a map somebody loads on its
own. The group was named `api` rather than left as `use` precisely so that the
three spellings — group, directory, `/kettle:api` — are one word.
The two skills with no region hold no flag tables of their own. They name a
command and send the reader to `/kettle:project`, which is the point: a file that
hand-copies a flag list is a file that will disagree with the binary in a month.
If the generator ever cannot express what a skill needs, the answer is to change
@@ -102,9 +107,10 @@ skills/
issue/ SKILL.md /kettle:issue — the offline commands
references/format.md THE canonical issue format; source of truth
sync/ SKILL.md /kettle:sync — the tracker commands
use/ SKILL.md /kettle:use — the `tea` CLI, for the Gitea entities
references/tea/ kettle does not cover: releases, webhooks,
actions, pull requests
api/ SKILL.md /kettle:api — every Gitea entity with no command of
its own: pull requests, releases, tags, milestones,
branches, actions, webhooks. The map of endpoints,
and which of them paginate
```
`references/format.md` is the one document here that the binary does not
@@ -138,6 +144,7 @@ and it stays hand-written.
| the guard hook and its entry in `hooks/hooks.json` | nothing. It existed to stop a `tea` command running under a login the model picked; the binary holds its own credentials and reads the login out of the project's config, so the failure is not expressible any more |
| `.claude/settings.local.json``env.GITEA_LOGIN`, the login pin | `<project>/.kettle/config.yaml` (a login **name**) plus `~/.config/kettle/logins.yaml` (the tokens, 0600, outside every working tree) |
| the tea plugin's own store marker | `.kettle/`; `kettle init` migrates an older layout in, and each migration is a move |
| `tea`, Gitea's own CLI, as an external requirement — a skill of reference docs for somebody else's flags, and a second set of logins nothing here could see | `kettle api`, one request through the transport the binary already had. What was 400 lines documenting another tool is now a map of endpoints; what was two credential stores is one |
`hooks/agents-sync.sh` is unrelated to any of that and stays. It maintains the
`AGENTS.md` convention by repairing the filesystem layout — a real file, with
+14 -12
View File
@@ -19,7 +19,7 @@ the tool.
| `/kettle:project` skill | Generated flag reference for `init`, `auth`, `config`, `gen` |
| `/kettle:issue` skill | Issues as units of work — create, read, grep, validate, tick, evict, walk the dependency graph. Entirely offline |
| `/kettle:sync` skill | Moves issues between the local store and Gitea — pull, push, comment, close, evict |
| `/kettle:use` skill | `tea` CLI reference for the Gitea entities kettle does not cover: pulls, releases, milestones, webhooks, actions |
| `/kettle:api` skill | Everything else Gitea has, through `kettle api` and the same login: pull requests, releases, tags, milestones, branches, actions, webhooks, notifications |
| `kettle-runner` agent | Subagent on Haiku that runs batches of commands and reports back a receipt — the mechanical half, off your main context |
## Prerequisites
@@ -27,17 +27,18 @@ the tool.
- **Claude Code** — CLI, desktop app, or IDE extension.
- **The `kettle` binary, on your `PATH`.** It is not installed for you, and
nothing here works without it — see below.
- **Python 3** — the two `agents-*` hooks are Python scripts; `python3` must be on
`$PATH`. Nothing else here needs it.
- **`tea`** (optional) — Gitea's own CLI, only for `/kettle:use`. `brew install
tea`, or from [gitea.com/gitea/tea/releases](https://gitea.com/gitea/tea/releases).
It keeps its own logins (`tea logins add`), separate from kettle's.
- **Python 3** — the `agents-sync` hook is a Python script despite its `.sh`
name; `python3` must be on `$PATH`. Nothing else here needs it.
There is **no second CLI to install**`kettle api`
reaches every Gitea entity this plugin has no command for, under the login the
project already pins.
### Installing the binary
Building it needs **Go 1.26**`cli/go.mod` says so because the Gitea SDK
requires it. There is no `vendor/` directory: the first build downloads eight
modules, verified against `go.sum`.
requires it. `vendor/` is committed, so a build compiles exactly what is in this
repository's history.
```bash
# from a clone of this repository
@@ -147,8 +148,10 @@ command yourself; the saving is in the loop, the retry, and reading somebody
else's stderr.
It cannot decide anything. No `Edit`, no `Write`, no `--force`, no widening the
set it was handed, no raw `tea`. A missing type, a failed validation, an unpushed
dependency come back as a question, not as a guess.
set it was handed, no request it composed itself — `kettle api` goes out as the
caller spelled it, and a deletion is never something it adds. A missing type, a
failed validation, an unpushed dependency come back as a question, not as a
guess.
## Project layout
@@ -169,8 +172,7 @@ skills/
issue/ /kettle:issue — the issue domain, offline
references/format.md canonical issue format (identity, types, templates)
sync/ /kettle:sync — the bridge to Gitea
use/ /kettle:use — tea CLI reference
references/tea/ command docs
api/ /kettle:api — every other Gitea entity, by endpoint
```
`AGENTS.md` carries the same layout with the reasoning behind it, and the
+8 -4
View File
@@ -23,6 +23,7 @@ Load the skill, do not remember the flags:
`sync-evict`
- `/kettle:issue``new`, `check`, `ac`, `tree`, `index`, `evict`
- `/kettle:project``config`, `auth list`
- `/kettle:api``api`, for the Gitea entities that have no command of their own
Invoke `Skill` with the one that owns the task at the start and use the generated
command reference it carries verbatim. That block is written from the binary's
@@ -33,10 +34,13 @@ truth if you need it in a hurry.
## Hard rules
1. **`kettle` only.** No raw `tea`, no `tea api`, no curl at a tracker. The
binary carries the project's credentials; there is no login for you to name
and none for you to choose. If a task needs an entity `kettle` does not cover,
that is a finding for the caller, not a command for you to improvise.
1. **`kettle` only.** No curl at a tracker, no other CLI, no request you composed
yourself. The binary carries the project's credentials; there is no login for
you to name and none for you to choose. `kettle api` is a kettle command and
is yours to run **as the caller spelled it** — endpoint, method and body come
from the task, and a `-X DELETE` is never something you add. An entity nobody
named an endpoint for is a finding for the caller, not a request for you to
improvise.
2. **No writing to issue files.** You have no `Edit` and no `Write`. Commands
write files; you do not. If a task needs a body edited or a metadata field
changed by hand, stop and say which file and which field. `kettle ac` is the
+246
View File
@@ -0,0 +1,246 @@
---
name: api
description: Everything Gitea has that is not an issue — pull requests, releases, tags, branches, milestones, labels, commits, actions, webhooks, notifications, tracked times, users, repositories — reached with `kettle api`, one endpoint per request, under the login the project already pins. Load when the user asks to open or review a PR, cut or edit a release, make a milestone or a tag, look at branches or commits, read notifications or actions, or hit any Gitea endpoint by hand. Issues are NOT handled here: /kettle:issue works on them offline and /kettle:sync moves them to and from the tracker.
---
# /kettle:api — Gitea beyond issues
`kettle api <endpoint>` is one authenticated request to the Gitea this project is
pinned to. No second tool, no second login: the URL, the token and the repository
are the ones `/kettle:auth` and `kettle init` already resolved, and the request
body is filed under `.kettle/payload/` like every other request kettle makes.
This skill is the map of what to ask for. `kettle help api` is the flag
reference, and it is generated from the binary — the block at the bottom of this
file is the same text.
## Issues are somewhere else
`kettle api` can reach an issue and must not be used to. An issue read this way
comes back as a full JSON payload — every label object, every URL, the whole
comment thread — which is precisely what the other two skills exist to keep out
of the context window.
| 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. |
The one exception is an issue endpoint that is not about the issue's content:
`issues/{n}/comments` is also **a pull request's** comment thread, and
`PATCH issues/{n}` is also how a pull request's title and body are edited. Gitea
numbers issues and pull requests in one sequence and serves both under
`/issues/`.
## What is a command and what is a request
Reach for the command where there is one: it knows the format, the store and the
round trip. Everything else is an endpoint.
| Subject | How to reach it | Pages? |
|---|---|---|
| issues (create, read, tick, validate) | `/kettle:issue` — offline, no request at all | — |
| issues (pull, push, comment, close, evict) | `/kettle:sync` | handled |
| the canonical `type/*` and `severity/*` labels | `kettle labels` | handled |
| this repository's own releases, with binaries | `cd cli && make release TAG=v1.2.3` | — |
| everything below | `kettle api` | see the column |
| Entity | Endpoint | Pages? |
|---|---|---|
| pull requests | `repos/{owner}/{repo}/pulls` | **yes** |
| one pull request | `repos/{owner}/{repo}/pulls/{n}` | no |
| create a pull request | `POST repos/{owner}/{repo}/pulls` | no |
| edit a PR's title or body | `PATCH repos/{owner}/{repo}/issues/{n}` | no |
| a PR's or issue's comments | `repos/{owner}/{repo}/issues/{n}/comments` | **yes** |
| edit one comment | `PATCH repos/{owner}/{repo}/issues/comments/{id}` | no |
| reviews on a PR | `repos/{owner}/{repo}/pulls/{n}/reviews` | **yes** |
| merge a PR | `POST repos/{owner}/{repo}/pulls/{n}/merge` | no |
| releases | `repos/{owner}/{repo}/releases` | **yes** |
| one release by tag | `repos/{owner}/{repo}/releases/tags/{tag}` | no |
| tags | `repos/{owner}/{repo}/tags` | **yes** |
| branches | `repos/{owner}/{repo}/branches` | **yes** |
| commits | `repos/{owner}/{repo}/commits` | **yes** |
| milestones | `repos/{owner}/{repo}/milestones` | **yes** |
| labels (all of them, not just canonical) | `repos/{owner}/{repo}/labels` | **yes** |
| webhooks | `repos/{owner}/{repo}/hooks` | **yes** |
| action tasks | `repos/{owner}/{repo}/actions/tasks` | **yes** |
| tracked times | `repos/{owner}/{repo}/times` | **yes** |
| the repository itself | `repos/{owner}/{repo}` | no |
| notifications | `notifications` | **yes** |
| who this token is | `user` | no |
| an organization's repositories | `orgs/{org}/repos` | **yes** |
`{owner}` and `{repo}` are filled in from the project's configuration. A path
that names a repository in full is left alone — `repos/other-owner/other/releases`
reads another repository on the same instance, which is why there is no `--repo`
flag. Another **instance** is `KETTLE_URL` and `KETTLE_TOKEN`, not a flag.
What the instance actually serves is its own version's business; its API docs at
`<instance-url>/api/swagger` are the authority when an endpoint answers 404.
## Pagination is yours
**One invocation is one HTTP request.** `kettle api` never follows a list to its
end, because a passthrough that silently stitched pages together would report as
one answer something that was several.
So for every row marked **yes** above:
```bash
kettle api 'repos/{owner}/{repo}/pulls?state=open&limit=50' # first page, 50 rows
kettle api 'repos/{owner}/{repo}/pulls?state=open&limit=50&page=2'
```
- `limit` is capped by the instance (`MAX_RESPONSE_ITEMS`, 50 by default); the
default page size is 30.
- **A short page is the last one.** Ask for 50, count what came back: fewer than
50 means there is no page 3. That is the same rule the binary's own listings
use, and it needs no response headers.
- Quote any endpoint holding `?` or `&`, or the shell takes it apart.
- Walking many pages of anything into your own context is a mistake before it is
a request. Narrow the query (`state=`, `since=`, `q=`), or pipe through `jq`
and keep the two fields you needed.
## Writing a body
Two ways, and the choice is about the body, not the endpoint:
```bash
# small and flat: every value is a string
kettle api --field body=lgtm repos/{owner}/{repo}/issues/7/comments
# anything real — multi-line, markdown, booleans, numbers, nesting
mkdir -p tmp/release
cat > tmp/release/v0-2-0.json <<'EOF'
{"tag_name": "v0.2.0", "name": "v0.2.0", "draft": false,
"body": "## Changes\n\nMulti-line markdown with `code`."}
EOF
kettle api --data @tmp/release/v0-2-0.json repos/{owner}/{repo}/releases
```
- A body implies `POST`; anything else is `-X PUT`, `-X PATCH`, `-X DELETE`.
- Newlines inside a JSON string are `\n`. Composing from a file:
`jq -Rs '{body: .}' < body.md > tmp/pull/x.json`.
- `--field` values are **always strings**. A `draft: false` or a number is a
`--data` body — guessing types is how a `tag_name` of `1.0` goes up as a
number.
- Keep `tmp/` gitignored and keep the file: a `PATCH` is usually the same body
with one line changed. `kettle` files its own copy under `.kettle/payload/`
automatically; that directory is the transport's and nothing hand-made goes in
it.
- Attachments are `multipart/form-data` and this command sends JSON. Upload
release binaries with the release tooling (`make release`), or the web UI.
## Deleting
`-X DELETE` needs `--yes` in the same invocation, and the refusal happens before
anything is sent:
```bash
kettle api -X DELETE --yes repos/{owner}/{repo}/releases/12
```
That flag is the whole gate. **Whether a thing should be deleted is the
operator's call, not a step in a plan** — ask, do not assume, and never widen a
deletion past what was named.
## When it says 403
Gitea scopes a token as `<read|write>:<category>`, and a token minted to file
issues carries `write:issue` and nothing else. Releases, pull requests, branches,
tags and actions are all `repository`, so that token answers **403 on the first
`kettle api` outside issues** — and the 403 names no scope.
`kettle auth list` shows what each login on this machine recorded; `kettle config`
shows what this project resolved. Nothing can be read back off the instance
(Gitea's own token listing needs a password, not a token), so a scope that was
never written down is a scope nobody knows. Minting a new token is the operator's
job, in the web UI — `/kettle:auth` has the procedure.
## What is not an API call at all
| Want | Do |
|---|---|
| check out a PR branch, clone, push | `git`. This is git's job and always was |
| who am I | `kettle api user` |
| open something in a browser | nothing here; hand the user the URL |
| add a login, list logins, ssh keys | `/kettle:auth`, and adding one is the operator's |
| administer users or the instance | nothing here. Not an agent's work |
The canonical issue format lives in
[`../issue/references/format.md`](../issue/references/format.md) — it describes
local files, not requests.
<!-- kettle:gen -->
**Generated from the kettle command registry by `kettle gen skills`.** Everything between the two markers is replaced on the next run — hand-written prose belongs outside them.
## `kettle api <endpoint>`
one request to this project's Gitea, for everything that is not an issue
Releases, pull requests, milestones, branches, tags, actions, webhooks,
notifications: everything Gitea has that this binary has no command for. One
invocation is ONE request — the credentials, the repository and the payload
scratchpad are the ones this project already resolved, so there is nothing to
configure and no second tool to log in.
THE ENDPOINT IS SPELLED THE WAY GITEA'S OWN DOCUMENTATION SPELLS IT. A bare path
is taken as relative to `/api/v1/`; a path that already begins `/api/` is sent as it
stands, which is how anything outside v1 is reached; a full URL is allowed only
on the instance this project points at, because every request here carries the
project's token in a header and a URL somewhere else would hand that token over.
`{owner}` and `{repo}` are filled in from the project's configuration. Quote an
endpoint that contains ? or & or the shell will take it apart.
ANOTHER REPOSITORY NEEDS NO FLAG — write its address into the path
(`repos/other-owner/other-repo/releases`) and nothing is substituted. There is no
--repo and no --login here for the same reason there is none on push or pull:
which login a project runs under is a fact about the project. Another INSTANCE
is KETTLE_URL and KETTLE_TOKEN, which is also what a CI run uses.
THE ANSWER IS THE SERVER'S BYTES ON STDOUT, unparsed and unreformatted — pipe it
to jq, redirect it to a file. There is no flag that names an output file: in
this tree --out is the issue store, and one word meaning two things is exactly
the trap the tool this replaces set with an -o that wrote a file called "json".
IT DOES NOT PAGINATE. One call is one request, so a listing answers with one
page: ask for the next with ?page=2, and for a bigger one with ?limit=50 (the
server's own default is 30, its maximum is usually 50). A passthrough that
stitched pages together silently would report as one answer something that was
several.
ISSUES ARE NOT THIS COMMAND'S JOB even though it can reach them. An issue read
this way arrives as a full JSON payload — every comment, every label object,
every URL — which is what /kettle:issue and /kettle:sync exist to keep out of a
context window. Use pull, push, comment and close.
A 403 here is usually the token rather than the request: a token minted for
issues carries write:issue, and releases, pull requests, branches and tags are
all under repository. `kettle auth list` shows what each login records.
-X DELETE NEEDS --yes. Everything else goes through as typed; a deletion does
not, because a flag typed on purpose is an operator's decision and the URL of a
release is one character away from the URL of the wrong release.
What it cannot do: an upload. Release attachments are multipart/form-data and
this sends JSON — the release tooling in cmd/release does those.
| flag | default | what it does |
| --- | --- | --- |
| `--X` | — | the same flag as --method, spelled the way curl and the tool this replaces spell it |
| `--data` | — | the request body: @file, @- for standard input, or the JSON itself |
| `--field` | — | key=value, added to a JSON body as a string; repeatable |
| `--method` | — | GET, POST, PUT, PATCH or DELETE (default GET, or POST when there is a body) |
| `--status` | `false` | print the status line on standard error |
| `--yes` | `false` | confirm a DELETE |
```bash
kettle api repos/{owner}/{repo}/releases # the latest page of releases, as JSON
kettle api user # who this project's token belongs to
kettle api 'repos/{owner}/{repo}/pulls?state=open&limit=50' # quote anything with ? or & in it
kettle api --data @tmp/release/v0-2-0.json repos/{owner}/{repo}/releases # a body from a file; POST is implied
kettle api --field body=lgtm repos/{owner}/{repo}/issues/7/comments # a small body without a file
kettle api -X DELETE --yes repos/{owner}/{repo}/releases/12 # a deletion, said out loud
kettle api repos/{owner}/{repo}/milestones | jq '.[].title' # the bytes are the server's; jq is yours
```
<!-- /kettle:gen -->
+27 -3
View File
@@ -85,9 +85,33 @@ repo claude-skills/marketplace
| `401` / `403` from a sync command | report it verbatim. Do **not** try another login, and do not edit or remove one to route around it — that is somebody's identity, not a setting |
| `token none` in `kettle config` | a name is pinned but no credential answers to it |
**`tea` does not read any of this.** The `tea` CLI keeps its own configuration
under `$XDG_CONFIG_HOME/tea` and its own logins (`tea logins list`), and
configuring one tool configures nothing in the other — see `/kettle:use`.
## Scopes: what the token is allowed to do
Gitea mints a token with scopes, spelled `<read|write>:<category>`. A token made
for issues carries `write:issue` — and that is enough for everything
`/kettle:issue` and `/kettle:sync` do, and **not** enough for anything
`/kettle:api` reaches: releases, pull requests, branches, tags and actions all
sit under `repository`.
| doing | needs |
|---|---|
| pull, push, comment, close, evict | `write:issue` |
| `kettle labels` | `write:issue` |
| `kettle api` on releases, PRs, tags, branches, actions | `write:repository` too |
| reading any of those without writing | the `read:` half is enough |
`kettle auth add --scopes write:issue,write:repository` writes that down beside
the login. **It is a note and nothing else** — nothing is checked against it and
nothing is refused because of it. It is worth writing down because the instance
will not answer the question: Gitea's own token listing needs a password rather
than a token, so a token cannot be asked what it may do. `kettle auth list` and
`kettle config` show what was recorded; `(not recorded)` means nobody wrote it
down, never "none".
A **403** from a sync command or from `kettle api` is usually this and says so.
Minting a new token is the operator's job in the web UI — the same flow as step
2 above, with both scopes ticked this time. Never remove or re-point a login to
route around a 403.
**No `kettle` on PATH?** `command not found: kettle` is the whole story. Stop and
tell the operator to install it: `cd cli && go build -o ~/.local/bin/kettle
+2 -1
View File
@@ -20,7 +20,8 @@ an issue. It is the single source of truth for identity, metadata, types,
labels, templates, and language rules.
**No `kettle` on PATH?** `command not found: kettle` is the whole story — the
Python scripts this plugin used to ship are gone and `tea` is not a substitute.
Python scripts this plugin used to ship are gone and no other CLI is a
substitute.
Stop and tell the operator to install it: `cd cli && go build -o
~/.local/bin/kettle ./cmd/kettle` in the marketplace repository (go.mod requires
**go 1.26**), or `go install
@@ -43,10 +43,10 @@ milestone: v0.2
depends: [migrate-schema]
origin: gitea
branch: feat/wire-sqlc
gitea: claude-skills/tea#42
gitea: claude-skills/marketplace#42
remote-updated: 2026-08-09T18:24:01Z
synced: 2026-08-09T18:40:00Z
url: https://git.noodles.cam/claude-skills/tea/issues/42
url: https://git.noodles.cam/claude-skills/marketplace/issues/42
---
# Wire sqlc into the appclick repo layer
+12 -1
View File
@@ -19,7 +19,7 @@ nothing on its own, which is what makes it safe inside a working tree.
`KETTLE_URL` and `KETTLE_TOKEN` each override the file they shadow.
**No `kettle` on PATH?** `command not found: kettle` is the whole story — no
script and no `tea` invocation substitutes for it. Stop and tell the operator to
script and no other CLI substitutes for it. Stop and tell the operator to
install it: `cd cli && go build -o ~/.local/bin/kettle ./cmd/kettle` in the
marketplace repository (go.mod requires **go 1.26**), or
`go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest`.
@@ -47,9 +47,19 @@ argument is in the shell history the moment it is typed:
`list` never prints a token. There is no flag to make it.
--scopes RECORDS WHAT THE TOKEN WAS MINTED WITH, and records is all it does:
nothing is checked against it and nothing is refused because of it. It is worth
writing down because the instance will not answer the question — Gitea's own
token listing needs a password, not a token, so a token cannot be asked what it
may do. Gitea spells them <read|write>:<category>; issues need `write:issue`,
and everything `kettle api` reaches outside issues — releases, pull requests,
branches, tags, actions — is `repository`. A token minted for issues alone
answers 403 there, and the 403 names no scope.
| flag | default | what it does |
| --- | --- | --- |
| `--name` | — | login name (add) |
| `--scopes` | — | what the token was minted with, comma separated, e.g. write:issue,write:repository; documentation only (add) |
| `--token` | — | token, if you would rather not use stdin (add) |
| `--url` | — | instance URL, e.g. https://git.example.com (add) |
| `--user` | — | account this token belongs to; documentation only (add) |
@@ -57,6 +67,7 @@ argument is in the shell history the moment it is typed:
```bash
kettle auth list # what this machine holds
pass show gitea | kettle auth add --name noodles --url https://git.example.com # add one, token on stdin
kettle auth add --name noodles --url https://git.example.com --scopes write:issue,write:repository < t.txt # and write down what it can do
kettle auth remove noodles # forget it
```
+10 -9
View File
@@ -12,7 +12,7 @@ second-guesses it. Knowledge flows one way: delete the tracker from the world an
the issue domain does not notice.
**No `kettle` on PATH?** `command not found: kettle` is the whole story — the
Python scripts this plugin used to ship are gone and raw `tea` is not a
Python scripts this plugin used to ship are gone and no other CLI is a
substitute. Stop and tell the operator to install it: `cd cli && go build -o
~/.local/bin/kettle ./cmd/kettle` in the marketplace repository (go.mod requires
**go 1.26**), or `go install
@@ -38,11 +38,12 @@ No login, an unknown login name, a 401: report it and stop — `/kettle:auth`.
## Never read an issue through a raw API dump
`tea issues 42 -o json` and `tea api …/issues/42` put the whole payload —
avatars, nested user objects, every comment body — into your context whether you
need it or not. `kettle pull` writes flat markdown and prints a compact line per
issue; `kettle remote` lists the tracker without writing anything at all. Use
those.
`kettle api repos/{owner}/{repo}/issues/42` will answer, and answering is the
problem: the whole payload — avatars, nested user objects, every comment body —
lands in your context whether you need it or not. `kettle pull` writes flat
markdown and prints a compact line per issue; `kettle remote` lists the tracker
without writing anything at all. Use those. `/kettle:api` is for the entities
that have no command, and it says the same thing from its side.
## The round trip is one rule
@@ -183,9 +184,9 @@ request bodies are debris of the transport, and a scratchpad inside a store make
`ls .kettle/issues` lie about what exists. Nothing in it is anybody's only copy —
deleting it costs nothing. A run that sends nothing leaves no directory behind.
For Gitea entities `kettle` does not cover — releases, webhooks, actions, pull
requests — the tool is `tea`, and it keeps its own configuration and its own
logins. `/kettle:use`.
For Gitea entities `kettle` has no command for — releases, webhooks, actions,
pull requests — `kettle api` sends the request under this same login, into this
same scratchpad. `/kettle:api`.
The commands themselves follow. Their usage lines, flags, defaults and examples
are generated from the binary's own command registry, so they cannot disagree
-176
View File
@@ -1,176 +0,0 @@
---
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, `tea` auto-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 of `tea logins list`. **Which one is the
operator's call**: ask with `AskUserQuestion` rather 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`, no
`tea logout`. `tea logins list` is 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
1. Identify the entity in the request: pulls, labels, milestones, releases,
times, repos, branches, actions, webhooks, notifications, etc.
2. Find the matching command in the index below.
3. 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`:
```bash
# 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](references/tea/index.md) — global flags, common options, output formats
- [ENTITIES](references/tea/entities.md) — issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comment
- [HELPERS](references/tea/helpers.md) — open, notifications, clone, api
- [MISC](references/tea/misc.md) — whoami, admin
- [SETUP](references/tea/setup.md) — logins, logout, ssh-keys
The canonical issue format lives in
[`../issue/references/format.md`](../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
1. Ensure the target dir exists: `mkdir -p tmp/{kind}` where `{kind}` is
`pull`, `release`, etc.
2. 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:
```bash
mkdir -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
```
Newlines inside the body must be encoded as `\n` in the JSON string. If
composing programmatically, pipe through
`jq -Rs '{body: .}' < body.md > tmp/release/v0-2-0.json`.
3. POST with `tea api`, passing the file with `-d @<path>`:
```bash
tea api -X POST -d @tmp/release/v0-2-0.json repos/{owner}/{repo}/releases
```
4. 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 json` for structured output when parsing programmatically — on
**entity commands only**. On `tea api`, `-o` is a *file name*: `-o json`
writes the response body to a file called `json` and leaves stdout empty.
The response is already JSON, so there is nothing to format; use `-` for
stdout, or leave the flag off.
- Use `--fields, -f` to narrow columns.
- Pagination: `--page, -p <n>` and `--limit, --lm <n>` (defaults 1 / 30).
- A `tea` command that fails on identity is a login problem in **tea's** own
config, never in kettle's — `tea logins list`, and the operator decides.
@@ -1,137 +0,0 @@
# tea CLI — ENTITIES
See [`./index.md`](./index.md) for global options and common flags shared by all commands.
## `tea issues` (aliases: `issue`, `i`)
Without args lists issues; with `<index>` shows issue detail.
Shared filters: `--state {all|open|closed}` (default: open), `--kind {issues|pulls|all}`, `--keyword/-k`, `--labels/-L`, `--milestones/-m`, `--author/-A`, `--assignee/-a`, `--mentions/-M`, `--owner/--org`, `--from/-F`, `--until/-u`, `--comments`. Available fields: `index,state,kind,author,author-id,url,title,body,created,updated,deadline,assignees,milestone,labels,comments,owner,repo`.
Subcommands:
- `list, ls` — list (same filters as above).
- `create, c` — create an issue. Options: `--title/-t`, `--description/-d`, `--assignees/-a`, `--labels/-L`, `--milestone/-m`, `--deadline/-D`, `--referenced-version/-v` (commit hash or tag).
- `edit, e <idx>...` — edit. `--title`, `--description`, `--add-assignees/-a`, `--add-labels/-L`, `--remove-labels`, `--milestone`, `--deadline`, `--referenced-version`. To unset a value pass an empty string (`--milestone ""`).
- `reopen, open <idx>...`
- `close <idx>...`
## `tea pulls` (aliases: `pull`, `pr`)
Without args lists PRs; with `<index>` shows PR detail. Fields: `index,state,author,author-id,url,title,body,mergeable,base,base-commit,head,diff,patch,created,updated,deadline,assignees,milestone,labels,comments,ci`.
Subcommands:
- `list, ls` (`--state`)
- `checkout, co <idx>` — check out PR locally. `--branch/-b` creates a local branch if missing. Needs a checkout, same as `create`: `--repo` is a path here, not a slug.
- `clean <idx>` — delete local and remote feature branches for a closed PR. `--ignore-sha` matches branch by name instead of commit hash. Needs a checkout, same as `create`.
- `create, c` — create a PR. `--head <user:branch>`, `--base/-b`, `--allow-maintainer-edits/--edits`, `--agit`, `--topic`, plus all issue-style fields (`--title`, `--description`, `--assignees`, `--labels`, `--milestone`, `--deadline`, `--referenced-version`).
**Needs a local checkout.** `--repo owner/repo` is *not* accepted here — the
slug fails with `local repository required: execute from a repo dir, or
specify a path with --repo`, whose advice reads like the flag was missing.
Run it with cwd inside the checkout and no `--repo`, or pass `--repo
/path/to/checkout`. From a git worktree, point `--repo` at the main
checkout. With no checkout in reach, `POST repos/{owner}/{repo}/pulls`
through `tea api`, which takes the slug.
- `close <idx>...`, `reopen, open <idx>...`
- `edit, e <idx>...` — like `issues edit` plus `--add-reviewers/-r`, `--remove-reviewers`.
- `review <idx>` — interactive review.
- `approve, lgtm, a <idx> [comment]`
- `reject <idx> <reason>`
- `merge, m <idx>``--style/-s {merge|rebase|squash|rebase-merge}` (default merge), `--title/-t`, `--message/-m`.
- `review-comments, rc <idx>` — list review comments. Fields: `id,body,reviewer,path,line,resolver,created,updated,url`.
- `resolve <comment-id>` / `unresolve <comment-id>`
## `tea labels` (alias: `label`)
- `list, ls``--save/-s` dumps labels to a file.
- `create, c``--name`, `--color`, `--description`, `--file` (bulk import from file).
- `update``--id`, `--name`, `--color`, `--description`.
- `delete, rm``--id`.
## `tea milestones` (aliases: `milestone`, `ms`)
Fields: `title,state,items_open,items_closed,items,duedate,description,created,updated,closed,id`.
- `list, ls` (`--state`)
- `create, c``--title/-t`, `--description/-d`, `--deadline/--expires/-x`, `--state`.
- `close <name>...``--force/-f` deletes instead of closing.
- `reopen, open <name>...`
- `delete, rm <name>`
- `issues, i <name>` — manage milestone contents:
- `add, a <name> <issue-idx>`
- `remove, r <name> <issue-idx>`
## `tea releases` (aliases: `release`, `r`)
- `list, ls`
- `create, c [<tag>]``--tag`, `--target` (branch/commit), `--title/-t`, `--note/-n`, `--note-file/-f`, `--draft/-d`, `--prerelease/-p`, `--asset/-a <path>` (repeatable).
- `edit, e <tag>...``--tag`, `--target`, `--title/-t`, `--note/-n`, `--draft/-d <bool>`, `--prerelease/-p <bool>`.
- `delete, rm <tag>...``--confirm/-y` required; `--delete-tag` also removes the git tag.
- `assets, asset, a` — manage release attachments:
- `list, ls <tag>`
- `create, c <tag> <asset>...`
- `delete, rm <tag> <attachment-name>...``--confirm/-y`.
## `tea times` (aliases: `time`, `t`)
Time tracking on issues/PRs. Fields: `id,created,repo,issue,user,duration`. Command-level: `--from/-f`, `--until/-u`, `--total/-t`, `--mine/-m`.
- `add, a <issue> <duration>` — e.g. `tea times add 1 1h25m`.
- `delete, rm <issue> <time-id>`
- `reset <issue>`
- `list, ls [username | #issue]` — username filters by user on the repo; `#N` filters by issue; `--mine` aggregates across all repos.
## `tea organizations` (aliases: `organization`, `org`)
- `list, ls`
- `create, c <name>``--full-name/-n`, `--description/-d`, `--website/-w`, `--location/-L`, `--visibility/-v`, `--repo-admins-can-change-team-access`.
- `delete, rm <name>`
## `tea repos` (alias: `repo`)
Fields: `description,forks,id,name,owner,stars,ssh,updated,url,permission,type`.
- `list, ls``--watched/-w`, `--starred/-s`, `--owner/-O`, `--type/-T {fork|mirror|source}`.
- `search, s [term]``--topic/-t`, `--type/-T`, `--owner/-O`, `--private {true|false}`, `--archived {true|false}`.
- `create, c``--name`, `--owner/-O`, `--private`, `--description/--desc`, `--init`, `--labels`, `--gitignores/--git`, `--license`, `--readme`, `--branch`, `--template`, `--trustmodel {committer|collaborator|collaborator+committer}`, `--object-format {sha1|sha256}`.
- `create-from-template, ct``--template/-t`, `--name/-n`, `--owner/-O`, `--private`, `--description/--desc`, copy toggles: `--content`, `--githooks`, `--avatar`, `--labels`, `--topics`, `--webhooks`.
- `fork, f``--owner/-O` (default: current user).
- `migrate, m``--name`, `--owner`, `--clone-url`, `--service {git|gitea|gitlab|gogs}`, `--mirror`, `--mirror-interval`, `--private`, `--template`, copy toggles: `--wiki`, `--issues`, `--labels`, `--pull-requests`, `--releases`, `--milestones`, `--lfs`, `--lfs-endpoint`, auth: `--auth-user`, `--auth-password`, `--auth-token`.
- `delete, rm``--name`, `--owner/-O`, `--force/-f`.
- `edit, e``--name`, `--description/--desc`, `--website`, `--private <bool>`, `--template <bool>`, `--archived <bool>`, `--default-branch`.
## `tea branches` (aliases: `branch`, `b`)
Fields: `name,protected,user-can-merge,user-can-push,protection`.
- `list, ls`
- `protect, P <branch>` — enable branch protection.
- `unprotect, U <branch>` — remove protection.
- `rename, rn <old> <new>`
## `tea actions` (alias: `action`)
CI management: secrets, variables, workflow definitions, workflow runs.
### `tea actions secrets` (alias: `secret`)
- `list, ls`
- `create, add, set <name> [value]``--file` or `--stdin` to read the value.
- `delete, remove, rm <name>``--confirm/-y`.
### `tea actions variables` (aliases: `variable`, `vars`, `var`)
- `list, ls``--name` to fetch a single variable.
- `set, create, update <name> [value]``--file`, `--stdin`.
- `delete, remove, rm <name>``--confirm/-y`.
### `tea actions runs` (alias: `run`)
- `list, ls``--status {success|failure|pending|queued|in_progress|skipped|canceled}`, `--branch`, `--event`, `--actor`, `--since`, `--until`.
- `view, show, get <run-id>``--jobs` prints the jobs table.
- `delete, remove, rm, cancel <run-id>``--confirm/-y`.
- `logs, log <run-id>``--job <id>`, `--follow/-f` (requires the job to be in progress).
### `tea actions workflows` (alias: `workflow`)
- `list, ls`
- `view, show, get <workflow-id>`
- `dispatch, trigger, run <workflow-id>``--ref/-r`, `--input/-i key=value` (repeatable), `--follow/-f`.
- `enable <workflow-id>`
- `disable <workflow-id>``--confirm/-y`.
## `tea webhooks` (aliases: `webhook`, `hooks`, `hook`)
Scope is selected by flag: `--repo`, `--org`, `--global`.
- `list, ls`
- `create, c <webhook-url>``--type {gitea|gogs|slack|discord|dingtalk|telegram|msteams|feishu|wechatwork|packagist}` (default: gitea), `--secret`, `--events` (default: push), `--active`, `--branch-filter`, `--authorization-header`.
- `update, edit, u <id>``--url`, `--secret`, `--events`, `--active` / `--inactive`, `--branch-filter`, `--authorization-header`.
- `delete, rm <id>``--confirm/-y`.
## `tea comment, c <issue/pr index> [body]`
Add a comment to an issue or PR. Body may be passed as an argument or supplied interactively.
@@ -1,30 +0,0 @@
# tea CLI — HELPERS
See [`./index.md`](./index.md) for global options and common flags shared by all commands.
## `tea open, o`
Open the current repository/context in a web browser.
## `tea notifications` (aliases: `notification`, `n`)
Defaults to the current repo; `--mine/-m` aggregates across all your repos. Fields: `id,status,updated,index,type,state,title,repository`. Filters: `--types/-t {issue|pull|repository|commit}`, `--states/-s {pinned|unread|read}` (default: `unread,pinned`).
- `ls, list`
- `read, r [all | <id>]`
- `unread, u [all | <id>]`
- `pin, p [all | <id>]`
- `unpin [all | <id>]`
## `tea clone, C <repo-slug> [target-dir]`
Clone without requiring a local git install. Accepts slug forms: `gitea/tea`, `tea`, `gitea.com/gitea/tea`, `git@gitea.com:gitea/tea`, `https://gitea.com/gitea/tea`, `ssh://gitea.com:22/gitea/tea`. A host in the slug overrides `--login`. Options: `--depth/-d`, `--login/-l`.
## `tea api <endpoint>`
Authenticated HTTP request to the Gitea API. Endpoints are auto-prefixed with `/api/v1/` unless they start with `/api/` or `http(s)://`. Placeholders `{owner}` and `{repo}` are filled from the repo context.
- `--method/-X {GET|POST|PUT|PATCH|DELETE}` (default GET; switches to POST automatically when a body is provided)
- `--field/-f key=value` — string field on body (repeatable).
- `--Field/-F key=value` — typed field (numbers, booleans, null, JSON arrays/objects); `@file` or `@-` (stdin); `"null"` forces literal string.
- `--data/-d` — raw JSON body (`@file` / `@-`). Incompatible with `-f`/`-F`.
- `--header/-H key:value` (repeatable)
- `--include/-i` — write status + response headers to stderr.
- `--output/-o <file>` — write response body to file (`-` = stdout). **Not the entity commands' format flag**: `-o json` here creates a file named `json` and prints nothing. The body is already JSON.
- Quote the endpoint if it contains `?` or `&` to prevent shell expansion.
@@ -1,36 +0,0 @@
# tea CLI — Index
Version: `tea 0.14.1` (go-sdk v0.25.1). Source: recursive `--help` traversal. Upstream: https://gitea.com/gitea/tea
`tea` is a productivity helper for Gitea. It uses the current git repository context (`$PWD`) — owner/repo/login are auto-detected when inside a repo. Config is persisted in `$XDG_CONFIG_HOME/tea`.
## Global options
- `--debug, --vvv` — enable debug mode
- `--help, -h`, `--version, -v`
## Common flags (present on nearly every command)
| Flag | Purpose |
|---|---|
| `--login, -l <name>` | use a specific login from the config |
| `--repo, -r <owner/repo>` | override repository context (local path or slug). **A slug only works where the command is pure API.** `pulls create`, `pulls checkout` and `pulls clean` need a real checkout and read this flag as a path — see [SKILL.md](../../SKILL.md) |
| `--remote, -R <name>` | discover login from this git remote |
| `--output, -o <fmt>` | output format: `simple, table, csv, tsv, yaml, json`. **Entity commands only** — on `tea api` the same flag is a FILE NAME, see [HELPERS](./helpers.md) |
| `--page, -p <n>` / `--limit, --lm <n>` | pagination (defaults 1 / 30) |
| `--fields, -f <list>` | which columns to print |
## Command categories
```
ENTITIES: issues, pulls, labels, milestones, releases, times,
organizations, repos, branches, actions, webhooks, comment
HELPERS: open, notifications, clone, api
MISC: whoami, admin
SETUP: logins, logout, ssh-keys
```
- [ENTITIES](./entities.md)
- [HELPERS](./helpers.md)
- [MISC](./misc.md)
- [SETUP](./setup.md)
@@ -1,17 +0,0 @@
# tea CLI — MISCELLANEOUS
See [`./index.md`](./index.md) for global options and common flags shared by all commands.
## `tea whoami`
Show the currently logged in user.
## `tea admin, a`
Operations requiring admin access on the Gitea instance.
### `tea admin users` (alias: `u`)
Fields: `id,login,full_name,email,avatar_url,language,is_admin,restricted,prohibit_login,location,website,description,visibility,activated,lastlogin_at,created_at`.
- `list, ls`
- `create, add, new``--username/-u`, `--password/-p` / `--password-file` / `--password-stdin`, `--email/-e`, `--full-name`, `--admin`, `--restricted`, `--prohibit-login`, `--no-must-change-password`, `--visibility {public|limited|private}`.
- `edit, update, e, u <username>` — paired flags: `--password` (or `--password-file`/`--password-stdin`), `--email/-e`, `--full-name`, `--description`, `--website`, `--location`, `--admin`/`--no-admin`, `--restricted`/`--no-restricted`, `--prohibit-login`/`--allow-login`, `--active`/`--inactive`, `--no-must-change-password`, `--visibility`, `--max-repo-creation` (-1 = unlimited), `--allow-git-hook`/`--no-allow-git-hook`, `--allow-import-local`/`--no-allow-import-local`, `--allow-create-organization`/`--no-allow-create-organization`.
- `delete, rm, remove <username>``--confirm/-y`.
@@ -1,19 +0,0 @@
# tea CLI — SETUP
See [`./index.md`](./index.md) for global options and common flags shared by all commands.
## `tea logins` (alias: `login`)
- `list, ls`
- `add` — interactive when called without args. `--name/-n`, `--url/-u` (`$GITEA_SERVER_URL`), `--token/-t` (`$GITEA_SERVER_TOKEN`), `--user` (`$GITEA_SERVER_USER`), `--password/--pwd` (`$GITEA_SERVER_PASSWORD`), `--otp` (`$GITEA_SERVER_OTP`), `--scopes` (`$GITEA_SCOPES`), `--ssh-key/-s`, `--ssh-agent-principal/-c`, `--ssh-agent-key/-a`, `--insecure/-i`, `--no-version-check/--nv`, `--helper/-j`, `--oauth/-o` (plus `--client-id`, `--redirect-url`).
- `edit, e` — interactive.
- `delete, rm <name>`
- `default [<login>]` — get or set the default login.
- `oauth-refresh [<login>]` — refresh an OAuth token (opens browser if the refresh token is also expired).
## `tea logout <name>`
Remove a stored login.
## `tea ssh-keys` (alias: `ssh-key`)
- `list, ls`
- `add <key-file>``--title/-t` (defaults to filename without extension).
- `delete, rm <key-id>``--confirm/-y`.