Last active
August 16, 2025 22:57
-
-
Save dgnsrekt/884ff14296c9ae03bdf2d8bd0f3281c2 to your computer and use it in GitHub Desktop.
Claude hooks I can't live without.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| PreToolUse hook to block hook bypassing and enforce Go quality standards. | |
| This hook prevents bypassing code quality checks and ensures proper Go | |
| development practices are followed. Sends security notifications when | |
| bypass attempts are detected. | |
| """ | |
| import json | |
| import sys | |
| # Import shared notification utilities | |
| from strategic_notifications import ( | |
| log, | |
| get_project_context, | |
| send_strategic_notification, | |
| play_audio | |
| ) | |
| HOOK_NAME = "block-skip-hooks" | |
| def send_security_alert( | |
| violation_type: str, | |
| command: str, | |
| session_id: str, | |
| cwd: str, | |
| details: str | |
| ) -> None: | |
| """ | |
| Send a high-priority security notification about hook bypass attempts. | |
| Args: | |
| violation_type: Type of security violation (e.g., "SKIP bypass", "no-verify bypass") | |
| command: The blocked command | |
| session_id: Claude Code session ID | |
| cwd: Current working directory | |
| details: Additional details about the violation | |
| """ | |
| try: | |
| project_info = get_project_context(cwd, HOOK_NAME) | |
| message = f"""Security Alert: Hook Bypass Attempt | |
| **Violation**: {violation_type} | |
| **Command**: {command} | |
| **Details**: {details} | |
| This attempt was BLOCKED to maintain code quality standards. | |
| All hook bypass attempts are logged and monitored.""" | |
| success = send_strategic_notification( | |
| message=message, | |
| title="🚨 Strategic Core: Security Alert", | |
| priority="high", | |
| tags=["rotating_light", "warning", "security", "strategic-core"], | |
| topic_type="alerts", | |
| project_info=project_info, | |
| session_id=session_id, | |
| hook_name=HOOK_NAME | |
| ) | |
| if success: | |
| log("Security alert notification sent", HOOK_NAME) | |
| # Play gutter-trash audio for security violations | |
| play_audio("gutter-trash.mp3", HOOK_NAME) | |
| else: | |
| log("Failed to send security alert notification", HOOK_NAME) | |
| except Exception as e: | |
| log(f"Error sending security alert: {e}", HOOK_NAME) | |
| def main() -> None: | |
| """Check and block inappropriate git and development commands.""" | |
| try: | |
| # Read the hook input from stdin | |
| hook_input = json.loads(sys.stdin.read()) | |
| # Extract tool information | |
| tool_input = hook_input.get("tool_input", {}) | |
| session_id = hook_input.get("session_id", "unknown") | |
| cwd = hook_input.get("cwd", "") | |
| # Get the command being executed | |
| command = tool_input.get("command", "") | |
| # Block git commits with SKIP environment variable | |
| if "git commit" in command and "SKIP=" in command: | |
| error_message = ( | |
| "🚫 Blocked: Using SKIP to bypass pre-commit hooks is not allowed.\n\n" | |
| "STOP and explain why you're trying to bypass quality checks:\n" | |
| "- What specific linting errors are you facing?\n" | |
| "- Have you tried fixing the actual issues?\n" | |
| "- Why do you think bypassing is necessary?\n\n" | |
| "Instead of skipping, fix the real issues:\n" | |
| "1. 🔧 Fix gofmt formatting (run: gofmt -w .)\n" | |
| "2. 📝 Add periods to comment endings\n" | |
| "3. 🚀 Address golangci-lint warnings properly\n" | |
| "4. 🔍 Fix go vet concerns\n\n" | |
| "Quality standards exist to maintain Strategic Core v2's excellence." | |
| ) | |
| # Send security alert | |
| send_security_alert( | |
| violation_type="SKIP environment variable bypass", | |
| command=command, | |
| session_id=session_id, | |
| cwd=cwd, | |
| details="Attempted to use SKIP= to bypass pre-commit hooks" | |
| ) | |
| sys.stdout.write( | |
| json.dumps({"decision": "block", "message": error_message}) + "\n" | |
| ) | |
| return | |
| # Block --no-verify git commits | |
| if "git commit" in command and (" -n" in command or "--no-verify" in command): | |
| bypass_flag = "-n" if " -n" in command else "--no-verify" | |
| error_message = ( | |
| f"🚫 Blocked: Using {bypass_flag} to bypass hooks is not allowed.\n\n" | |
| f"STOP and explain why you're trying to bypass quality checks:\n" | |
| f"- What specific linting errors are you facing?\n" | |
| f"- Have you tried fixing the actual issues?\n" | |
| f"- Why do you think bypassing is necessary?\n\n" | |
| f"Instead of using {bypass_flag}, fix the real issues:\n" | |
| f"1. 🔧 Fix gofmt formatting (run: gofmt -w .)\n" | |
| f"2. 📝 Add periods to comment endings\n" | |
| f"3. 🚀 Address golangci-lint warnings properly\n" | |
| f"4. 🔍 Fix go vet concerns\n\n" | |
| f"Git hooks ensure Go code quality for Strategic Core v2." | |
| ) | |
| # Send security alert | |
| bypass_flag = "-n" if " -n" in command else "--no-verify" | |
| send_security_alert( | |
| violation_type=f"Git {bypass_flag} bypass", | |
| command=command, | |
| session_id=session_id, | |
| cwd=cwd, | |
| details=f"Attempted to use {bypass_flag} flag to bypass git hooks" | |
| ) | |
| sys.stdout.write( | |
| json.dumps({"decision": "block", "message": error_message}) + "\n" | |
| ) | |
| return | |
| # Block modification of linting/quality config files | |
| config_files = [".golangci.yml", ".pre-commit-config.yaml"] | |
| for config_file in config_files: | |
| if any(pattern in command for pattern in [ | |
| f"Edit.*{config_file}", | |
| f"Write.*{config_file}", | |
| f"MultiEdit.*{config_file}", | |
| f"vim {config_file}", | |
| f"nano {config_file}", | |
| f"echo.*{config_file}", | |
| f"sed.*{config_file}", | |
| ]): | |
| error_message = ( | |
| f"🚫 Blocked: Modification of {config_file} is not allowed.\n\n" | |
| f"Instead of modifying quality control files, you should:\n" | |
| f"1. 🔧 Fix the actual code issues (gofmt, periods in comments, etc.)\n" | |
| f"2. 📝 Address linting warnings properly\n" | |
| f"3. 🚀 Write better code that passes quality checks\n\n" | |
| f"If you're trying to bypass quality checks, STOP and explain:\n" | |
| f"- What specific linting issues are you facing?\n" | |
| f"- Why do you think modifying {config_file} is necessary?\n" | |
| f"- What's the proper way to fix the underlying code issues?\n\n" | |
| f"Quality standards exist to maintain Strategic Core v2's excellence." | |
| ) | |
| # Send security alert for config modification attempts | |
| send_security_alert( | |
| violation_type="Config file modification attempt", | |
| command=command, | |
| session_id=session_id, | |
| cwd=cwd, | |
| details=f"Attempted to modify {config_file} instead of fixing code issues" | |
| ) | |
| sys.stdout.write( | |
| json.dumps({"decision": "block", "message": error_message}) + "\n" | |
| ) | |
| return | |
| # Block potentially dangerous Go commands | |
| dangerous_patterns = [ | |
| ("go mod edit -replace", "🚫 Blocked: Direct go.mod replacement editing"), | |
| ( | |
| "go clean -modcache", | |
| "🚫 Blocked: Clearing module cache without confirmation", | |
| ), | |
| ("rm -rf vendor", "🚫 Blocked: Direct vendor directory removal"), | |
| ] | |
| for pattern, message in dangerous_patterns: | |
| if pattern in command: | |
| # Send security alert for dangerous Go commands | |
| send_security_alert( | |
| violation_type="Dangerous Go command", | |
| command=command, | |
| session_id=session_id, | |
| cwd=cwd, | |
| details=f"Attempted to execute potentially harmful command: {pattern}" | |
| ) | |
| sys.stdout.write( | |
| json.dumps( | |
| { | |
| "decision": "block", | |
| "message": f"{message}\n\nUse proper Go module commands instead.", | |
| } | |
| ) | |
| + "\n" | |
| ) | |
| return | |
| # Warn about potential Go anti-patterns (no alerts, just warnings) | |
| warning_patterns = [ | |
| ("go get -u all", "⚠️ Warning: Updating all dependencies at once"), | |
| ("go build -tags", "⚠️ Warning: Using build tags"), | |
| ] | |
| for pattern, message in warning_patterns: | |
| if pattern in command: | |
| log(message, HOOK_NAME) | |
| # Allow all other commands | |
| sys.stdout.write(json.dumps({"decision": "approve"}) + "\n") | |
| except Exception as e: | |
| # On error, allow the command but log the issue | |
| log(f"Hook error: {e}", HOOK_NAME) | |
| log(f"Exception type: {type(e).__name__}", HOOK_NAME) | |
| sys.stdout.write(json.dumps({"decision": "approve"}) + "\n") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| PostToolUse hook to format Go files after Write, Edit, or MultiEdit operations. | |
| This hook automatically runs gofmt and goimports on Go files to ensure | |
| consistent code formatting and imports organization. | |
| """ | |
| import json | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| def log(message: str) -> None: | |
| """Print a message to stderr to avoid interfering with hook output.""" | |
| print(f"[format-go-hook] {message}", file=sys.stderr) | |
| def main() -> None: | |
| """Format Go files after tool operations.""" | |
| try: | |
| # Read the hook input from stdin | |
| hook_input = json.loads(sys.stdin.read()) | |
| # Extract tool information | |
| tool_name = hook_input.get("tool_name", "") | |
| tool_input = hook_input.get("tool_input", {}) | |
| log(f"Processing {tool_name} tool") | |
| # Get the file path based on the tool | |
| file_path = None | |
| if tool_name in ["Write", "Edit", "MultiEdit"]: | |
| file_path = tool_input.get("file_path", "") | |
| if not file_path: | |
| log("No file path found, skipping") | |
| return | |
| # Check if it's a Go file | |
| if not file_path.endswith(".go"): | |
| log(f"Skipping non-Go file: {file_path}") | |
| return | |
| # Get the project directory from environment | |
| project_dir = hook_input.get("cwd", ".") | |
| # Convert to Path object for easier handling | |
| file_path = Path(file_path) | |
| if not file_path.is_absolute(): | |
| file_path = Path(project_dir) / file_path | |
| # Check if file exists | |
| if not file_path.exists(): | |
| log(f"File does not exist: {file_path}") | |
| return | |
| log(f"Formatting Go file: {file_path}") | |
| # Run gofmt formatter | |
| try: | |
| log("Running gofmt formatter...") | |
| result = subprocess.run( | |
| ["gofmt", "-w", str(file_path)], | |
| cwd=project_dir, | |
| capture_output=True, | |
| text=True, | |
| check=False, | |
| ) | |
| if result.returncode == 0: | |
| log(f"✓ gofmt formatted {file_path.name}") | |
| else: | |
| log( | |
| f"✗ gofmt failed: {result.stderr.strip() if result.stderr else 'Unknown error'}" | |
| ) | |
| except Exception as e: | |
| log(f"✗ gofmt not available: {e}") | |
| # Run goimports for import organization | |
| try: | |
| log("Running goimports...") | |
| result = subprocess.run( | |
| ["goimports", "-w", str(file_path)], | |
| cwd=project_dir, | |
| capture_output=True, | |
| text=True, | |
| check=False, | |
| ) | |
| if result.returncode == 0: | |
| log(f"✓ goimports organized imports for {file_path.name}") | |
| else: | |
| log( | |
| f"✗ goimports failed: {result.stderr.strip() if result.stderr else 'Unknown error'}" | |
| ) | |
| except Exception as e: | |
| log( | |
| f"✗ goimports not available (install: go install golang.org/x/tools/cmd/goimports@latest): {e}" | |
| ) | |
| # Run go vet for basic correctness checks | |
| try: | |
| log("Running go vet...") | |
| result = subprocess.run( | |
| ["go", "vet", str(file_path)], | |
| cwd=project_dir, | |
| capture_output=True, | |
| text=True, | |
| check=False, | |
| ) | |
| if result.returncode == 0: | |
| log(f"✓ go vet: {file_path.name} passed") | |
| else: | |
| log(f"⚠ go vet found issues: {result.stderr.strip()}") | |
| except Exception as e: | |
| log(f"✗ go vet not available: {e}") | |
| log(f"Completed formatting for {file_path.name}") | |
| except Exception as e: | |
| log(f"Hook error: {e}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "Bash", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/block-skip-hooks.py" | |
| } | |
| ] | |
| } | |
| ], | |
| "PostToolUse": [ | |
| { | |
| "matcher": "Write|Edit|MultiEdit", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/format-go-hook.py" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Strategic Core notification utilities for Claude Code hooks. | |
| Shared functions for sending ntfy notifications and getting project context | |
| across all Strategic Core Claude hooks. | |
| """ | |
| import os | |
| import subprocess | |
| import sys | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Optional, List | |
| def log(message: str, hook_name: str = "strategic-notifications") -> None: | |
| """Print a message to stderr to avoid interfering with hook output.""" | |
| print(f"[{hook_name}] {message}", file=sys.stderr) | |
| def play_audio(audio_file: str, hook_name: str = "strategic-notifications") -> bool: | |
| """ | |
| Play an audio file using system audio player. | |
| Args: | |
| audio_file: Name of the audio file (e.g., "toasty.mp3", "gutter-trash.mp3") | |
| hook_name: Name of the calling hook (for logging) | |
| Returns: | |
| True if audio played successfully, False otherwise | |
| """ | |
| try: | |
| # Get the path to the assets directory relative to this script | |
| script_dir = Path(__file__).parent | |
| assets_dir = script_dir / "assets" | |
| audio_path = assets_dir / audio_file | |
| if not audio_path.exists(): | |
| log(f"Audio file not found: {audio_path}", hook_name) | |
| return False | |
| # Try different audio players based on the system | |
| audio_players = [ | |
| ["afplay"], # macOS | |
| ["mpg123", "-q"], # Linux (quiet mode) | |
| ["mpv", "--no-video", "--really-quiet"], # Cross-platform | |
| ["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet"], # ffmpeg | |
| ["paplay"], # PulseAudio | |
| ] | |
| for player_cmd in audio_players: | |
| try: | |
| # Check if the player is available | |
| result = subprocess.run( | |
| ["which", player_cmd[0]], | |
| capture_output=True, | |
| timeout=2 | |
| ) | |
| if result.returncode == 0: | |
| # Player is available, try to play the audio | |
| cmd = player_cmd + [str(audio_path)] | |
| subprocess.run( | |
| cmd, | |
| capture_output=True, | |
| timeout=5, # Don't let audio play longer than 5 seconds | |
| check=False # Don't raise exception on non-zero exit | |
| ) | |
| log(f"♪ Played audio: {audio_file}", hook_name) | |
| return True | |
| except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError): | |
| continue | |
| log(f"No suitable audio player found for: {audio_file}", hook_name) | |
| return False | |
| except Exception as e: | |
| log(f"Error playing audio {audio_file}: {e}", hook_name) | |
| return False | |
| def send_ntfy_notification( | |
| server_url: str, | |
| topic: str, | |
| message: str, | |
| title: Optional[str] = None, | |
| priority: str = "default", | |
| tags: Optional[List[str]] = None, | |
| hook_name: str = "strategic-notifications" | |
| ) -> bool: | |
| """ | |
| Send notification to ntfy server. | |
| Args: | |
| server_url: The ntfy server URL (e.g., "http://localhost:2586") | |
| topic: The topic to send to | |
| message: The notification message | |
| title: Optional notification title | |
| priority: Notification priority (default, low, high, max) | |
| tags: Optional list of tags for the notification | |
| hook_name: Name of the calling hook (for logging) | |
| Returns: | |
| True if notification was sent successfully, False otherwise | |
| """ | |
| try: | |
| headers = [] | |
| if title: | |
| headers.extend(["-H", f"Title: {title}"]) | |
| if priority != "default": | |
| headers.extend(["-H", f"Priority: {priority}"]) | |
| if tags: | |
| headers.extend(["-H", f"Tags: {','.join(tags)}"]) | |
| # Add timestamp header | |
| headers.extend(["-H", f"X-Timestamp: {datetime.now().isoformat()}"]) | |
| # Construct curl command | |
| cmd = ["curl", "-s", "-f"] + headers + ["-d", message, f"{server_url}/{topic}"] | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) | |
| if result.returncode == 0: | |
| log(f"✓ Notification sent successfully to {topic}", hook_name) | |
| return True | |
| else: | |
| log(f"✗ Failed to send notification: {result.stderr}", hook_name) | |
| return False | |
| except subprocess.TimeoutExpired: | |
| log("✗ Notification timeout - server may be unreachable", hook_name) | |
| return False | |
| except Exception as e: | |
| log(f"✗ Error sending notification: {e}", hook_name) | |
| log(f"✗ Exception type: {type(e).__name__}", hook_name) | |
| log(f"✗ Exception details: {str(e)}", hook_name) | |
| return False | |
| def get_project_context(cwd: str, hook_name: str = "strategic-notifications") -> dict: | |
| """ | |
| Get context about the current project. | |
| Args: | |
| cwd: Current working directory path | |
| hook_name: Name of the calling hook (for logging) | |
| Returns: | |
| Dictionary with project information including name, type, and git status | |
| """ | |
| try: | |
| project_path = Path(cwd) | |
| project_name = project_path.name | |
| # Check if it's a Go project | |
| is_go_project = (project_path / "go.mod").exists() | |
| # Check if it's a Python project | |
| is_python_project = any((project_path / f).exists() for f in ["pyproject.toml", "setup.py", "requirements.txt"]) | |
| # Check if it's a Node.js project | |
| is_node_project = (project_path / "package.json").exists() | |
| # Determine project type | |
| if is_go_project: | |
| project_type = "Go project" | |
| elif is_python_project: | |
| project_type = "Python project" | |
| elif is_node_project: | |
| project_type = "Node.js project" | |
| else: | |
| project_type = "project" | |
| # Check git status if available | |
| git_status = "unknown" | |
| try: | |
| result = subprocess.run( | |
| ["git", "status", "--porcelain"], | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=5 | |
| ) | |
| if result.returncode == 0: | |
| changes = len(result.stdout.strip().split('\n')) if result.stdout.strip() else 0 | |
| git_status = f"{changes} changes" if changes > 0 else "clean" | |
| except (subprocess.TimeoutExpired, subprocess.CalledProcessError, Exception) as e: | |
| log(f"⚠ Git status check failed: {type(e).__name__}: {e}", hook_name) | |
| # Get current git branch if available | |
| git_branch = "unknown" | |
| try: | |
| result = subprocess.run( | |
| ["git", "branch", "--show-current"], | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=5 | |
| ) | |
| if result.returncode == 0: | |
| git_branch = result.stdout.strip() or "main" | |
| except (subprocess.TimeoutExpired, subprocess.CalledProcessError, Exception): | |
| pass | |
| return { | |
| "project_name": project_name, | |
| "project_type": project_type, | |
| "git_status": git_status, | |
| "git_branch": git_branch, | |
| "path": str(project_path), | |
| "is_go_project": is_go_project, | |
| "is_python_project": is_python_project, | |
| "is_node_project": is_node_project | |
| } | |
| except Exception as e: | |
| log(f"✗ Error getting project context: {e}", hook_name) | |
| log(f"✗ Exception type: {type(e).__name__}", hook_name) | |
| log(f"✗ Exception details: {str(e)}", hook_name) | |
| return { | |
| "project_name": "unknown", | |
| "project_type": "project", | |
| "git_status": "unknown", | |
| "git_branch": "unknown", | |
| "path": cwd, | |
| "is_go_project": False, | |
| "is_python_project": False, | |
| "is_node_project": False | |
| } | |
| def get_strategic_core_config() -> dict: | |
| """ | |
| Get Strategic Core specific configuration. | |
| Returns: | |
| Dictionary with ntfy server settings and topics | |
| """ | |
| return { | |
| "ntfy_server": "http://nas1-oryx:2586", | |
| "dev_topic": "strategic-core-dev", | |
| "notification_topic": "strategic-core-notifications", | |
| "alert_topic": "strategic-core-alerts" | |
| } | |
| def format_message_header(project_info: dict, session_id: str) -> str: | |
| """ | |
| Format a standard message header for Strategic Core notifications. | |
| Args: | |
| project_info: Project context from get_project_context() | |
| session_id: Claude Code session ID | |
| Returns: | |
| Formatted header string | |
| """ | |
| return f"""**Project**: {project_info['project_name']} ({project_info['project_type']}) | |
| **Branch**: {project_info['git_branch']} | |
| **Git Status**: {project_info['git_status']} | |
| Session ID: {session_id[:8]}... | |
| Path: {os.path.basename(project_info['path'])} | |
| Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}""" | |
| def send_strategic_notification( | |
| message: str, | |
| title: str, | |
| priority: str = "default", | |
| tags: Optional[List[str]] = None, | |
| topic_type: str = "dev", | |
| project_info: Optional[dict] = None, | |
| session_id: str = "unknown", | |
| hook_name: str = "strategic-notifications" | |
| ) -> bool: | |
| """ | |
| Send a Strategic Core notification with standard formatting. | |
| Args: | |
| message: The main notification message | |
| title: Notification title | |
| priority: Notification priority (default, low, high, max) | |
| tags: Optional list of tags | |
| topic_type: Type of topic (dev, notifications, alerts) | |
| project_info: Project context (if None, will be detected from cwd) | |
| session_id: Claude Code session ID | |
| hook_name: Name of the calling hook | |
| Returns: | |
| True if notification was sent successfully, False otherwise | |
| """ | |
| config = get_strategic_core_config() | |
| # Select topic based on type | |
| topic_map = { | |
| "dev": config["dev_topic"], | |
| "notifications": config["notification_topic"], | |
| "alerts": config["alert_topic"] | |
| } | |
| topic = topic_map.get(topic_type, config["dev_topic"]) | |
| # Add strategic-core tag if not present | |
| if tags is None: | |
| tags = ["strategic-core"] | |
| elif "strategic-core" not in tags: | |
| tags.append("strategic-core") | |
| # Format message with header if project info is provided | |
| if project_info: | |
| header = format_message_header(project_info, session_id) | |
| formatted_message = f"Strategic Core v2 Development\n\n{header}\n\n**Message**: {message}" | |
| else: | |
| formatted_message = f"Strategic Core v2: {message}" | |
| return send_ntfy_notification( | |
| server_url=config["ntfy_server"], | |
| topic=topic, | |
| message=formatted_message, | |
| title=title, | |
| priority=priority, | |
| tags=tags, | |
| hook_name=hook_name | |
| ) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For the sound effects.
https://www.101soundboards.com/sounds/70577-gutter-trash
https://www.myinstants.com/en/instant/toasty-mortal-kombat-52053/