b3db734cd8
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>
48 lines
2.1 KiB
Bash
Executable File
48 lines
2.1 KiB
Bash
Executable File
#!/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
|