A minimal macOS menubar app for starting and stopping attendance tracking in Personio.
- Lives in your menubar (no dock icon)
- One-click Start/Stop attendance tracking
- Optional live timer display in menubar
- Today's total tracked time
- Status indicator showing configuration and connection state
- Automatic state recovery after restart
- Auto-opens setup on first launch
- Connection test to validate API credentials
- Secure credential storage in macOS Keychain
- macOS 13.0 (Ventura) or later
- Personio account with API access
- API credentials (Client ID & Client Secret)
- Your Personio Employee ID
# Add the tap
brew tap timae/tap
# Install
brew install --cask personio-timer- Download the latest release from Releases
- Unzip and drag
PersonioTimer.appto/Applications - Launch from Applications or Spotlight
- Launch PersonioTimer - Preferences window opens automatically on first launch
- Enter your credentials:
- Client ID: From Personio API settings
- Client Secret: From Personio API settings
- Employee ID: Your numeric employee ID
- Click Test Connection to verify everything works
- Click Save
- Log into Personio
- Go to Settings > Integrations > API credentials
- Create new credentials with attendance read/write permissions
- Copy the Client ID and Client Secret
Your Employee ID is visible in the URL when viewing your profile in Personio:
https://yourcompany.personio.de/staff/employees/12345
^^^^^
Employee ID
| Action | How |
|---|---|
| Start tracking | Click menubar icon > Start |
| Stop tracking | Click menubar icon > Stop |
| View today's total | Click menubar icon (shown in menu) |
| Sync totals | Click menubar icon > Sync Now |
| Open Personio | Click menubar icon > Open Personio |
┌─────────────────────────┐
│ Status: Ready (ID: 123) │ ← Connection/config status
├─────────────────────────┤
│ Start / Stop │ ← Primary action
│ Today: 4h 32m │ ← Total tracked today
├─────────────────────────┤
│ Sync Now │
│ Open Personio │
├─────────────────────────┤
│ Preferences... │
│ Quit PersonioTimer │
└─────────────────────────┘
The status line shows:
- Status: No API credentials - Credentials not configured
- Status: No Employee ID - Employee ID not set
- Status: Ready (ID: xxx) - Configured and ready to track
- Status: Tracking (ID: xxx) - Currently tracking time
- Status: Loading... - API call in progress
- Error: [message] - Last operation failed
# Clone the repository
git clone https://github.com/timae/Personio-Timer.git
cd Personio-Timer/PersonioTimer
# Build with Xcode
xcodebuild -project PersonioTimer.xcodeproj \
-scheme PersonioTimer \
-configuration Release \
build
# Or open in Xcode
open PersonioTimer.xcodeprojPersonioTimer/
├── PersonioTimer/
│ ├── App/
│ │ ├── main.swift # Entry point
│ │ ├── AppDelegate.swift # App lifecycle
│ │ ├── StatusBarController.swift # Menubar UI
│ │ └── PreferencesWindowController.swift # Settings window
│ ├── Core/
│ │ ├── PersonioAPIClient.swift # HTTP client
│ │ ├── PersonioModels.swift # API models
│ │ ├── TokenCache.swift # Token storage
│ │ └── AttendanceService.swift # Business logic
│ ├── Storage/
│ │ ├── KeychainStore.swift # Secure credentials
│ │ └── LocalStateStore.swift # App state
│ ├── Utilities/
│ │ └── TimeUtils.swift # Time formatting
│ └── Resources/
│ └── Info.plist
├── Packaging/
│ ├── personio-timer.rb # Homebrew cask
│ ├── entitlements.plist
│ └── release-notes.md
└── README.md
- API credentials are stored in macOS Keychain
- Credentials are never logged or exposed
- Authentication tokens are kept in memory only
- Network requests use HTTPS
- Verify Client ID and Secret in Personio
- Ensure the API credentials have attendance permissions
- Try generating new credentials
- Check the Status line in the menu for details
- Open Preferences and configure credentials and Employee ID
- Use "Test Connection" to verify your setup
- Check if the previous entry was manually closed in Personio
- Verify network connectivity
- Check System Settings > Control Center > Menu Bar Only
- Try quitting and relaunching the app
- This happens because each rebuild changes the app's code signature
- Click "Always Allow" when prompted
- For distribution builds with stable signing, this only happens once
MIT License - see LICENSE file for details.
- Have Personio API credentials ready
- Know your Employee ID
- Have access to Personio web interface for verification
- Launch app for the first time
- Preferences window should open automatically
- Enter valid Client ID, Secret, and Employee ID
- Click "Test Connection"
Expected: Green checkmark with "Connected! Found X attendance(s) today"
- After successful connection test, click Save
- Check the menu
Expected:
- Status shows "Status: Ready (ID: xxx)"
- Start button is enabled
- Click Start
- Wait for menu to update
Expected:
- Status shows "Status: Tracking (ID: xxx)"
- Menu shows "Stop" instead of "Start"
- Menubar icon fills in (clock.fill)
- Timer appears in menubar (if enabled in preferences)
- Personio web shows new attendance entry (open, no end time)
- With tracking active, click Stop
- Check Personio web
Expected:
- Status shows "Status: Ready (ID: xxx)"
- Menu shows "Start"
- Timer disappears from menubar
- Personio entry now has end time set
- "Today: Xh Ym" updates
- Start tracking
- Quit app (Cmd+Q, confirm "Quit Anyway")
- Relaunch app
Expected:
- App resumes tracking
- Timer continues from original start time
- No duplicate entry in Personio
- Start tracking
- Click Quit PersonioTimer
Expected: Dialog offers "Stop and Quit", "Quit Anyway", "Cancel"
- In Preferences, enter wrong Client Secret
- Click "Test Connection"
Expected: Red X with error message
- Have some completed entries in Personio
- Click Sync Now
Expected: Today total updates to match Personio
- Configure preferences
- Quit app
- Relaunch
- Open Preferences
Expected: All values are preserved
- Click "Open Personio"
Expected: Browser opens to Personio dashboard
- Start tracking
- Disconnect from network
- Try to Stop
Expected: Error message shown in Status line, state preserved for retry
- Start tracking in app
- In Personio web, manually create another attendance entry for today
- Stop and try to Start again in app
Expected: Error message about overlap (or graceful handling)
- All TC pass
- No credentials in logs (check Console.app, filter by "PersonioTimer")
- App uses < 50MB memory
- No CPU usage when idle