AgentNotch is a macOS menu bar app that displays real-time AI coding assistant telemetry in the Mac's notch area. It monitors Claude Code and OpenAI Codex sessions via JSONL file parsing and displays tool usage, token counts, and session status.
AgentNotch/
├── Core/
│ ├── ClaudeCode/
│ │ └── ClaudeCodeManager.swift # Claude Code JSONL parsing, session watching
│ ├── Codex/
│ │ └── CodexManager.swift # OpenAI Codex JSONL parsing, session watching
│ ├── Settings/
│ │ └── AppSettings.swift # @AppStorage settings
│ ├── Telemetry/
│ │ ├── TelemetryCoordinator.swift # OTLP telemetry handling
│ │ └── OTLPHTTPServer.swift # HTTP server for telemetry
│ └── Process/
│ └── MCPProcessManager.swift # MCP process management
├── Models/
│ ├── ClaudeCodeModels.swift # ClaudeSession, ClaudeToolExecution, etc.
│ └── CodexModels.swift # CodexSession, CodexToolExecution, etc.
├── Views/
│ ├── Notch/
│ │ └── AgentNotchContentView.swift # Main notch UI
│ ├── ClaudeCode/
│ │ └── ClaudeToolListView.swift # Claude tool list UI
│ ├── Codex/
│ │ └── CodexToolListView.swift # Codex tool list UI
│ └── Settings/
│ └── AgentSettingsView.swift # Settings UI
└── homebrew/ # Homebrew cask distribution
├── agentnotch.rb # Cask formula
└── AgentNotch.zip # Release artifact
- Claude directory:
~/.claude/ - IDE sessions:
~/.claude/ide/*.lock - Project JSONL:
~/.claude/projects/{project-key}/{uuid}.jsonl - Stats cache:
~/.claude/stats-cache.json
Path /Users/foo/project becomes -Users-foo-project (replace / with -)
tool_use- Tool execution started (has id, name, input)tool_result- Tool completed (has tool_use_id)thinking- Model thinking blocktext- Text output (check for[Request interrupted by user)toolUseResult- Top-level field for tool results
- Thinking:
content[].type == "thinking"orrole == "assistant" - Tool running: Active tools in
state.activeTools - Session done:
stop_reason == "end_turn"or interrupted text - Permission needed: Tool running > 2.5s without result
- Idle timeout: No new tool for 10 seconds
- Codex directory:
~/.codex/ - Sessions:
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl - History:
~/.codex/history.jsonl - Config:
~/.codex/config.toml
session_meta- Session start (id, cwd, cli_version, model_provider, git info)turn_context- Turn metadata (model, cwd, approval_policy)event_msgwithpayload.type:token_count- Token usage withinfo.total_token_usageagent_reasoning- Model thinking/reasoning text
response_itemwithpayload.type:function_call- Tool call started (name, arguments, call_id)function_call_output- Tool completed (call_id, output)message- User/assistant messages
struct CodexTokenUsage {
var inputTokens: Int
var outputTokens: Int
var cachedInputTokens: Int // Prompt cache hits
var reasoningOutputTokens: Int // o1/o3 reasoning tokens
var totalTokens: Int
var modelContextWindow: Int
}- Thinking:
agent_reasoningevent or no active tools with activity - Tool running: Active tools in
state.activeTools - Idle timeout: 3 seconds of no new events
All print statements wrapped in debugLog() - only active in DEBUG builds:
@inline(__always)
func debugLog(_ message: @autoclosure () -> String) {
#if DEBUG
print(message())
#endif
}// Claude Code
enableClaudeCodeJSONL: Bool = true
showSessionDots: Bool = true
showPermissionIndicator: Bool = true
showTodoList: Bool = true
showThinkingState: Bool = true
// Codex
enableCodexJSONL: Bool = true
// Context & Display
contextTokenLimit: Int = 200_000 // 50k-1M range
showContextProgress: Bool = true
toolDisplayMode: String = "list" // "list" or "singular"struct ClaudeTokenUsage {
var inputTokens: Int
var outputTokens: Int
var cacheReadInputTokens: Int // Green in UI - savings
var cacheCreationInputTokens: Int // Yellow in UI - creation
}closed- Minimal view with wings showing tool/thinking statuspeeking- Dropdown notification (permission, completion)open- Full expanded view with tools list, todos, footer
- Claude Code: Orange glow (
rgb(0.9, 0.4, 0.1)) - Codex: Blue glow (
rgb(0.1, 0.3, 0.7))
- Session duration
- Git branch badge (purple)
- Token total (sparkles icon)
- Cache read tokens (green arrow down)
- Cache write tokens (yellow arrow up)
- Shows token usage vs configurable limit
- Colors: green (<50%), yellow (50-70%), orange (70-90%), red (>90%)
appgram/homebrew-tap
- Build app in Xcode (Release)
- Copy .app to
homebrew/AgentNotch.app - Create zip:
zip -r AgentNotch.zip AgentNotch.app - Calculate SHA256:
shasum -a 256 AgentNotch.zip - Update
agentnotch.rbwith new version and sha256 - Create GitHub release:
gh release create vX.X.X AgentNotch.zip --repo AppGram/agentnotch - Push formula to tap: copy to homebrew-tap/Casks/, commit, push
brew tap appgram/homebrew-tap
brew install --cask agentnotch
brew upgrade --cask agentnotch- Uses
DispatchSource.makeFileSystemObjectSourcewithO_EVTONLY - Watches for
.write, .extendevents - Fallback scan if direct projectKey match fails
- Per-session state in
sessionStates[sessionId] - Main state synced when
selectedSession?.id == sessionId - Always call
objectWillChange.send()for UI updates
Check multiple locations:
toolUseResultfield containing "interrupted"textcontent containing[Request interrupted by usertool_resultwith "rejected" content
idleCheckTimer- 3s, marks thinking as falsetoolIdleTimer- 10s, marks session as done if no new toolspermissionCheckTimer- 2.5s, marks tool as needing permission