""" Claude SDK Client Configuration =============================== Functions for creating and configuring the Claude Agent SDK client. """ import json import os from pathlib import Path from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient from claude_code_sdk.types import HookMatcher from security import bash_security_hook # Puppeteer MCP tools for browser automation PUPPETEER_TOOLS = [ "mcp__puppeteer__puppeteer_navigate", "mcp__puppeteer__puppeteer_screenshot", "mcp__puppeteer__puppeteer_click", "mcp__puppeteer__puppeteer_fill", "mcp__puppeteer__puppeteer_select", "mcp__puppeteer__puppeteer_hover", "mcp__puppeteer__puppeteer_evaluate", ] # Built-in tools BUILTIN_TOOLS = [ "Read", "Write", "Edit", "Glob", "Grep", "Bash", ] def create_client(project_dir: Path, model: str) -> ClaudeSDKClient: """Create a Claude Agent SDK client with multi-layered security. Auth options ------------ This demo supports two ways of authenticating: 1. API key via ``ANTHROPIC_API_KEY`` (standard Claude API key) 2. Claude Code auth token via ``CLAUDE_CODE_OAUTH_TOKEN`` If neither is set, client creation will fail with a clear error. Args: project_dir: Directory for the project model: Claude model to use Returns: Configured ClaudeSDKClient Security layers (defense in depth): 1. Sandbox - OS-level bash command isolation prevents filesystem escape 2. Permissions - File operations restricted to project_dir only 3. Security hooks - Bash commands validated against an allowlist (see security.py for ALLOWED_COMMANDS) """ api_key = os.environ.get("ANTHROPIC_API_KEY") oauth_token = os.environ.get("CLAUDE_CODE_OAUTH_TOKEN") if not api_key and not oauth_token: raise ValueError( "No Claude auth configured. Set either ANTHROPIC_API_KEY (Claude API key) " "or CLAUDE_CODE_OAUTH_TOKEN (Claude Code auth token from `claude setup-token`)." ) # Create comprehensive security settings # Note: Using relative paths ("./**") restricts access to project directory # since cwd is set to project_dir security_settings = { "sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True}, "permissions": { "defaultMode": "acceptEdits", # Auto-approve edits within allowed directories "allow": [ # Allow all file operations within the project directory "Read(./**)", "Write(./**)", "Edit(./**)", "Glob(./**)", "Grep(./**)", # Bash permission granted here, but actual commands are validated # by the bash_security_hook (see security.py for allowed commands) "Bash(*)", # Allow Puppeteer MCP tools for browser automation *PUPPETEER_TOOLS, ], }, } # Ensure project directory exists before creating settings file project_dir.mkdir(parents=True, exist_ok=True) # Write settings to a file in the project directory settings_file = project_dir / ".claude_settings.json" with open(settings_file, "w") as f: json.dump(security_settings, f, indent=2) print(f"Created security settings at {settings_file}") print(" - Sandbox enabled (OS-level bash isolation)") print(f" - Filesystem restricted to: {project_dir.resolve()}") print(" - Bash commands restricted to allowlist (see security.py)") print(" - MCP servers: puppeteer (browser automation)") print() return ClaudeSDKClient( options=ClaudeCodeOptions( model=model, system_prompt="You are an expert full-stack developer building a production-quality web application.", allowed_tools=[ *BUILTIN_TOOLS, *PUPPETEER_TOOLS, ], mcp_servers={ "puppeteer": {"command": "npx", "args": ["puppeteer-mcp-server"]} }, hooks={ "PreToolUse": [ HookMatcher(matcher="Bash", hooks=[bash_security_hook]), ], }, max_turns=1000, cwd=str(project_dir.resolve()), settings=str(settings_file.resolve()), # Use absolute path ) )