Files
marketplace/skills/use/SKILL.md
T
naudachu 335b0bbd54 feat: local issue cache and draft-then-push workflow
Replace fetch_issue.py with four scripts around a flat, greppable cache in
tmp/issues/. Planning stays offline and issues reach Gitea in one push:

- issue_get.py: fetch by key or by filter (--milestone/--label/-q). The list
  endpoint carries issue bodies, so a whole milestone costs one request per 50
  issues. Gitea silently ignores an unresolvable milestones= filter and returns
  the entire backlog, so the milestone is resolved up front and every returned
  issue is re-checked locally. --deps walks the dependency graph downwards via
  the structured sections plus native dependencies and writes tree-<slug>.md.
- issue_push.py: validate a local draft against the canonical format, create
  missing labels with the right colors and exclusivity, POST, delete the draft.
- issue_list.py: discovery to stdout, writes nothing.
- issue_index.py: rebuild INDEX.md from what is on disk.

Files use one metadata field per line with inline lists so plain grep works
without a parser. This is a cache and a drafting area, not a mirror: no drift
tracking, no sync back.

Projects are not fetchable — the projects API is 404 on Gitea 1.26; documented
alongside the milestone caveat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:35:38 +05:00

11 KiB

name, description
name description
use Reference docs for the `tea` CLI — Gitea's command-line client. Load when the user asks about Gitea repos, issues, pulls, releases, actions, 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.

/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.

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

  1. Identify the entity in the request: issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comments, notifications, etc.
  2. Find the matching command in the index below.
  3. Run it via Bash with the placeholder login, e.g. tea issues 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.

Issues: work on local files, not on live tea calls

Never run tea issues <n> -o json or tea api .../issues/<n> to read an issue — the full JSON payload (avatars, nested user objects, every comment body) lands in your context whether you need it or not. Use the scripts in <skill-base-dir>/scripts/. They pull issues into a flat, greppable cache under $PWD/tmp/issues/ and print only a compact index.

This is a cache, not a mirror: nothing tracks drift, nothing syncs back. Refetch when you need current data; the fetched: field tells you the age.

Script Network What it does
issue_get.py <key…> or issue_get.py --milestone M | --label L | -q TEXT yes fetch issue(s) → tmp/issues/<n>.md (+ <n>.comments.md, tree-<slug>.md)
issue_list.py [--state] [--label] [--milestone] [-q TEXT] yes discovery: one line per issue to stdout, writes nothing
issue_push.py <draft…>|--all [--keep] [--dry-run] yes validate a draft, create labels, POST the issue, delete the draft
issue_index.py no rebuild tmp/issues/INDEX.md (auto after get/push)
tmp/issues/INDEX.md            table of everything cached — read this first
tmp/issues/42.md               metadata block + `# Title` + body
tmp/issues/42.comments.md      comments (only with --comments)
tmp/issues/tree-40.md          dependency map (only with --deps)
tmp/issues/drafts/<slug>.md    issues not yet created in Gitea

Key forms for <key>: 42, #42, owner/repo#42, or a full issue URL. Repo defaults to the current directory's git remote (add --repo owner/repo outside one). No --login on any script call: they resolve the operator's pin from .claude/settings.local.json themselves — same source as the tea-guard hook. No pin → exit with a pointer to /tea:auth.

Do not loop issue_get.py over numbers to pull a group — pass the filter. The list endpoint carries the issue bodies, so a milestone costs one request per 50 issues, not one per issue:

python3 <skill-base-dir>/scripts/issue_get.py --milestone 6          # id or title
python3 <skill-base-dir>/scripts/issue_get.py --milestone v0.2 --deps
python3 <skill-base-dir>/scripts/issue_get.py --label type/bug --label comp/hooks --state all
python3 <skill-base-dir>/scripts/issue_get.py -q sqlc --limit 20

Filters AND together; --state defaults to open; --limit defaults to 100. Keys and filters are mutually exclusive. --comments stays single-issue — loop over the numbers when a whole thread set is needed.

Two traps this handles for you:

  • Gitea silently ignores an unresolvable milestone filter and returns the whole backlog. The script resolves the milestone first (exits listing the real ones if it does not exist) and re-checks every returned issue locally. Never trust a raw tea api ...issues?milestones=X call for this.
  • Projects are not fetchable. The projects API is not exposed (404 on Gitea 1.26 for repos/…/projects, orgs/…/projects, projects/{id}). Use milestones or labels; project columns live in the web UI only.

After a filtered fetch, INDEX.md carries a milestone column, and the cache is greppable by it: grep -l 'milestone: v0.2' tmp/issues/*.md.

Working a feature as one document

--deps walks the dependency graph downwards — the structured ## Depends on and ## Issues sections plus Gitea's native dependencies. Prose #N mentions are ignored on purpose, or the walk would drag in half the backlog. Comments are not fetched during a walk (loop over the numbers if you need them).

python3 <skill-base-dir>/scripts/issue_get.py 40 --deps      # feature + children

Read tree-40.md once for the shape (a filtered fetch writes one forest, tree-<slug>.md), then grep the files as one document:

grep -ln 'depends:.*#42' tmp/issues/*.md        # who depends on #42 (upwards)
grep -l  'labels:.*type/bug' tmp/issues/*.md    # all cached bugs
grep -A3 '## Acceptance criteria' tmp/issues/4*.md
grep -c  '^- \[ \]' tmp/issues/42.md            # open checkboxes

Metadata is written one field per line with inline lists (labels: [a, b]) precisely so plain grep works without a parser.

Creating issues: draft locally, push once

During planning write drafts to tmp/issues/drafts/<slug>.md — no network, no tea call. A draft is the metadata block with labels: only, plus the canonical body:

---
labels: [type/task, tech/sql]
---
# Wire sqlc into the appclick repo layer

## Summary
...

When the plan is agreed, issue_push.py validates the format (exactly one type/*, English title without a type prefix, ## Summary / ## Spec / ## Acceptance criteria present), creates missing labels with the right colors and exclusivity, POSTs, prints the URL and deletes the draft — the issue lives in Gitea now. --keep writes tmp/issues/<n>.md instead; --dry-run validates without touching the network. Guided procedure: /tea:issue.

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
  • ISSUE FORMAT — canonical issue format: label namespaces (type/*, severity/* exclusive; tech/*, comp/* free), types bug|task|refactor|test|feature|draft, templates, dependencies, title and language rules. MANDATORY whenever creating or editing an issue; the /tea:issue skill is the guided procedure for it.

Rich payloads — write to $PWD/tmp/ first, then tea api

Entity subcommands (tea comment, tea issues create, tea pulls 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.

Issue creation is already wrapped: use issue_push.py (above) instead of hand-rolling the JSON. The procedure below covers everything else — comments, pulls, releases, and PATCHes to existing issues.

Procedure

  1. Ensure the target dir exists: mkdir -p tmp/{kind} where {kind} is comment, issue, 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:
    mkdir -p tmp/comment
    cat > tmp/comment/issue-60.json <<'EOF'
    {"body": "## Heading\n\nMulti-line markdown with `code`, | tables |, and ```fences```."}
    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/comment/issue-60.json.
  3. POST with tea api, passing the file with -d @<path>:
    tea api --login "$GITEA_LOGIN" \
      -X POST -d @tmp/comment/issue-60.json \
      repos/{owner}/{repo}/issues/60/comments
    
  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
Comment on issue/PR POST repos/{owner}/{repo}/issues/{n}/comments
Edit comment PATCH repos/{owner}/{repo}/issues/comments/{id}
Create issue POST repos/{owner}/{repo}/issues
Edit issue/PR body or title PATCH repos/{owner}/{repo}/issues/{n}
Create PR POST repos/{owner}/{repo}/pulls
Create release POST repos/{owner}/{repo}/releases

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 json for structured output when parsing programmatically.
  • Use --fields, -f to narrow columns.
  • Pagination: --page, -p <n> and --limit, --lm <n> (defaults 1 / 30).
  • If a tea command is blocked by tea-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).