Agent Orchestration
Hook
Hooks
What team-avatar runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add tmcfarlane/team-avatar > /plugin install team-avatar@team-avatar
Ships with team-avatar. Installing the plugin gets these hooks.
Where it lives
- hooks/guard-shell.shGitHub
Read the script
#!/usr/bin/env bash # beforeShellExecution hook — guards the agent's shell calls. # # Reads a JSON payload on stdin (fields: command, cwd, hook_event_name, ...) and prints a # JSON decision on stdout per Cursor's hook protocol: # {"continue": <bool>, "permission": "allow|deny|ask", "userMessage": "...", "agentMessage": "..."} # # Blocks (deny) clearly destructive commands and commits that contain the anti-patterns # Team Avatar agents are told never to introduce. # # Robustness notes (learned from validation on Cursor 3.8.23): # - Cursor runs hooks with the GUI app's PATH, which often LACKS /opt/homebrew/bin, so we # cannot assume `python3` or `jq` are on PATH. We try jq, then python3 at known absolute # paths, then /usr/bin/perl (always present on macOS). If the payload can't be parsed we # return "ask" (surface) rather than silently allowing. # - We cd into the payload's `cwd` before running git checks so `git diff --cached` resolves # in the agent's actual worktree. # - Pair with "failClosed": true in hooks.json so a script error denies instead of allowing. # # Env switches: # OMC_HOOKS_OBSERVE=1 run non-blocking: downgrade every deny to allow + an informational note. # OMC_HOOKS_DEBUG=1 append each invocation's command to hooks/last-invocation.log (proves firing). set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INPUT="$(cat)" # --- Robust JSON string-field extraction (no reliance on PATH) --- json_field() { local field="$1" data="$2" out py if command -v jq >/dev/null 2>&1; then if out="$(printf '%s' "$data" | jq -r --arg f "$field" '.[$f] // empty' 2>/dev/null)"; then printf '%s' "$out"; return 0 fi fi for py in python3 /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do if "$py" -c '' >/dev/null 2>&1; then if out="$(printf '%s' "$data" | "$py" -c "import sys,json;print(json.load(sys.stdin).get('$field',''))" 2>/dev/null)"; then printf '%s' "$out"; return 0 fi fi done if [ -x /usr/bin/perl ]; then printf '%s' "$data" | /usr/bin/perl -0777 -ne 'if(/"'"$field"'"\s*:\s*"((?:[^"\\]|\\.)*)"/s){my $x=$1;$x=~s/\\n/\n/g;$x=~s/\\t/\t/g;$x=~s/\\(.)/$1/g;print $x}' 2>/dev/null return 0 fi return 1 } COMMAND="$(json_field command "$INPUT")"; PARSE_RC=$? CWD="$(json_field cwd "$INPUT")" if [ "${OMC_HOOKS_DEBUG:-}" = "1" ]; then printf '%s\t%s\n' "$(date '+%FT%T' 2>/dev/null)" "${COMMAND:-<unparsed>}" >> "$HERE/last-invocation.log" 2>/dev/null || true fi allow() { printf '{"continue": true, "permission": "allow"}\n'; exit 0; } ask() { printf '{"continue": true, "permission": "ask", "userMessage": "%s", "agentMessage": "%s"}\n' "$1" "$1"; exit 0; } decide() { local reason="$1" if [ "${OMC_HOOKS_OBSERVE:-}" = "1" ]; then printf '{"continue": true, "permission": "allow", "agentMessage": "[observe] would block: %s"}\n' "$reason" exit 0 fi printf '{"continue": false, "permission": "deny", "userMessage": "Blocked by oh-my-cursor: %s", "agentMessage": "Denied by policy: %s. Adjust .cursor/hooks/guard-shell.sh or set OMC_HOOKS_OBSERVE=1 to override."}\n' "$reason" "$reason" exit 0 } # Surface for human approval (deterministic "hold for review"). Used where the auto-review # classifier is unreliable, e.g. reading credential/secret files. hold() { local reason="$1" if [ "${OMC_HOOKS_OBSERVE:-}" = "1" ]; then printf '{"continue": true, "permission": "allow", "agentMessage": "[observe] would hold for review: %s"}\n' "$reason" exit 0 fi printf '{"continue": true, "permission": "ask", "userMessage": "Held by oh-my-cursor for review: %s", "agentMessage": "Held for review: %s. Approve only if this credential/secret access is intended."}\n' "$reason" "$reason" exit 0 } # Received a payload but couldn't parse the command -> surface instead of silently allowing. if [ -n "$INPUT" ] && { [ "$PARSE_RC" -ne 0 ] || { [ -z "$COMMAND" ] && printf '%s' "$INPUT" | grep -q '"command"'; }; }; then [ "${OMC_HOOKS_OBSERVE:-}" = "1" ] && allow ask "oh-my-cursor guard could not parse the shell command; review before running" fi [ -z "$COMMAND" ] && allow # Resolve git checks in the agent's real worktree. if [ -n "$CWD" ] && [ -d "$CWD" ]; then cd "$CWD" 2>/dev/null || true; fi # --- Destructive command denylist (unambiguous, always guarded) --- case "$COMMAND" in *"rm -rf /"*|*"rm -rf ~"*|*"rm -rf /*"*|*":(){ :|:& };:"*) decide "destructive filesystem command" ;; *"git push"*"--force"*"main"*|*"git push"*"--force"*"master"*|*"git push -f"*"main"*|*"git push -f"*"master"*) decide "force-push to a protected branch" ;; *"git reset --hard origin/main"*|*"git reset --hard origin/master"*) decide "hard reset of a shared branch" ;; *"chmod -R 777 /"*|*"mkfs"*|*"dd if="*"of=/dev/"*) decide "system-level destructive command" ;; esac # --- Commit anti-pattern guard --- case "$COMMAND" in *"git commit"*) if [ -x "$HERE/pre-commit-check.sh" ]; then if ! OUT="$("$HERE/pre-commit-check.sh" 2>&1)"; then decide "commit contains forbidden anti-patterns ($(printf '%s' "$OUT" | tr '\n' ' ' | cut -c1-160))" fi fi ;; esac # --- Credential / secret access guard (hold for review) --- # The permissions.json auto-review classifier is best-effort and let `cat ~/.ssh/config` # through during E2E test C2, so enforce a deterministic hold here regardless of the classifier. case "$COMMAND" in *"/.ssh/"*|*"/.aws/"*|*"/.gnupg/"*|*"id_rsa"*|*"id_dsa"*|*"id_ecdsa"*|*"id_ed25519"*|*".pem"*|*".netrc"*|*".pgpass"*|*"/.kube/config"*|*"kubeconfig"*|*"/.docker/config.json"*|*"/.config/gcloud/"*|*".aws/credentials"*) hold "command accesses a credential/secret file" ;; esac allow - hooks/post-edit-lint.shGitHub
Read the script
#!/usr/bin/env bash # afterFileEdit hook — lints a file right after the agent edits it. # # Reads a JSON payload on stdin (fields: file_path, edits, hook_event_name, ...). # afterFileEdit is INFORMATIONAL: Cursor ignores the output, so this hook only surfaces # lint findings; it cannot (and should not) block the edit. # # Uses the same PATH-robust JSON parsing as guard-shell.sh (Cursor runs hooks with the GUI # app PATH, which may lack python3/jq). Set OMC_HOOKS_DEBUG=1 to log invocations. set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INPUT="$(cat)" json_field() { local field="$1" data="$2" out py if command -v jq >/dev/null 2>&1; then if out="$(printf '%s' "$data" | jq -r --arg f "$field" '.[$f] // empty' 2>/dev/null)"; then printf '%s' "$out"; return 0 fi fi for py in python3 /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do if "$py" -c '' >/dev/null 2>&1; then if out="$(printf '%s' "$data" | "$py" -c "import sys,json;print(json.load(sys.stdin).get('$field',''))" 2>/dev/null)"; then printf '%s' "$out"; return 0 fi fi done if [ -x /usr/bin/perl ]; then printf '%s' "$data" | /usr/bin/perl -0777 -ne 'if(/"'"$field"'"\s*:\s*"((?:[^"\\]|\\.)*)"/s){my $x=$1;$x=~s/\\n/\n/g;$x=~s/\\(.)/$1/g;print $x}' 2>/dev/null return 0 fi return 1 } FILE="$(json_field file_path "$INPUT")" if [ "${OMC_HOOKS_DEBUG:-}" = "1" ]; then printf '%s\tedit\t%s\n' "$(date '+%FT%T' 2>/dev/null)" "${FILE:-<unparsed>}" >> "$HERE/last-invocation.log" 2>/dev/null || true fi [ -z "$FILE" ] && exit 0 [ -f "$FILE" ] || exit 0 ext="${FILE##*.}" case "$ext" in ts|tsx|js|jsx) command -v npx >/dev/null 2>&1 && npx eslint --no-error-on-unmatched-pattern "$FILE" 2>/dev/null || true ;; py) if command -v ruff >/dev/null 2>&1; then ruff check "$FILE" 2>/dev/null || true elif command -v flake8 >/dev/null 2>&1; then flake8 "$FILE" 2>/dev/null || true fi ;; rs) command -v cargo >/dev/null 2>&1 && cargo clippy --message-format=short 2>/dev/null || true ;; go) command -v golangci-lint >/dev/null 2>&1 && golangci-lint run "$FILE" 2>/dev/null || true ;; esac exit 0 - hooks/pre-commit-check.shGitHub
Read the script
#!/usr/bin/env bash # Pre-commit hook: enforce hard constraints before any commit. # Place in .cursor/hooks/ and configure via Cursor's hook system. # # Catches anti-patterns that agents are instructed never to use, # providing system-level enforcement as a safety net. set -euo pipefail STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) if [ -z "$STAGED_FILES" ]; then exit 0 fi VIOLATIONS=0 check_pattern() { local pattern="$1" local label="$2" local file="$3" if grep -qn "$pattern" "$file" 2>/dev/null; then echo "VIOLATION in $file: $label" grep -n "$pattern" "$file" 2>/dev/null | head -5 echo "" VIOLATIONS=$((VIOLATIONS + 1)) fi } for file in $STAGED_FILES; do if [ ! -f "$file" ]; then continue fi ext="${file##*.}" case "$ext" in ts|tsx|js|jsx) check_pattern "as any" "Type safety: 'as any' suppresses type checking" "$file" check_pattern "@ts-ignore" "Type safety: '@ts-ignore' suppresses errors" "$file" check_pattern "@ts-expect-error" "Type safety: '@ts-expect-error' suppresses errors" "$file" check_pattern "catch.*{[[:space:]]*}" "Error handling: empty catch block" "$file" ;; py) check_pattern "except:$" "Error handling: bare except clause" "$file" check_pattern "pass$" "Error handling: potential empty except/pass" "$file" ;; esac done if [ "$VIOLATIONS" -gt 0 ]; then echo "Found $VIOLATIONS constraint violation(s). Fix before committing." exit 1 fi
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withteam-avatar
Team Avatar: a universal agent orchestrator for Claude Code, Codex, and Cursor. Nine Avatar characters, 58 engineering skills. pstack with Avatar characters on top.
Get the whole plugin
Stats
110
Stars
14
Forks
Active
Maintenance
TypeScript
Language
MIT
License
5h ago
Last commit
8mo ago
Created
6h ago
Added

