My agentic slop goes here. Not intended for anyone else!
at main 6.6 kB view raw
1#!/usr/bin/env python3 2# /// script 3# requires-python = ">=3.9" 4# dependencies = [ 5# "claude-code-sdk", 6# ] 7# /// 8""" 9Permission demo for Claude Code SDK Python. 10Demonstrates how the permission callback system works. 11""" 12 13import asyncio 14import sys 15import logging 16from typing import Any, Dict 17 18from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions 19from claude_code_sdk.types import ( 20 PermissionResultAllow, 21 PermissionResultDeny, 22 ToolPermissionContext, 23) 24 25# Set up logging 26logging.basicConfig( 27 level=logging.INFO, 28 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' 29) 30logger = logging.getLogger(__name__) 31 32# Track granted permissions 33granted_permissions = set() 34 35 36async def interactive_permission_callback( 37 tool_name: str, 38 tool_input: Dict[str, Any], 39 context: ToolPermissionContext 40) -> PermissionResultAllow | PermissionResultDeny: 41 """Interactive permission callback that asks user for permission.""" 42 43 logger.info(f"🔔 Permission callback invoked for tool: {tool_name}") 44 print(f"\n🔐 PERMISSION REQUEST 🔐") 45 print(f"Tool: {tool_name}") 46 47 # Log the full input for debugging 48 logger.info(f"Full input: {tool_input}") 49 50 # Show input details 51 try: 52 if tool_name == "Read": 53 file_path = tool_input.get("file_path", "") 54 print(f"File: {file_path}") 55 elif tool_name == "Bash": 56 command = tool_input.get("command", "") 57 print(f"Command: {command}") 58 elif tool_name in ["Write", "Edit"]: 59 file_path = tool_input.get("file_path", "") 60 print(f"File: {file_path}") 61 elif tool_name == "Glob": 62 pattern = tool_input.get("pattern", "") 63 path = tool_input.get("path", "(current directory)") 64 print(f"Pattern: {pattern}") 65 print(f"Path: {path}") 66 elif tool_name == "Grep": 67 pattern = tool_input.get("pattern", "") 68 path = tool_input.get("path", "(current directory)") 69 print(f"Pattern: {pattern}") 70 print(f"Path: {path}") 71 else: 72 print(f"Input: {tool_input}") 73 except Exception as e: 74 logger.info(f"Failed to parse input details: {e}") 75 76 # Check if already granted 77 if tool_name in granted_permissions: 78 print("→ Auto-approved (previously granted)") 79 logger.info(f"Returning allow result for {tool_name}") 80 return PermissionResultAllow() 81 82 # Ask user 83 response = input("Allow? [y/N/always]: ").lower().strip() 84 85 if response in ["y", "yes"]: 86 print("→ Allowed (this time only)") 87 logger.info(f"User approved {tool_name} for this request only") 88 return PermissionResultAllow() 89 elif response in ["a", "always"]: 90 granted_permissions.add(tool_name) 91 print(f"✅ Permission granted for: {tool_name}") 92 logger.info(f"User granted permanent permission for {tool_name}") 93 return PermissionResultAllow() 94 else: 95 print(f"❌ Permission denied for: {tool_name}") 96 logger.info(f"User denied permission for {tool_name}") 97 return PermissionResultDeny( 98 message=f"User denied access to {tool_name}", 99 interrupt=False 100 ) 101 102 103async def run_demo(): 104 """Run the permission demo.""" 105 print("🚀 Starting Permission Demo") 106 print("==================================") 107 print("This demo starts with NO permissions.") 108 print("Claude will request permissions as needed.\n") 109 110 # Create options with custom permission callback 111 # Test WITHOUT allowed_tools to see if permission requests come through 112 options = ClaudeCodeOptions( 113 model="sonnet", 114 # allowed_tools=["Read", "Write", "Bash", "Edit", "Glob", "Grep"], 115 can_use_tool=interactive_permission_callback, 116 ) 117 118 async with ClaudeSDKClient(options=options) as client: 119 # First prompt - Claude will need to request Read permission 120 print("\n📤 Sending first prompt (reading from ../lib)...") 121 messages = [] 122 await client.query( 123 "Please read and analyze the source files in the ../lib directory. " 124 "Focus on the main OCaml modules and their purpose. " 125 "What is the overall architecture of this Claude library?" 126 ) 127 128 async for msg in client.receive_response(): 129 messages.append(msg) 130 if hasattr(msg, 'content'): 131 if isinstance(msg.content, str): 132 print(f"\n📝 Claude says:\n{msg.content}") 133 elif isinstance(msg.content, list): 134 for block in msg.content: 135 if hasattr(block, 'text'): 136 print(f"\n📝 Claude says:\n{block.text}") 137 138 # Show current permissions 139 print("\n📋 Current permission status:") 140 if granted_permissions: 141 print(f"Currently granted permissions: {', '.join(granted_permissions)}") 142 else: 143 print("No permissions granted yet") 144 145 # Second prompt - will need Write permission 146 print("\n📤 Sending second prompt (writing TEST.md)...") 147 await client.query( 148 "Now write a summary of what you learned about the Claude library " 149 "architecture to a file called TEST.md in the current directory. " 150 "Include the main modules, their purposes, and how they work together." 151 ) 152 153 async for msg in client.receive_response(): 154 if hasattr(msg, 'content'): 155 if isinstance(msg.content, str): 156 print(f"\n📝 Claude says:\n{msg.content}") 157 elif isinstance(msg.content, list): 158 for block in msg.content: 159 if hasattr(block, 'text'): 160 print(f"\n📝 Claude says:\n{block.text}") 161 162 # Show final permissions 163 print("\n📋 Final permission status:") 164 if granted_permissions: 165 print(f"Currently granted permissions: {', '.join(granted_permissions)}") 166 else: 167 print("No permissions granted yet") 168 169 print("\n==================================") 170 print("✨ Demo complete!") 171 172 173async def main(): 174 """Main entry point.""" 175 try: 176 await run_demo() 177 except KeyboardInterrupt: 178 print("\n\nDemo interrupted by user.") 179 except Exception as e: 180 logger.error(f"Error in demo: {e}", exc_info=True) 181 sys.exit(1) 182 183 184if __name__ == "__main__": 185 asyncio.run(main())