### Automatic Hook Configuration with HookInstaller (Swift) Source: https://context7.com/farouqaldori/claude-island/llms.txt Handles automatic installation and configuration of Claude Code hooks. It copies the Python hook script to `~/.claude/hooks/` and updates `~/.claude/settings.json` with the necessary hook event configurations. This ensures the CLI communicates events to the application. ```swift import Foundation // Install hooks on app launch (idempotent) HookInstaller.installIfNeeded() // Creates: ~/.claude/hooks/claude-island-state.py // Updates: ~/.claude/settings.json with hook configurations // Check if hooks are installed if HookInstaller.isInstalled() { print("Claude Island hooks are active") } // Uninstall hooks completely HookInstaller.uninstall() // Removes the hook script and settings.json entries // The installer configures these hook events in settings.json: // - UserPromptSubmit: Fires when user sends a message // - PreToolUse: Before tool execution (caches tool_use_id) // - PostToolUse: After tool completes // - PermissionRequest: When tool needs approval (with 86400s timeout) // - Notification: Various Claude notifications // - Stop/SubagentStop: Session interrupts // - SessionStart/SessionEnd: Lifecycle events // - PreCompact: Context compaction events ``` -------------------------------- ### ClaudeSessionMonitor: SwiftUI State Observation and Actions (Swift) Source: https://context7.com/farouqaldori/claude-island/llms.txt Illustrates how ClaudeSessionMonitor, a MainActor-isolated wrapper for SessionStore, is used within a SwiftUI view. It handles starting and stopping monitoring, and provides methods for UI actions like approving/denying permissions and archiving sessions. Dependencies include SwiftUI. ```swift import SwiftUI struct SessionListView: View { @StateObject private var sessionMonitor = ClaudeSessionMonitor() var body: some View { VStack { // Active sessions ForEach(sessionMonitor.instances) { session in SessionRow(session: session) } // Sessions needing attention (permissions, input) ForEach(sessionMonitor.pendingInstances) { session in PendingSessionRow(session: session) } } .onAppear { // Start listening for hook events sessionMonitor.startMonitoring() } .onDisappear { sessionMonitor.stopMonitoring() } } func handleApprove(sessionId: String) { // Approve the active permission request sessionMonitor.approvePermission(sessionId: sessionId) } func handleDeny(sessionId: String) { // Deny with optional reason sessionMonitor.denyPermission( sessionId: sessionId, reason: "User declined this action" ) } func handleArchive(sessionId: String) { // Remove session from tracking sessionMonitor.archiveSession(sessionId: sessionId) } func loadSessionHistory(session: SessionState) { // Load full chat history from JSONL sessionMonitor.loadHistory( sessionId: session.sessionId, cwd: session.cwd ) } } ``` -------------------------------- ### SessionStore: Process Events and Manage State (Swift) Source: https://context7.com/farouqaldori/claude-island/llms.txt Demonstrates how to interact with the SessionStore actor to process various events, manage session lifecycle, and query session state. It utilizes Combine for observing state changes and Task for asynchronous operations. Dependencies include Combine and Foundation. ```swift import Combine import Foundation // Subscribe to session state changes let cancellable = SessionStore.shared.sessionsPublisher .receive(on: DispatchQueue.main) .sink { sessions in for session in sessions { print("Session: \(session.projectName)") print("Phase: \(session.phase)") print("PID: \(session.pid ?? 0)") print("TTY: \(session.tty ?? "none")") print("Items: \(session.chatItems.count)") } } // Process events through the store Task { // Hook events await SessionStore.shared.process(.hookReceived(hookEvent)) // Permission decisions await SessionStore.shared.process( .permissionApproved(sessionId: "uuid", toolUseId: "toolu_01ABC") ) await SessionStore.shared.process( .permissionDenied(sessionId: "uuid", toolUseId: "toolu_01ABC", reason: "User denied") ) // Session lifecycle await SessionStore.shared.process(.sessionEnded(sessionId: "uuid") ) // Load chat history from JSONL file await SessionStore.shared.process( .loadHistory(sessionId: "uuid", cwd: "/path/to/project") ) // Tool completion events await SessionStore.shared.process( .toolCompleted(sessionId: "uuid", toolUseId: "toolu_01ABC", result: result) ) // Interrupt detection (Ctrl+C) await SessionStore.shared.process(.interruptDetected(sessionId: "uuid") ) } // Query session state Task { if let session = await SessionStore.shared.session(for: "uuid") { print("Display title: \(session.displayTitle)") print("Needs attention: \(session.needsAttention)") print("Active permission: \(session.activePermission?.toolName ?? "none")") } let hasPermission = await SessionStore.shared.hasActivePermission(sessionId: "uuid") let allSessions = await SessionStore.shared.allSessions() } ``` -------------------------------- ### Approve Tool Once via Tmux Source: https://context7.com/farouqaldori/claude-island/llms.txt Approves a tool execution once by sending the '1' key followed by Enter to a specified tmux pane. This is part of the ToolApprovalHandler's functionality for programmatic terminal interaction. ```swift import Foundation // Approve a tool once (sends '1' + Enter) Task { let target = TmuxTarget(session: "main", window: "0", pane: "1") let success = await ToolApprovalHandler.shared.approveOnce(target: target) print("Approval sent: \(success)") } ``` -------------------------------- ### Approve Tool Always via Tmux Source: https://context7.com/farouqaldori/claude-island/llms.txt Configures continuous tool approval by sending the '2' key followed by Enter to a specified tmux pane. This function is utilized by the ToolApprovalHandler for ongoing approval processes. ```swift // Approve tool always (sends '2' + Enter) Task { let target = TmuxTarget(session: "dev", window: "claude", pane: "0") let success = await ToolApprovalHandler.shared.approveAlways(target: target) } ``` -------------------------------- ### NotchViewModel for Dynamic Island UI Management in SwiftUI Source: https://context7.com/farouqaldori/claude-island/llms.txt The NotchViewModel manages the notch overlay's visual state, animations, and user interactions within a SwiftUI view. It handles hover detection, click events, and content switching between different views like instances list, settings menu, and chat. ```swift import SwiftUI struct NotchContainerView: View { @StateObject private var viewModel: NotchViewModel init(screen: NSScreen) { let geometry = screen.notchGeometry _viewModel = StateObject(wrappedValue: NotchViewModel( deviceNotchRect: geometry.notchRect, screenRect: geometry.screenRect, windowHeight: geometry.windowHeight, hasPhysicalNotch: screen.hasNotch )) } var body: some View { NotchView(viewModel: viewModel) } } // ViewModel state and actions class NotchViewModel: ObservableObject { @Published var status: NotchStatus // .closed, .opened, .popping @Published var contentType: NotchContentType // .instances, .menu, .chat(session) @Published var isHovering: Bool // Open with reason tracking func notchOpen(reason: NotchOpenReason = .unknown) // Reasons: .click, .hover, .notification, .boot, .unknown func notchClose() func notchPop() // Brief expand animation func notchUnpop() func toggleMenu() func showChat(for session: SessionState) func exitChat() func performBootAnimation() // Expand briefly on launch // Geometry access var openedSize: CGSize // Dynamic based on contentType var deviceNotchRect: CGRect var screenRect: CGRect } // Content types enum NotchContentType { case instances // List of active sessions case menu // Settings menu case chat(SessionState) // Chat history for a session } ``` -------------------------------- ### Reject Tool with Message via Tmux Source: https://context7.com/farouqaldori/claude-island/llms.txt Rejects a tool execution and optionally sends a rejection message to a specified tmux pane. It sends 'n' followed by Enter, then the message. This is handled by the ToolApprovalHandler. ```swift // Reject with optional message (sends 'n' + Enter, then message) Task { let target = TmuxTarget(session: "main", window: "0", pane: "1") let success = await ToolApprovalHandler.shared.reject( target: target, message: "Please use a different approach" ) } ``` -------------------------------- ### Build Claude Island with Xcode Source: https://github.com/farouqaldori/claude-island/blob/main/README.md This command builds the Claude Island application in release mode using Xcode. It's the primary method for building the application from source. ```bash xcodebuild -scheme ClaudeIsland -configuration Release build ``` -------------------------------- ### SessionState Data Model and Usage in Swift Source: https://context7.com/farouqaldori/claude-island/llms.txt The SessionState struct represents the complete state for a Claude session. It includes identity, phase, chat history, and tool tracking. This snippet demonstrates its initialization, phase transitions, derived properties, and iterating through chat history. ```swift import Foundation // SessionState contains all session information let session = SessionState( sessionId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", cwd: "/Users/dev/my-project", projectName: "my-project", pid: 12345, tty: "ttys001", isInTmux: true, phase: .processing ) // Session phases (state machine) enum SessionPhase { case idle // Waiting for activity case processing // Running tools/generating case waitingForInput // Ready for user input case waitingForApproval(context) // Tool needs permission case compacting // Context being compacted case ended // Session terminated } // Check phase transitions let phase = SessionPhase.processing phase.canTransition(to: .waitingForApproval(ctx)) // true phase.canTransition(to: .idle) // true SessionPhase.ended.canTransition(to: .processing) // false (terminal) // Derived properties session.needsAttention // true if waitingForApproval or waitingForInput session.activePermission // PermissionContext if waiting for approval session.displayTitle // summary ?? firstUserMessage ?? projectName session.pendingToolName // Tool name if awaiting approval session.pendingToolInput // Formatted input string for display session.canInteract // Whether UI interaction is available // Chat history items for item in session.chatItems { switch item.type { case .user(let text): print("User: \(text)") case .assistant(let text): print("Assistant: \(text)") case .toolCall(let tool): print("Tool: \(tool.name) - Status: \(tool.status)") if let result = tool.result { print("Result: \(result)") } // Subagent tools for Task tools for subTool in tool.subagentTools { print(" Subagent: \(subTool.name)") } case .thinking(let text): print("Thinking: \(text)") case .interrupted: print("(Interrupted)") } } // Tool status tracking enum ToolStatus { case running case success case error case interrupted case waitingForApproval } ``` -------------------------------- ### Unix Socket Event Handling with HookSocketServer (Swift) Source: https://context7.com/farouqaldori/claude-island/llms.txt Manages bidirectional communication with Claude Code hooks via Unix domain sockets. It receives session events, tracks pending permission requests, and sends user decisions back to the CLI. The server supports multiple concurrent sessions and maintains a cache for correlating tool use IDs across events. ```swift import Foundation // Start the socket server with event handlers HookSocketServer.shared.start( onEvent: { event in // Process incoming hook events print("Session: (event.sessionId)") print("Event: (event.event)") print("Status: (event.status)") // Event types: UserPromptSubmit, PreToolUse, PostToolUse, // PermissionRequest, Notification, Stop, // SubagentStop, SessionStart, SessionEnd, PreCompact if let tool = event.tool { print("Tool: (tool)") } // Check if this event expects a response if event.expectsResponse { print("Waiting for permission decision...") } }, onPermissionFailure: { sessionId, toolUseId in // Handle socket failures for pending permissions print("Permission socket failed for session: (sessionId)") } ) // Respond to a permission request by tool use ID HookSocketServer.shared.respondToPermission( toolUseId: "toolu_01ABC123...", decision: "allow", // "allow", "deny", or "ask" reason: nil ) // Respond by session ID (uses most recent pending permission) HookSocketServer.shared.respondToPermissionBySession( sessionId: "session-uuid", decision: "deny", reason: "User declined file modification" ) // Check pending permissions let hasPending = HookSocketServer.shared.hasPendingPermission(sessionId: "session-uuid") if let pending = HookSocketServer.shared.getPendingPermission(sessionId: "session-uuid") { print("Tool: (pending.toolName ?? "unknown")") print("Tool ID: (pending.toolId ?? "unknown")") } // Cancel pending permissions when Claude stops waiting HookSocketServer.shared.cancelPendingPermissions(sessionId: "session-uuid") // Stop the server HookSocketServer.shared.stop() ``` -------------------------------- ### TmuxTarget Struct Definition Source: https://context7.com/farouqaldori/claude-island/llms.txt Defines the TmuxTarget structure used to represent a specific tmux pane, including session, window, and pane identifiers. It also provides a computed property to generate the target string for tmux commands. ```swift // TmuxTarget represents a specific tmux pane struct TmuxTarget { let session: String let window: String? let pane: String? var targetString: String { var target = session if let window = window { target += ":\(window)" } if let pane = pane { target += ".\(pane)" } return target } } ``` -------------------------------- ### Python Hook Script for Session Events and Permissions Source: https://context7.com/farouqaldori/claude-island/llms.txt This Python script, claude-island-state.py, sends session events (like tool usage, session start/end) to a companion application via a Unix socket. It specifically handles 'PermissionRequest' events by waiting for a decision from the app before returning the appropriate response to Claude Code. Dependencies include standard Python libraries like 'json', 'os', 'socket', and 'sys'. ```python #!/usr/bin/env python3 """ Claude Island Hook - Sends session state via Unix socket For PermissionRequest: waits for user decision from the app """ import json import os import socket import sys SOCKET_PATH = "/tmp/claude-island.sock" TIMEOUT_SECONDS = 300 # 5 minutes for permission decisions def send_event(state): """Send event to app, return response if any""" try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.settimeout(TIMEOUT_SECONDS) sock.connect(SOCKET_PATH) sock.sendall(json.dumps(state).encode()) # For permission requests, wait for response if state.get("status") == "waiting_for_approval": response = sock.recv(4096) sock.close() if response: return json.loads(response.decode()) else: sock.close() return None except (socket.error, OSError, json.JSONDecodeError): return None def main(): data = json.load(sys.stdin) session_id = data.get("session_id", "unknown") event = data.get("hook_event_name", "") cwd = data.get("cwd", "") state = { "session_id": session_id, "cwd": cwd, "event": event, "pid": os.getppid(), "tty": get_tty(), } # Map events to status if event == "PermissionRequest": state["status"] = "waiting_for_approval" state["tool"] = data.get("tool_name") state["tool_input"] = data.get("tool_input", {}) response = send_event(state) if response: decision = response.get("decision", "ask") if decision == "allow": output = { "hookSpecificOutput": { "hookEventName": "PermissionRequest", "decision": {"behavior": "allow"} } } print(json.dumps(output)) sys.exit(0) elif decision == "deny": output = { "hookSpecificOutput": { "hookEventName": "PermissionRequest", "decision": { "behavior": "deny", "message": response.get("reason", "Denied via ClaudeIsland") } } } print(json.dumps(output)) sys.exit(0) # No response or "ask" - let Claude Code show normal UI sys.exit(0) # Other events: fire and forget elif event == "PreToolUse": state["status"] = "running_tool" state["tool"] = data.get("tool_name") state["tool_input"] = data.get("tool_input", {}) state["tool_use_id"] = data.get("tool_use_id") elif event == "PostToolUse": state["status"] = "processing" state["tool_use_id"] = data.get("tool_use_id") elif event == "SessionStart": state["status"] = "waiting_for_input" elif event == "SessionEnd": state["status"] = "ended" elif event == "Stop": state["status"] = "waiting_for_input" send_event(state) if __name__ == "__main__": main() ``` -------------------------------- ### Send Arbitrary Message to Tmux Pane Source: https://context7.com/farouqaldori/claude-island/llms.txt Sends an arbitrary string message directly to a specified tmux pane. This functionality is part of the ToolApprovalHandler for direct communication within the terminal. ```swift // Send arbitrary message to tmux pane Task { let target = TmuxTarget(session: "main", window: "0", pane: "1") await ToolApprovalHandler.shared.sendMessage( "Hello from Claude Island", to: target ) } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.