diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..4fc842c --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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"] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5718e41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.docs/ +tmp/ diff --git a/SKILL.md b/SKILL.md deleted file mode 100644 index 673ac6e..0000000 --- a/SKILL.md +++ /dev/null @@ -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 ` and `--limit, --lm ` (defaults 1 / 30). diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..7aec263 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/tea-guard.sh" + } + ] + } + ] + } +} diff --git a/hooks/tea-guard.sh b/hooks/tea-guard.sh new file mode 100755 index 0000000..d04c2f9 --- /dev/null +++ b/hooks/tea-guard.sh @@ -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 diff --git a/skills/login/SKILL.md b/skills/login/SKILL.md new file mode 100644 index 0000000..47b3259 --- /dev/null +++ b/skills/login/SKILL.md @@ -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": "" } } + ``` +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 ...`. + +## 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. diff --git a/skills/use/SKILL.md b/skills/use/SKILL.md new file mode 100644 index 0000000..25d9a7b --- /dev/null +++ b/skills/use/SKILL.md @@ -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}/.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 @`: + ```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 ` and `--limit, --lm ` (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`. diff --git a/references/tea/entities.md b/skills/use/references/tea/entities.md similarity index 100% rename from references/tea/entities.md rename to skills/use/references/tea/entities.md diff --git a/references/tea/helpers.md b/skills/use/references/tea/helpers.md similarity index 100% rename from references/tea/helpers.md rename to skills/use/references/tea/helpers.md diff --git a/references/tea/index.md b/skills/use/references/tea/index.md similarity index 100% rename from references/tea/index.md rename to skills/use/references/tea/index.md diff --git a/references/tea/misc.md b/skills/use/references/tea/misc.md similarity index 100% rename from references/tea/misc.md rename to skills/use/references/tea/misc.md diff --git a/references/tea/setup.md b/skills/use/references/tea/setup.md similarity index 100% rename from references/tea/setup.md rename to skills/use/references/tea/setup.md