Imported from reason-machines/codex-skills (
skills/codexu-macos-usage-tracker/SKILL.md). Install upstream withnpx skills add reason-machines/codex-skills --skill codexu-macos-usage-tracker. Copyright stays with the author.
codexU macOS Usage Tracker
Skill by ara.so — Codex Skills collection.
codexU is a native macOS menu bar and desktop application for monitoring OpenAI Codex / ChatGPT Codex and Claude Code usage. It provides real-time quota tracking (5-hour and 7-day windows), token usage analytics, project rankings, skill/tool usage stats, and a today task board. All data is read locally from ~/.codex/ and ~/.claude/ without uploading to third-party services.
What codexU Does
- Quota Monitoring: Displays 5-hour and 7-day quota remaining/used percentages with reset timers in the menu bar
- Token Analytics: Tracks uncached input, cached input, and output tokens with API cost equivalency estimates
- Task Board: Generates today's task board from local Codex threads and enabled automations
- Usage Trends: Shows 6-month daily token heatmap and 7-day trend summaries
- Project Rankings: Lists top projects by token usage, estimated value, thread count, and last activity
- Skill/Tool Stats: Displays most-used tools and skills from local session events
- Dual Runtime: Supports both Codex and Claude Code with global runtime switching
- Local-First: All data parsed from local SQLite and JSONL files, no external API calls for usage
Installation
From GitHub Release (Recommended)
-
Download the correct DMG for your Mac architecture from GitHub Releases:
- Apple Silicon:
codexU-<version>-mac-arm64.dmg - Intel:
codexU-<version>-mac-x86_64.dmg
- Apple Silicon:
-
Open the DMG and drag
codexU.appto/Applications -
First launch requires manual security approval:
# Open the app once (will be blocked) open /Applications/codexU.app # Then go to System Settings > Privacy & Security > Security # Click "Open Anyway" next to codexU.app # Confirm with Touch ID or password -
Grant file access when prompted:
- codexU needs read access to
~/.codex/for Codex data - If using Claude Code tracking, also needs
~/.claude/access
- codexU needs read access to
From Source
Requirements:
- macOS 14+
- Xcode Command Line Tools
- Codex installed and logged in
# Clone the repository
git clone https://github.com/shanggqm/codexU.git
cd codexU
# Build the app
make build
# Run directly
make run
# Install to /Applications
make install
# Check local data sources
make probe
Key Commands and Usage
Menu Bar Interactions
// Click menu bar icon → Opens Runtime menu with quick stats
// Shows Codex or Claude Code card with:
// - 5-hour and 7-day remaining quota
// - Today's token usage
// - Total token usage
// Click card → Opens main window switched to that runtime
Keyboard Shortcuts
Command + U: Toggle main window visibility (customizable in Settings)- Esc (during shortcut recording): Cancel
- Backspace (during shortcut recording): Clear
Main Window Views
- Today Tasks: In-progress, pending, scheduled, and completed tasks from local threads
- Usage Trends: Daily token heatmap (6 months) + 7-day trend summary
- Project Rankings: Top projects by token, value, threads, last active
- Skill Usage: Top tool calls and skill usage from local sessions
Settings Window
Access via menu bar Runtime menu → "Open Settings" or codexU app menu:
- General: Language (中文/English), appearance (auto/light/dark)
- Status Bar: Display mode (minimal/classic/rich), quota metric (used/remaining), visible indicators (5h/7d/today tokens/reset countdown)
- Window: Keep main window on top, close behavior (hide/quit)
- System: Auto-update check, check for beta versions, view status, manual update check
Configuration and Data Sources
Data Sources
codexU reads from local files only:
Codex:
~/.codex/state_5.sqlite # Account, quotas, token totals
~/.codex/sessions/**/rollout-*.jsonl # Fine-grained token events
~/.codex/archived_sessions/*.jsonl # Archived session events
~/.codex/automations/**/automation.toml # Enabled automations
Claude Code:
~/.claude/projects/**/*.jsonl # Transcript usage data
~/.claude/tasks/**/*.json # Task definitions
~/Library/Caches/codexU/claude-code/statusline-snapshot.json # Optional quota cache
Status Bar Customization
The status bar supports three display modes with adaptive single/dual ring layouts:
Minimal Mode:
- Bold quota ring only
- Adapts to single ring when only one quota window active
Classic Mode:
- Quota progress rings with percentage inside
- Separate ring per active quota window
Rich Mode:
- Full labels, progress bars, reset timers
- 5h/7d progress colors match main window blue/purple rings
Quota Metrics:
- Used: Clockwise/left-to-right progress
- Remaining: Counter-clockwise/right-to-left progress
API Cost Equivalency ("羊毛进度")
codexU estimates API-equivalent value for monthly Codex usage:
API Equivalent Value =
(uncached_input_tokens / 1,000,000) * uncached_input_price
+ (cached_input_tokens / 1,000,000) * cached_input_price
+ (output_tokens / 1,000,000) * output_price
Progress bar shows position relative to Plus, Pro 100, Pro 200, and max monthly value (~$46,500 based on 200M tokens/day * 30 days).
Code Examples
Building and Packaging
# Build for current architecture
make build
# Build release DMG for current arch
make release
# Build for specific architecture
make release-arm64 # Apple Silicon
make release-intel # Intel
make release-all # Both architectures
# Output in dist/ folder:
# dist/codexU-1.0.5-mac-arm64.dmg
# dist/codexU-1.0.5-mac-arm64.dmg.sha256
# dist/codexU-1.0.5-mac-x86_64.dmg
# dist/codexU-1.0.5-mac-x86_64.dmg.sha256
Accessing Local Usage Data (Swift)
codexU uses SwiftUI and Swift concurrency to parse local Codex data:
import Foundation
import SQLite3
// Example: Read quota from state_5.sqlite
func readCodexQuota() throws -> (fiveHour: Double, sevenDay: Double) {
let dbPath = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".codex/state_5.sqlite")
.path
var db: OpaquePointer?
guard sqlite3_open(dbPath, &db) == SQLITE_OK else {
throw NSError(domain: "codexU", code: 1)
}
defer { sqlite3_close(db) }
// Query rate limits table (structure varies by Codex version)
var stmt: OpaquePointer?
let query = """
SELECT window_duration_seconds, remaining_count, total_count
FROM rate_limits
WHERE window_duration_seconds IN (18000, 604800)
"""
var quotas: [Int: (Double, Double)] = [:]
if sqlite3_prepare_v2(db, query, -1, &stmt, nil) == SQLITE_OK {
while sqlite3_step(stmt) == SQLITE_ROW {
let window = Int(sqlite3_column_int(stmt, 0))
let remaining = Double(sqlite3_column_int64(stmt, 1))
let total = Double(sqlite3_column_int64(stmt, 2))
quotas[window] = (remaining, total)
}
}
sqlite3_finalize(stmt)
let fiveHour = quotas[18000].map { $0.0 / $0.1 } ?? 0.0
let sevenDay = quotas[604800].map { $0.0 / $0.1 } ?? 0.0
return (fiveHour, sevenDay)
}
Reading Session Token Events
import Foundation
struct TokenEvent: Codable {
let type: String
let timestamp: Date
let tokenCount: TokenCount
struct TokenCount: Codable {
let inputTokens: Int
let outputTokens: Int
let cacheReadInputTokens: Int?
let cacheCreationInputTokens: Int?
}
}
// Parse rollout JSONL for token usage
func parseSessionTokens(sessionPath: String) throws -> [TokenEvent] {
let rolloutPath = sessionPath + "/rollout-0.jsonl"
let content = try String(contentsOfFile: rolloutPath)
return content.split(separator: "\n")
.compactMap { line in
guard let data = line.data(using: .utf8),
let event = try? JSONDecoder().decode(TokenEvent.self, from: data),
event.type == "token_count" else {
return nil
}
return event
}
}
// Calculate API equivalent cost
func calculateAPICost(tokens: TokenEvent.TokenCount, model: String = "claude-sonnet-3-5-20241022") -> Double {
let uncachedInput = tokens.inputTokens - (tokens.cacheReadInputTokens ?? 0)
let cachedInput = tokens.cacheReadInputTokens ?? 0
let output = tokens.outputTokens
// OpenAI API pricing per 1M tokens (example rates)
let uncachedInputPrice = 3.0 // $3/1M
let cachedInputPrice = 0.3 // $0.3/1M (cached discount)
let outputPrice = 15.0 // $15/1M
return (Double(uncachedInput) / 1_000_000 * uncachedInputPrice) +
(Double(cachedInput) / 1_000_000 * cachedInputPrice) +
(Double(output) / 1_000_000 * outputPrice)
}
Custom Shortcut Registration
import Carbon
import SwiftUI
class ShortcutManager: ObservableObject {
@Published var currentShortcut: (modifiers: UInt32, keyCode: UInt16)?
private var eventHandler: EventHandlerRef?
func register(modifiers: UInt32, keyCode: UInt16, handler: @escaping () -> Void) {
// Minimum: 2 modifiers including Command or Control
guard modifiers.nonzeroBitCount >= 2,
(modifiers & UInt32(cmdKey)) != 0 || (modifiers & UInt32(controlKey)) != 0 else {
return
}
let hotKeyID = EventHotKeyID(signature: FourCharCode("codU"), id: 1)
var hotKeyRef: EventHotKeyRef?
RegisterEventHotKey(
keyCode,
modifiers,
hotKeyID,
GetApplicationEventTarget(),
0,
&hotKeyRef
)
currentShortcut = (modifiers, keyCode)
}
}
Common Patterns
Setting Up First-Time Use
// Check if Codex is installed and logged in
func checkCodexSetup() -> Bool {
let stateDB = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".codex/state_5.sqlite")
guard FileManager.default.fileExists(atPath: stateDB.path) else {
print("Codex not installed or not used yet")
return false
}
// Verify at least one session exists
let sessionsDir = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".codex/sessions")
let hasSession = (try? FileManager.default.contentsOfDirectory(atPath: sessionsDir.path))?.isEmpty == false
return hasSession
}
Switching Between Codex and Claude Code
enum Runtime: String, CaseIterable {
case codex = "Codex"
case claude = "Claude Code"
}
@Published var selectedRuntime: Runtime = .codex {
didSet {
// Refresh all views with new runtime data
Task {
await refreshQuota()
await refreshTokenStats()
await refreshTaskBoard()
}
}
}
// Toggle runtime from menu bar or main window
func toggleRuntime() {
selectedRuntime = selectedRuntime == .codex ? .claude : .codex
}
Adaptive Single/Dual Ring Layout
struct QuotaDisplay: View {
let fiveHourQuota: Double?
let sevenDayQuota: Double?
var body: some View {
HStack(spacing: 8) {
if let fiveHour = fiveHourQuota {
QuotaRing(percentage: fiveHour, label: "5h", color: .blue)
}
if let sevenDay = sevenDayQuota {
QuotaRing(percentage: sevenDay, label: "7d", color: .purple)
}
}
.animation(.easeInOut, value: fiveHourQuota != nil)
.animation(.easeInOut, value: sevenDayQuota != nil)
}
}
Particle Effects with Performance Optimization
struct ParticleView: View {
@Environment(\.scenePhase) private var scenePhase
@State private var isWindowFocused = false
let powerSavingMode: Bool
var shouldRenderParticles: Bool {
// Only render when window is visible, focused, and not in power saving
scenePhase == .active && isWindowFocused && !powerSavingMode
}
var body: some View {
ZStack {
if shouldRenderParticles {
ParticleEmitter()
.transition(.opacity)
}
}
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { _ in
isWindowFocused = true
}
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)) { _ in
isWindowFocused = false
}
}
}
Troubleshooting
Quota Shows -- or Empty
Problem: Status bar or main window shows -- for quotas instead of percentages.
Solutions:
- Verify Codex is logged in:
codex auth login - Check SQLite file exists:
ls -la ~/.codex/state_5.sqlite - Use at least once to generate state: Create a new Codex thread
- For Claude Code: Ensure statusline snapshot cache exists (optional feature)
- Check file permissions:
chmod 600 ~/.codex/state_5.sqlite
Token Statistics Not Updating
Problem: Today's tokens or trend charts show zero or stale data.
Solutions:
- Verify session files exist:
ls ~/.codex/sessions/ - Check rollout JSONL files:
find ~/.codex/sessions -name "rollout-*.jsonl" - For Claude Code: Check transcript JSONL:
find ~/.claude/projects -name "*.jsonl" - Click refresh button in main window
- Check Console.app for codexU parsing errors
App Won't Open After Install
Problem: macOS blocks app launch with security warning.
Solutions:
# Method 1: Right-click → Open in Finder
# Method 2: Command line override
xattr -dr com.apple.quarantine /Applications/codexU.app
# Then open System Settings > Privacy & Security
# Click "Open Anyway" next to codexU.app
Shortcut Key Not Working
Problem: Custom shortcut doesn't trigger main window.
Solutions:
- Ensure at least 2 modifiers including Command or Control
- Check for conflicts: System Settings > Keyboard > Keyboard Shortcuts
- Reset to default in codexU Settings
- Common conflicts: Avoid
Command + Shift + U(Character Viewer) - Try alternative:
Command + Control + U,Command + Option + U
High CPU Usage
Problem: codexU uses excessive CPU in background.
Solutions:
- Update to v1.0.5+ (optimized polling and particle rendering)
- Enable power saving mode in Settings (only renders particles on hover)
- Particle effects auto-disable when:
- Window is minimized or hidden
- Battery is low
- Thermal state is critical
- "Reduce motion" accessibility setting is on
- Close main window (hides Dock icon, keeps menu bar)
Build Errors from Source
Problem: make build fails with Swift compilation errors.
Solutions:
# Ensure Xcode Command Line Tools installed
xcode-select --install
# Check macOS version (requires 14+)
sw_vers
# Clean build artifacts
make clean
rm -rf build/
# Verify Swift version
swift --version # Should be Swift 5.9+
# Try explicit architecture
make build ARCH=arm64 # or x86_64
DMG Packaging Fails
Problem: make release fails or produces corrupt DMG.
Solutions:
# Install required tools
brew install create-dmg
# Check disk space
df -h
# Clean dist folder
rm -rf dist/
mkdir -p dist/
# Build without signing first
make build
# Manual DMG creation
create-dmg \
--volname "codexU" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--app-drop-link 450 200 \
dist/codexU-test.dmg \
build/Release/codexU.app
Claude Code Quota Missing
Problem: Claude Code 5h/7d quotas show -- even with active subscription.
Cause: codexU needs optional statusline snapshot cache file.
Solutions:
- This is expected behavior (cache file is optional)
- Token statistics and trends still work from transcript JSONL
- Manual cache population not currently supported
- Only affects quota display, not usage analytics
Update Check Fails
Problem: "Check for Updates" in Settings shows connection error.
Solutions:
# Test GitHub API access
curl -I https://api.github.com/repos/shanggqm/codexU/releases/latest
# Check network proxy settings
# System Settings > Network > Advanced > Proxies
# Disable auto-check if behind firewall:
# codexU Settings > System > Uncheck "Automatically check for updates"
File Access Denied Errors
Problem: codexU logs show permission denied for ~/.codex/ or ~/.claude/.
Solutions:
- Grant Full Disk Access: System Settings > Privacy & Security > Full Disk Access → Add codexU
- Or manually fix permissions:
chmod -R 755 ~/.codex
chmod -R 755 ~/.claude
- If using symlinks, ensure target directories are readable
- Check Console.app for specific file paths being denied
Reference Links
- Homepage: https://shanggqm.github.io/codexU-site/
- Repository: https://github.com/shanggqm/codexU
- Releases: https://github.com/shanggqm/codexU/releases
- Issues: https://github.com/shanggqm/codexU/issues
- Distribution Guide: DISTRIBUTION.md
- License: MIT