A lightweight, self-hosted activity monitor and productivity dashboard for your personal computer. Runs silently in the background, tracks which applications and windows you use, and presents the data in a local web dashboard with automatic insights and tool recommendations.
No data leaves your machine. No accounts. No subscriptions.
- Monitors which app is in focus every 30 seconds and logs it to a local SQLite database
- Categorises activity automatically (Deep Work, Terminal, Communication, Distraction, etc.)
- Detects idle time so AFK periods don't inflate your numbers
- Serves a dashboard at
http://localhost:5555showing:- Live productivity score (0–100)
- Today's time breakdown by category (donut chart); click any legend item to toggle it and see the score recalculate live
- Browser Breakdown row — dedicated second row showing browser-app activity only: Browser Score card + Browser Breakdown donut + Browser Time by Category bars
- 7-day trend (stacked bar chart)
- Hourly timeline of today
- Most active windows and apps
- Recommendations and insights panel
- Generates recommendations hourly based on your actual usage patterns (distraction rate, context switching, peak hours, etc.)
- Syncs recommendations between machines via any shared folder you already have
- Settings panel (⚙ top-right of the dashboard) for live editing: theme, app categories, browser site rules, auto-categorization, and dashboard port — most changes take effect on the next poll with no restart; General tab includes a Restart Monitor button to force the monitor to pick up category changes immediately
- Score Killers panel on the dashboard — shows the top 5 apps eating your score so you know exactly what to cut when your number is low
- Log viewer built into the Settings panel — view and download any service log without touching the terminal
- Backup / Restore — export your config and category rules to a JSON file, restore on any machine
- Python 3.9 or newer
- macOS: pre-installed. Verify:
python3 --version - Linux: pre-installed on most distros. Verify:
python3 --version - Windows: download from python.org — check "Add Python to PATH" during install
- macOS: pre-installed. Verify:
- pip (comes with Python)
- Git (to clone the repository)
- macOS:
xcode-select --install - Linux:
sudo apt install gitorsudo dnf install git - Windows: git-scm.com
- macOS:
- ~50 MB disk space for the app + database over time
- Accessibility permission for Terminal (granted during setup — details below)
xdotool— for reading the active window namesudo apt install xdotool # Ubuntu / Debian / Mint sudo dnf install xdotool # Fedora / RHEL sudo pacman -S xdotool # Arch / Manjaro
xprintidle— optional, enables idle time detectionsudo apt install xprintidle # Ubuntu / Debian- Wayland note:
xdotoolrequires XWayland. Most Wayland desktops include it; if app names show as "unknown", verify XWayland is running.
pywin32andpsutil— installed automatically byinstall.py- Python must be in your system PATH (select this option during Python installation)
Open a terminal (Terminal on macOS/Linux, Command Prompt or PowerShell on Windows) and run:
git clone https://github.com/HuggingBunny/Productivity-Monitor.git
cd productivity-monitorpython3 install.py # macOS / Linux
python install.py # WindowsThe installer will ask you a few questions:
| Prompt | What to enter | Default |
|---|---|---|
| Data directory | Where to store your activity database and logs | OS-appropriate (see below) |
| Dashboard port | Port for the web dashboard | 5555 |
| Poll interval | How often to check the active app (seconds) | 30 |
| Idle threshold | Seconds of inactivity before marking as idle | 300 (5 min) |
| Enable sync | Whether to sync recommendations across machines | n |
| Sync folder | Path to a shared folder (if sync enabled) | — |
Default data directories:
| OS | Default path |
|---|---|
| macOS | ~/Library/Application Support/productivity-monitor/ |
| Linux | ~/.local/share/productivity-monitor/ |
| Windows | %APPDATA%\productivity-monitor\ |
To accept all defaults without being prompted:
python3 install.py --defaultsThe monitor uses macOS Accessibility APIs to read the active window name. You need to grant this once:
- Open System Settings
- Go to Privacy & Security → Accessibility
- Click the + button
- Add Terminal (or iTerm2, Warp, or whichever terminal you use)
- Ensure the toggle next to it is ON
Without this, the monitor still runs but app names will show as "unknown".
Navigate to http://localhost:5555 in any browser.
The green pulsing dot in the top-left confirms the monitor is active. Data appears within 30 seconds of the first poll.
All settings live in config.json in the project root. Edit this file and re-run python3 install.py to apply changes, or use the ⚙ Settings panel in the dashboard — category and auto-categorize changes take effect on the next poll with no restart.
{
"data_dir": "/path/to/your/data",
"dashboard_port": 5555,
"poll_interval_seconds": 30,
"idle_threshold_seconds": 300,
"sync_enabled": false,
"sync_path": "",
"auto_categorize": true
}| Key | Description |
|---|---|
data_dir |
Where activity.db and logs are stored. Can be any writable path. |
dashboard_port |
Port for the web dashboard. Change if 5555 conflicts with something else. |
poll_interval_seconds |
How often the monitor checks the active app. Lower = more granular, higher CPU. |
idle_threshold_seconds |
Inactivity duration before a period is marked idle instead of active. |
sync_enabled |
Set to true to enable recommendation syncing between machines. |
sync_path |
Path to any shared folder (Obsidian vault, Dropbox, OneDrive, network share). |
auto_categorize |
true (default) — automatically match apps to categories. Set false to pause all classification; everything logs as uncategorized. Editable live from the Settings panel. |
The easiest way is the App Categories tab in the Settings panel — click any category, add or remove app names, and save. Changes are written directly to categories.json and picked up on the next poll.
To edit the file directly, each entry in categories.json follows this structure:
"deep_work": {
"label": "Deep Work",
"color": "#4CAF50",
"apps": ["code", "cursor", "xcode", "pycharm"],
"window_overrides": [
{
"keywords": ["github", "stackoverflow", "docs"],
"category": "deep_work"
}
]
}apps— partial matches against the app process name (case-insensitive)window_overrides— when an app (e.g. Chrome) matches multiple categories, window title keywords break the tie. Checked in order; first match wins.
Built-in categories: deep_work, terminal, communication, documentation, ai_tools, browsing, planning, meetings, distraction, creative, system, idle, uncategorized.
Open http://localhost:5555 in any browser. The page auto-refreshes every 60 seconds.
A 0–100 score calculated from today's active time:
- Adds to score: Deep Work, Terminal, Documentation, Planning, AI Tools
- Subtracts from score: Distraction
- Neutral: Communication, Browsing, Meetings, System
| Score | Label |
|---|---|
| 85–100 | Absolutely crushing it |
| 70–84 | In the zone |
| 55–69 | Solid session |
| 40–54 | Decent — room to improve |
| 25–39 | Fragmented day |
| 10–24 | Off track |
| 0–9 | Not much tracked yet |
Below the category breakdown, the Score Killers Today section lists your top 5 apps by time spent in non-productive categories (everything except Deep Work, Terminal, Documentation, Planning, AI Tools, and Idle). This is the fastest answer to "why is my score low today" — no digging required.
Pre-seeded with tool and workflow suggestions on first run. Additional data-driven recommendations are generated automatically every hour once you have enough usage history (~1 hour of data). Dismiss any recommendation with the ✕ button — dismissed items don't return.
Pattern-based recommendations look for:
- Distraction rate above 12% of active time
- Deep work below 35% of active time
- Communication consuming more than 35% of active time
- Context switching faster than 5 category changes per hour
- Heavy browser use without a local docs solution
- Your personal peak productivity hour
Click the ⚙ button in the top-right of the header to open the Settings panel. Six tabs:
Switch between Dark, Light, and System (follows your OS) themes. Applied instantly and saved in your browser — no server round-trip.
- Auto-categorize apps toggle. When on (default), the monitor automatically assigns apps to categories using name matching and browser window title rules. When off, every app logs as
uncategorized. Takes effect on the next poll (≤ 30 seconds). - Dashboard port field. Change the port the web server listens on. Saved to
config.json; requires a dashboard restart to take effect (the monitor keeps running — only the web process needs to rebind).
Live editor for the app-to-category mappings in categories.json:
- Click any category pill to select it
- Existing apps appear as draggable tags — drag and drop an app tag onto any other category pill to move it
- Click × to remove an app from a category
- Type an app process name and press Enter or click + Add
- Click Save Categories — takes effect on the next monitor poll, no restart needed
App names are partial, case-insensitive substring matches. "code" matches "vscode", "Code Helper", etc.
Manage how browser tabs are classified by window title keywords. Rules are checked in order — first match wins.
- Each rule card shows its keyword tags and the target category
- Delete removes a rule; ↑ ↓ reorders priority
- The Add New Rule form at the bottom takes a comma-separated keyword list and a target category
- Click Save All Rules to write changes to
categories.json - Each rule has a + button to instantly pre-select its category in the Add Rule form
Example: add netsec, shodan, exploit-db → Deep Work to count security research as productive time.
View and download service log files without opening a terminal.
- Dropdown — select which log to view: Monitor log (Python logging output), monitor stdout/stderr, dashboard stdout/stderr
- ↻ Refresh — reload the current log (shows last 200 lines)
- ⬇ Download — download the full raw log file for offline analysis
- The log viewer auto-scrolls to the most recent entries
- Log files are automatically truncated every 48 hours — they never grow without bound
Export and restore your configuration and category rules.
- Download Backup — downloads a JSON file containing your current
config.jsonandcategories.jsonwith a version stamp and timestamp. Safe to commit to your own private repo or store anywhere. - Restore from Backup — pick a previously downloaded backup file. The UI validates it before showing the Restore button. On confirm, overwrites
config.jsonandcategories.jsonon the server. Use this when setting up a new machine from a known-good state.
Each machine clones the repo and runs install.py independently. Activity data stays local to each machine. Recommendations can optionally be shared.
git clone https://github.com/HuggingBunny/Productivity-Monitor.git
cd productivity-monitor
python3 install.pyIf you already have it running on one Mac and want to push it to another:
# Edit .deployrc with the remote Mac's address first
bash deploy.shSee DEPLOY.md for the full step-by-step guide with no assumed knowledge.
Recommendations (tool suggestions, workflow insights) are the only state worth sharing across machines — activity data is machine-specific.
1. Configure a shared sync folder in config.json on each machine:
{
"sync_enabled": true,
"sync_path": "/path/to/shared/folder"
}The sync_path can be anything both machines can access:
- An Obsidian vault synced via iCloud / Obsidian Sync
- A Dropbox or OneDrive folder
- A network share (
/Volumes/nas/sharedor\\server\share) - Any folder you keep in sync yourself
2. Export from the source machine:
python3 sync.py export3. Import on the destination machine:
python3 sync.py importOther sync commands:
python3 sync.py status # show what's in the sync file
python3 sync.py path # print the resolved sync file pathPull the latest changes and restart services:
cd productivity-monitor
git pull
python3 install.py --defaultsYour config.json and data/ are preserved across updates.
python3 uninstall.pyThis stops both background services and removes their auto-start entries. Your activity database is preserved — delete the data_dir folder manually if you want a clean slate.
The monitor service isn't active. Re-run the installer:
python3 install.py --defaultsOr check the error log in your configured data_dir:
# macOS/Linux
cat ~/Library/Application\ Support/productivity-monitor/monitor-err.log
# Windows (PowerShell)
type "$env:APPDATA\productivity-monitor\monitor-err.log"Accessibility permission isn't granted or hasn't taken effect.
- System Settings → Privacy & Security → Accessibility → verify Terminal is listed and toggled ON
- If it's already there, remove it and re-add it
- Wait 60 seconds after granting, then refresh the dashboard
xdotool isn't installed or isn't finding the active window.
xdotool getactivewindow getwindowname # test it manually
sudo apt install xdotool # install if missingOn Wayland: verify XWayland is running — echo $WAYLAND_DISPLAY should return a value, and xdotool should still work via XWayland compatibility.
Check if the dashboard process is running:
# macOS/Linux
ps aux | grep "dashboard/app.py" | grep -v grep
# Windows (PowerShell)
Get-Process python | Where-Object { $_.CommandLine -like "*app.py*" }If nothing appears, restart with:
python3 install.py --defaultsIf another app is using port 5555, change dashboard_port in config.json and re-run the installer.
Python isn't in your PATH. Either:
- Reinstall Python from python.org and tick "Add Python to PATH"
- Or use the full path:
C:\Users\YourName\AppData\Local\Programs\Python\Python3x\python.exe install.py
pip3 install flask # macOS / Linux
pip install flask # Windows
python3 install.py --defaults # then restartsystemd user services require user lingering to start without a login session:
loginctl enable-linger $USER- Confirm the source machine ran
python3 sync.py exportafter the recommendations were added - Check the sync file exists:
python3 sync.py path - Verify both machines point to the same physical folder
productivity-monitor/
├── VERSION Current version number (semver)
├── CHANGELOG.md Per-version change history
├── monitor.py Background daemon — polls active app every N seconds
│ reloads categories.json + config.json on every poll
│ log files truncated every 48h (no runaway growth)
├── dashboard/
│ ├── app.py Flask web server — dashboard + full settings API
│ │ endpoints: /api/today, /api/weekly, /api/timeline,
│ │ /api/top-windows, /api/score-killers, /api/browser-breakdown,
│ │ /api/recommendations, /api/dismiss/<id>, /api/categories,
│ │ /api/config, /api/logs, /api/logs/download, /api/backup, /api/restore
│ └── templates/
│ └── index.html Dashboard UI (Bootstrap 5 + Chart.js, dark/light/system theme)
│ ⚙ Settings panel: Appearance, General, App Categories,
│ Browser Rules, Logs, Backup (6 tabs)
│ Score Killers panel, drag-and-drop app tags
├── analyze.py Hourly pattern analysis → generates recommendations
├── categories.json App → productivity category mappings (edit via Settings or directly)
├── config.json Your settings — data path, port, sync, intervals, auto_categorize
├── config_loader.py Reads config.json, provides defaults per OS
├── platform_utils.py OS abstraction — app detection + idle time (macOS/Linux/Windows)
├── install.py Cross-platform installer (macOS / Linux / Windows)
│ interactive port selection with validation
├── uninstall.py Cross-platform service removal
├── sync.py Recommendation sync via shared folder
├── requirements.txt Python dependencies
├── deploy.sh Push to remote Mac via rsync + SSH (runs install.py --defaults)
├── vault-sync.sh Legacy bash sync script (macOS only — use sync.py instead)
├── install.sh Legacy bash installer (macOS only — use install.py instead)
├── uninstall.sh Legacy bash uninstaller (macOS only — use uninstall.py instead)
├── DEPLOY.md Beginner-friendly multi-Mac deployment guide
└── data/ Created on first run — never committed to git
├── activity.db SQLite database — all your activity data
├── monitor.log Monitor daemon log (truncated every 48h)
└── *.log Service output/error logs
Everything stays local:
- No network calls except serving the dashboard on
localhost - No telemetry, analytics, or external APIs
- The SQLite database is stored wherever you configure
data_dir data/is excluded from git via.gitignore— your activity is never committed
MIT
