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:
naudachu
2026-05-30 15:54:48 +05:00
parent bb4ad963ee
commit b3db734cd8
12 changed files with 217 additions and 30 deletions
+47
View File
@@ -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