A macOS menu bar app that notifies you when Claude Code needs attention and brings the right terminal session to front with one click.
When Claude Code stops working (needs input, permission, done, or error), the app:
- Receives the event via Unix socket from a hook script
- Posts a macOS notification with context (event type, project name)
- Plays a sound (configurable, with repeat count)
- Clicking the notification focuses the correct terminal session
- Optional keyboard shortcut focuses the latest notification's terminal session
Claude Code Hook → Shell Script → Unix Socket → Your Turn App → macOS Notification
↓ ↓
Click/Shortcut → AppleScript → iTerm2/Terminal
Sources/
├── Your_TurnApp.swift # App entry point, menu bar setup
├── App/ # App lifecycle & infrastructure
│ ├── AppDelegate.swift # Window management, notification delegate
│ └── SocketServer.swift # Unix socket listener, JSON parsing
├── Hooks/ # AI agent hook integrations
│ └── HookInstaller.swift # Claude Code hook installation
├── Features/ # Feature modules
│ ├── Notifications/
│ │ ├── NotificationService.swift # Event → notification logic
│ │ └── SoundPlayer.swift # App-controlled sound playback
│ ├── Hotkey/
│ │ ├── HotkeyManager.swift # Carbon keyboard shortcut registration
│ │ ├── HotkeyConfiguration.swift # Key code + modifiers data model
│ │ └── NotificationStack.swift # LIFO stack of pending notifications
│ └── HealthCheck/
│ ├── HealthCheckService.swift # Integration health monitoring
│ └── HealthStatus.swift # Health state model
├── Terminals/ # Terminal integrations
│ ├── TerminalController.swift # Protocol + registry
│ ├── AppleScriptRunner.swift # Shared AppleScript utilities
│ ├── ITerm/
│ │ ├── ITerm2.swift # Centralized iTerm2 logic
│ │ └── ITermController.swift # TerminalActivating adapter
│ ├── TerminalApp/
│ │ ├── TerminalApp.swift # Centralized Terminal.app logic
│ │ └── TerminalAppController.swift # TerminalActivating adapter
│ └── Warp/
│ ├── Warp.swift # Centralized Warp logic
│ └── WarpController.swift # TerminalActivating adapter
├── Models/ # Data models
│ ├── HookEvent.swift # JSON event model from Claude Code
│ └── SystemSound.swift # Sound picker data model
├── Extensions/ # Swift extensions
│ └── UserDefaults+BoolWithDefault.swift # Bool with default value helper
├── Views/ # UI layer
│ ├── Settings/
│ │ ├── SettingsView.swift # Main settings window (tabs)
│ │ ├── GeneralSection.swift # Launch at login, keyboard shortcut settings
│ │ ├── NotificationSection.swift # Sound preferences
│ │ ├── EventsSection.swift # Per-event toggles
│ │ └── HealthStatusSection.swift # Health display
│ ├── SetupWizard/
│ │ ├── SetupWizardView.swift # Wizard container
│ │ ├── WizardStepLayout.swift # Common step layout
│ │ ├── WelcomeStep.swift # Welcome/intro step
│ │ ├── HooksStep.swift # Hook installation step
│ │ ├── NotificationsStep.swift # Notification permission step
│ │ ├── TerminalAppIntegrationStep.swift # Terminal.app integration step
│ │ ├── ITermIntegrationStep.swift # iTerm2 integration step
│ │ ├── LaunchAtLoginStep.swift # Login item setup step
│ │ └── CompleteStep.swift # Setup complete summary
│ └── Components/
│ ├── ToggleRow.swift # Reusable toggle component
│ ├── NotificationGuidanceSheet.swift # System Settings guidance for notifications
│ └── AutomationGuidanceSheet.swift # System Settings guidance for automation
└── Resources/
├── your-turn-notify.sh # Hook script (deployed to ~/.claude/hooks/)
└── Sounds/ # Classic Mac sounds (18 bundled .aiff files)
- Unix Socket:
~/Library/Application Support/Your Turn/claude-notify.sock - Hook Script:
~/.claude/hooks/your-turn-notify.sh(deployed by app) - Claude Settings:
~/.claude/settings.json(hooks registered here)
| Key | Type | Default | Description |
|---|---|---|---|
setupComplete |
Bool | false | First-launch wizard completed |
notify.enabled |
Bool | true | Master toggle for notifications |
notify.soundEnabled |
Bool | true | Enable/disable sound playback |
notify.sound |
String | "Sosumi.aiff" | Notification sound filename |
notify.soundRepeatCount |
Int | 1 | Times to play sound (1-5) |
notify.taskComplete |
Bool | true | Notify on task complete (Stop) |
notify.inputNeeded |
Bool | true | Notify when input needed (permission_prompt, elicitation_dialog) |
hotkey.enabled |
Bool | false | Enable/disable keyboard shortcut |
hotkey.keyCode |
Int | (unset) | Virtual key code (UInt16 as Int) |
hotkey.modifiers |
Int | (unset) | NSEvent.ModifierFlags rawValue as Int |
Naming convention: Code uses "hotkey" (Carbon API term) — class names, UserDefaults keys, notification names. User-facing text (UI labels, log messages, docs) uses "keyboard shortcut".
Always use ./Scripts/build.sh instead of running xcodebuild directly. The script automatically detects if code signing is available and adjusts accordingly.
SourceKit (Swift language server) often reports false errors when editing files because it cannot see the full project context:
Cannot find 'TypeName' in scope- Type exists in another project fileCannot find type 'ProtocolName' in scope- Protocol exists in projectCannot infer type of closure parameter- Type available at compile time
How to handle:
- Do not trust SourceKit diagnostics alone
- Always verify with
./Scripts/build.sh - If build succeeds, SourceKit errors are false positives
Utility scripts in Scripts/:
| Script | Usage | Description |
|---|---|---|
build.sh |
./Scripts/build.sh [command] |
Primary build script - always use this instead of xcodebuild directly |
setup-local-config.sh |
./Scripts/setup-local-config.sh [TEAM_ID] |
Configure code signing with your Apple Developer Team ID |
store-notary-credentials.sh |
./Scripts/store-notary-credentials.sh |
Store notarization credentials in keychain (one-time setup) |
debug.sh |
(called by build.sh) | Launch app in lldb with auto-run |
test-socket.sh |
./Scripts/test-socket.sh [event] |
Send test events (permission, mcp, idle, stop) |
reset-for-testing.sh |
./Scripts/reset-for-testing.sh |
Reset app state for fresh testing |
generate-menu-icon.swift |
swift Scripts/generate-menu-icon.swift |
Generate menu bar icon assets |
build.sh commands:
./Scripts/build.sh- Build only (debug config)./Scripts/build.sh release- Build with release config./Scripts/build.sh notarize- Build, notarize, and create distribution ZIP./Scripts/build.sh run- Build and launch app./Scripts/build.sh debug- Build and launch with lldb./Scripts/build.sh clean- Remove build directory
The build script automatically creates an empty Local.xcconfig if missing, allowing builds without code signing. Run ./Scripts/setup-local-config.sh TEAM_ID to enable code signing with your Apple Developer Team ID.
| Terminal | Focus Support | Notes |
|---|---|---|
| iTerm2 | Full | TERM_SESSION_ID for exact session |
| Terminal.app | Full | AppleScript with tty matching |
| Warp | Basic | Brings app to front only |
- macOS only - Uses AppKit, UserNotifications, AppleScript, Carbon (keyboard shortcut)
- Not App Store - Requires Unix socket (sandbox incompatible)
- Swift/SwiftUI - Modern Swift with SwiftUI for settings UI
- No third-party deps - Native frameworks only