An issue was a Gitea row that happened to be cached locally: its identity was the tracker's number (42.md), its dependencies were tracker numbers (depends: [#12]), and a local issue existed only as a draft that push deleted on success. Nothing could be planned or tracked without a tracker. Split into layers, with knowledge flowing one way: skills/issue DOMAIN what an issue is: format, validation, dep graph ^ offline; stdlib imports only, no subprocess | imports skills/sync BRIDGE map.py md <-> Gitea JSON, pure, no I/O _gitea.py login pin, api, pagination, filters skills/use REFERENCE tea CLI docs for non-issue entities skills/issue never imports skills/sync. Delete the sync layer and the domain keeps working. Identity is now a slug derived from the title (wire-sqlc-appclick.md) and is stable across retitles and pushes. Tracker numbers live in a `gitea:` field, never in a file name and never in `depends:`; the pair is indexed in .remote.json, which is a cache over the files, not a second source of truth. Behavior changes: - Pushing is additive. The file is never deleted; it gains gitea:/url:/ synced: and origin: flips from local to gitea. `origin: local` is a durable state, not a pending one. - Pushes go in topological order so dependencies get numbers first. - The dependency graph is computed offline from `depends:` metadata; body prose is passed through unchanged in both directions rather than being rewritten between slugs and #N. - `origin` is domain-owned (whether work exists elsewhere is a fact about the work); the handle and how to reach it stay with sync. Script moves: issue_get.py -> sync/pull.py issue_push.py -> sync/push.py issue_list.py -> sync/remote.py issue_index.py -> issue/issue_index.py _tea.py -> split into issue/issue.py, sync/map.py, sync/_gitea.py New: issue/issue_new.py, issue/issue_check.py, issue/issue_tree.py, and sync/comment.py — comment posting was the last issue operation still hand-rolled through raw `tea api`. references/issue-format.md moves to skills/issue/references/format.md; label hex colors move out of it into map.py, since a color is how a tracker paints a chip, not what an issue is. Verified: offline path end to end (new, check, tree, index, push --dry-run) and read-only against Gitea (remote listing, pull with mapping, comment guard). Write paths of push.py and comment.py are not exercised here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.4 KiB
name: use
description: Reference docs for the tea CLI — Gitea's command-line client. Load when the user asks about Gitea repos, pulls, releases, milestones, labels, actions, webhooks, or other Gitea entities, to look up the right tea command and flags. Always write the login as the literal placeholder --login "$GITEA_LOGIN" — the tea-guard hook substitutes the operator-pinned login; set it with /tea:auth. Issues are NOT handled here: use /tea:issue to work on them and /tea:sync to move them to and from Gitea.
/tea:use — tea CLI reference
Reference material for the tea CLI (Gitea's official command-line client).
Use these docs to look up commands, flags, filters, and output fields before
running tea via Bash.
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 |
|---|---|
/tea:issue |
issues as units of work — create, read, grep, validate, dependency graph. Offline. |
/tea:sync |
moving issues between the local store and Gitea — pull, push, comment. |
This skill covers everything else Gitea has: pulls, releases, milestones, labels, repos, branches, actions, webhooks, notifications, times.
Login: always write the placeholder, never a name (enforced)
Every tea invocation that touches Gitea MUST carry the login as the literal
placeholder --login "$GITEA_LOGIN" (or -l "$GITEA_LOGIN"). Do not
substitute an actual login name yourself.
The tea-guard PreToolUse hook enforces this and resolves it:
- no
--login→ blocked. --login "$GITEA_LOGIN"→ the hook reads the operator's pinned login from.claude/settings.local.json(env.GITEA_LOGIN) at call time and rewrites the command to use that literal before it runs.--login <some-name>or any other variable → blocked. You may not choose the login; only the operator does (via/tea:auth).- no login pinned → blocked with a pointer to run
/tea:auth.
Why: without an explicit login tea silently falls back to the machine's
default (possibly the user's personal account), and a login you pick may be
the wrong identity. Pinning is the operator's decision; the hook guarantees it.
The pin takes effect immediately — no restart. Only tea logins list and
tea --version/--help are exempt from the guard.
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 with the placeholder login, e.g.
tea pulls list --login "$GITEA_LOGIN" --repo owner/repo --state open. (The hook rewrites"$GITEA_LOGIN"to the operator-pinned login.)
tea auto-detects owner/repo from $PWD inside a git repo; otherwise pass
--repo owner/repo (or -r). Login is not auto-detected — it is pinned
per-project by the operator (see /tea:auth) and injected by the guard.
Config lives in $XDG_CONFIG_HOME/tea.
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 moved to
../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 $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 /tea:sync rather than
hand-rolling their JSON. 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 --login "$GITEA_LOGIN" \ -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" --login "$GITEA_LOGIN")
are still fine via entity commands. Always the placeholder, never a login name.
Tips
- Pass
-o jsonfor structured output when parsing programmatically. - Use
--fields, -fto narrow columns. - Pagination:
--page, -p <n>and--limit, --lm <n>(defaults 1 / 30). - If a
teacommand is blocked bytea-guard: either you forgot--login "$GITEA_LOGIN", you wrote a literal login name instead of the placeholder (not allowed — let the guard substitute), or no login is pinned (run/tea:auth).