Restructure tea skill into a plugin with a mandatory-login guard
Convert the standalone `tea` skill into a skills-dir plugin so commands are
namespaced and an enforcement hook can ship with it:
- /tea:login — pin the project Gitea login into .claude/settings.local.json
- /tea:use — tea CLI reference (was the old root SKILL.md), with the
login rule slimmed since the hook now enforces it
- hooks/tea-guard.sh — PreToolUse(Bash) guard: blocks any `tea` command that
touches Gitea unless it carries --login and $GITEA_LOGIN is set. Exempts
`tea logins list` and `tea --version/--help` so /tea:login can bootstrap.
References moved under skills/use/references/. `claude plugin validate` passes;
guard unit-tested across allow/block cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "tea",
|
||||||
|
"description": "Gitea CLI (tea) reference plus a mandatory-login guard. Ships /tea:login (pin a login) and /tea:use (command reference), and a PreToolUse hook that blocks any tea command that would touch Gitea without --login.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "naudachu"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["gitea", "tea", "cli", "git", "login-guard"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
.docs/
|
||||||
|
tmp/
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
name: gitea-docs
|
|
||||||
description: 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.
|
|
||||||
---
|
|
||||||
|
|
||||||
# gitea-docs
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
1. Identify the entity in the user 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, e.g. `tea issues list --repo owner/repo --state open`.
|
|
||||||
|
|
||||||
`tea` auto-detects owner/repo/login from `$PWD` when inside a git repo; otherwise pass `--repo owner/repo` (or `-r`) explicitly. Config lives in `$XDG_CONFIG_HOME/tea`.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/tea-guard.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# tea-guard — PreToolUse hook for Bash commands.
|
||||||
|
#
|
||||||
|
# Enforces, deterministically, the one rule the prose in /tea:use cannot:
|
||||||
|
# every `tea` invocation that touches Gitea MUST carry --login "$GITEA_LOGIN",
|
||||||
|
# and $GITEA_LOGIN must be set. Without this, `tea` silently falls back to the
|
||||||
|
# machine's default login (often the user's personal account) and writes under
|
||||||
|
# the wrong identity.
|
||||||
|
#
|
||||||
|
# Exit codes: 0 = allow, 2 = block (stderr is fed back to Claude as the reason).
|
||||||
|
#
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
input="$(cat)"
|
||||||
|
|
||||||
|
# Extract tool_input.command from the hook payload (jq if present, else python3).
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
cmd="$(printf '%s' "$input" | /usr/bin/python3 -c 'import sys,json; print(json.load(sys.stdin).get("tool_input",{}).get("command","") or "")' 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not a `tea` command → not our concern.
|
||||||
|
if ! printf '%s' "$cmd" | grep -Eq '(^|[;&|(]|[[:space:]])tea([[:space:]]|$)'; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Whitelist: login enumeration + meta. These do not act under any identity and
|
||||||
|
# are needed by /tea:login itself (which runs while $GITEA_LOGIN may be unset).
|
||||||
|
if printf '%s' "$cmd" | grep -Eq 'tea[[:space:]]+(logins[[:space:]]+(list|ls)|--version|-v|--help|help)([[:space:]]|$)'; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Require an explicit --login / -l on the invocation.
|
||||||
|
if ! printf '%s' "$cmd" | grep -Eq '(--login|[[:space:]]-l)([[:space:]=])'; then
|
||||||
|
echo "tea-guard: BLOCKED — every 'tea' command must include --login \"\$GITEA_LOGIN\" (or -l). Run /tea:login to pin the project login, then retry. Only 'tea logins list' and 'tea --version/--help' are exempt." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Require $GITEA_LOGIN to be set in the environment.
|
||||||
|
if [ -z "${GITEA_LOGIN:-}" ]; then
|
||||||
|
echo "tea-guard: BLOCKED — \$GITEA_LOGIN is empty/unset, so --login \"\$GITEA_LOGIN\" would expand to nothing and tea would fall back to the default login. Run /tea:login to pin a login (writes .claude/settings.local.json), then restart the session so it is exported." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: login
|
||||||
|
description: Pin the Gitea login used by the tea CLI in this project. Run when $GITEA_LOGIN is unset, when the tea-guard hook blocks a command demanding a login, or when the user types /tea:login. Enumerates available logins, lets the user pick one, and persists it to .claude/settings.local.json.
|
||||||
|
---
|
||||||
|
|
||||||
|
# /tea:login — pin the project Gitea login
|
||||||
|
|
||||||
|
Goal: select exactly one `tea` login for this project and persist it to
|
||||||
|
`.claude/settings.local.json` under `env.GITEA_LOGIN`, so every later `tea`
|
||||||
|
call can pass `--login "$GITEA_LOGIN"`. The `tea-guard` hook blocks every
|
||||||
|
Gitea-touching `tea` command until this is done — this command is the
|
||||||
|
remediation it points to.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Enumerate logins (allowed by the guard even without `--login`):
|
||||||
|
`tea logins list -o json`
|
||||||
|
2. **No logins:** stop and ask the user to run `tea logins add` themselves —
|
||||||
|
it is interactive (prompts for URL/token). Do not run it for them.
|
||||||
|
3. **One login:** propose pinning it; confirm with the user before writing.
|
||||||
|
4. **Several logins:** use `AskUserQuestion` to let the user pick. Show each
|
||||||
|
login's `name`, `user`, and `url` so the choice is unambiguous.
|
||||||
|
5. Merge the chosen name into `.claude/settings.local.json` under `env` —
|
||||||
|
do not clobber other keys:
|
||||||
|
```json
|
||||||
|
{ "env": { "GITEA_LOGIN": "<chosen-name>" } }
|
||||||
|
```
|
||||||
|
6. Tell the user: the updated `$GITEA_LOGIN` is only exported into Bash after a
|
||||||
|
**session restart**. Until they restart, pass the literal name explicitly:
|
||||||
|
`tea --login <chosen-name> ...`.
|
||||||
|
|
||||||
|
## Hard rules (identity safety)
|
||||||
|
|
||||||
|
- NEVER run commands that mutate logins or global login state:
|
||||||
|
`tea logins add/edit/delete/default`, `tea logout`. Read-only
|
||||||
|
`tea logins list` is the only allowed login command.
|
||||||
|
- If a `tea` call fails with a permission/scope error, report it to the user.
|
||||||
|
Do NOT try to fix it by switching to, or editing, a different login.
|
||||||
|
- If you ever see `no gitea login detected, falling back to login '...'`,
|
||||||
|
treat it as a hard failure: stop, do not act on the result, surface it.
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
name: use
|
||||||
|
description: 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. Every tea call must carry --login "$GITEA_LOGIN" (enforced by the tea-guard hook; set it with /tea:login).
|
||||||
|
---
|
||||||
|
|
||||||
|
# /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 is mandatory (enforced)
|
||||||
|
|
||||||
|
Every `tea` invocation that touches Gitea MUST include `--login "$GITEA_LOGIN"`
|
||||||
|
(or `-l "$GITEA_LOGIN"`). This is enforced by the **`tea-guard`** PreToolUse
|
||||||
|
hook — a `tea` command without `--login` is blocked before it runs. If
|
||||||
|
`$GITEA_LOGIN` is unset, run **`/tea:login`** to pin one.
|
||||||
|
|
||||||
|
Why: without `--login`, `tea` silently falls back to the machine's default
|
||||||
|
login (possibly the user's personal account) and writes under the wrong
|
||||||
|
identity. The login value is pinned per-project in `.claude/settings.local.json`
|
||||||
|
under `env.GITEA_LOGIN`. 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 login, e.g.
|
||||||
|
`tea issues list --login "$GITEA_LOGIN" --repo owner/repo --state open`.
|
||||||
|
|
||||||
|
`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 (see `/tea:login`). Config lives in `$XDG_CONFIG_HOME/tea`.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
```bash
|
||||||
|
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>`:
|
||||||
|
```bash
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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`, you forgot `--login` or
|
||||||
|
`$GITEA_LOGIN` is unset — add the flag or run `/tea:login`.
|
||||||
Reference in New Issue
Block a user