In-progress local-first macOS activity tracker for building near-perfect personal work logs.
The goal is a quiet background service substrate that records active app/browser sessions durably, then gives humans, a future SwiftUI app, and internal AI agents clean CLI hooks to query history by day, app, title, category, URL/domain, and export format.
- Tracks active macOS app sessions with start/end timestamps and exact duration.
- Detects idle time from macOS HID idle state and records it as
activity_type: "idle"instead of blaming the foreground app. - Tolerates brief active-app probe misses so transient AppleScript/macOS hiccups do not create false gaps.
- Tolerates initial active-app/idle probe failures so the background service can start and recover instead of exiting.
- Records longer unknown spans as
activity_type: "untracked"when probing recovers, so missing time stays visible. - Captures active browser tab title and URL from the same active-tab AppleScript sample when the browser supports it.
- Stabilizes brief same-browser title/URL probe misses from current tab context without mixing conflicting tab data.
- Converts longer browser tab-context outages into
activity_type: "untracked"instead of low-quality browser rows with empty title/URL. - Captures native app window title when macOS reports it.
- Falls back to the foreground app title/name when window-level title is unavailable.
- Stores source-of-truth logs in SQLite under
~/.activity_tracker/activity.db. - Configures SQLite with WAL, normal synchronous mode, foreign keys, and a busy timeout.
- Lets CLI reads coexist with the background writer.
- Maintains indexed epoch timestamps in SQLite for scalable day/range queries.
- Mirrors completed sessions to JSONL for audit/export fallback and keeps the default CSV view current.
- Maintains an open-session checkpoint so a restart can recover the active span instead of losing it.
- Reports collector health, freshness, service state, and today's data-quality audit via
health --json. - Generates CSV exports for spreadsheet workflows.
- Provides
--jsonoutput for AI/tool callers. - Provides
now --jsonfor cheap current activity polling by SwiftUI/menu-bar clients. - Provides
inventory --jsonfor SwiftUI/AI filter menus across apps, domains, categories, and activity types. - Provides
schema --jsonso SwiftUI/AI tool harnesses can discover the CLI/data contract without parsing help text. - Installs as a
launchduser service for behind-the-scenes collection.
cargo build --releaseBinary:
./target/release/activity_trackerRun foreground tracker:
./target/release/activity_tracker trackInstall background tracker:
./target/release/activity_tracker service installmacOS will likely ask for Accessibility permission for the terminal or binary host. Without it, active app detection can be unavailable.
activity_tracker paths --json
activity_tracker schema --json
activity_tracker now --json
activity_tracker agent --json
activity_tracker agent --last-minutes 240 --json
activity_tracker agent 2026-06-03 --json
activity_tracker health --json
activity_tracker doctor --json
activity_tracker verify --json
activity_tracker service install --no-load --json
activity_tracker service status --json
activity_tracker service logs --lines 80 --json
activity_tracker service uninstall --json
activity_tracker day 2026-06-03 --json
activity_tracker report 2026-06-03 --json
activity_tracker timeline 2026-06-03 --json
activity_tracker query --from 2026-06-03 --to 2026-06-03 --domain github --json
activity_tracker query --since 2026-06-03T08:00:00+02:00 --until 2026-06-03T09:00:00+02:00 --json
activity_tracker query --last-minutes 120 --json
activity_tracker query --last-minutes 120 --order desc --limit 20 --json
activity_tracker query --category Development --limit 50 --json
activity_tracker inventory --last-minutes 240 --limit 20 --json
activity_tracker logs 2026-06-03 --json
activity_tracker audit 2026-06-03 --json
activity_tracker audit --last-minutes 120 --json
activity_tracker logs 2026-06-03 --domain github --json
activity_tracker logs 2026-06-03 --app "Code" --json
activity_tracker logs 2026-06-03 --title "project" --json
activity_tracker logs 2026-06-03 --url "pull/123" --json
activity_tracker query --text "driverry devops" --json
activity_tracker logs 2026-06-03 --activity-type idle --json
activity_tracker summary --json
activity_tracker export --date 2026-06-03 --format csv
activity_tracker export --date 2026-06-03 --format jsonl
activity_tracker export --date 2026-06-03 --format jsonl --json
activity_tracker import-csv ~/Desktop/usage_stats.csv --dry-run --json
activity_tracker reclassify --dry-run --json
activity_tracker reclassify --from 2026-06-03 --to 2026-06-03 --dry-run --json
activity_tracker repair-gaps --dry-run --json
activity_tracker repair-titles --dry-run --json
activity_tracker repair-urls --dry-run --json
activity_tracker repair-context --dry-run --json
activity_tracker repair-context --last-minutes 120 --dry-run --json
activity_tracker repair-mirror --jsonPreferred first call for internal AI/reporting tools:
agent --json returns:
- service readiness,
report_ready, andaction_required - repair plan with actionable commands
next_actionwith the next repair/report/inspection commands- window-scoped quality gate, warnings, and audit
- today-wide audit context
- bounded summary and recent timeline context
- open checkpoint and storage paths
Defaults:
- window: last 120 minutes
- summary rows: top 12
- timeline blocks: 20 most recent
Useful variants:
agent YYYY-MM-DD --jsonfor one-day report contextagent --summary-limit N --timeline-limit N --jsonfor larger/smaller contextagent --include-sessions --jsonwhen a tool needs raw sessions
Readiness and quality:
agent.report_readyrequires service binary/config, freshness, coverage, and full storage verification to be clean.- If derived JSONL/CSV storage is broken while SQLite is healthy, repair plan includes
activity_tracker repair-mirror --json. - Rolling
agent --last-minuteswindows treat uncovered leading/trailing spans as gaps. audit --jsonreports coverage, context richness, quality breakdowns, bounded samples, and open-session state.- Audit quality includes gaps, overlaps, invalid rows, missing titles/URLs, blank tabs, context mismatches, idle/untracked counts, and uncategorized counts.
History reads:
report --jsonis the preferred full daily payload.- Report includes day summary, raw sessions, current checkpoint, provisional active session, and storage paths.
query --jsonis the preferred cross-day/all-history search payload.- Query supports local date windows, RFC3339 windows, rolling
--last-minutes, filters, summary, compact timeline, raw sessions, and open checkpoint. day,logs,query,summary, andreportinclude the active open session when it overlaps the query.- Exports stay based on completed sessions.
--textsearches app, bundle, title, URL, domain, category, and activity type.queryandlogsaccept--order asc|desc; use--order desc --limit Nfor newest matching rows.- Windowed summaries and timelines are clipped to requested day/range/last-minutes bounds.
- Raw session arrays keep original persisted bounds for audit/debug context.
timeline --jsonreturns compact ordered blocks grouped by app/domain/category.inventory --jsonreturns windowed app, domain, category, and activity-type facets for agents or SwiftUI filter pickers.
Service/setup payloads:
health --jsonis the service substrate check.- Health includes launchd state, service binary/argument validation, freshness, storage verification, latest activity age, open checkpoint, paths, and today's audit.
doctor --jsonis the setup diagnostic payload.- Doctor includes writable storage, osascript availability, probe status/errors, launchd config validation, storage verification, paths, and hints.
now --jsonis the cheap current-state poll for SwiftUI/menu-bar clients.verify --jsonchecks SQLite integrity plus JSONL and default CSV readability/count/content sync.service status --jsonreports launchd load/running state, PID, program, arguments, and log paths.service logs --jsonreports bounded launchd stdout/stderr tails.schema --jsonreports the stable CLI/data contract, including read payload, import, and repair report fields.schema --jsondistinguishes fullpaths --jsonfields from embeddedpathsfields used by app/report payloads.--jsoncan be passed command-locally (doctor --json) or globally before the subcommand (--json doctor).- Global
--jsonrequires an explicit subcommand; plain no-subcommand mode still defaults to foregroundtrackfor humans. - Runtime failures under
--jsonemit{ "ok": false, "error": { "code", "message" } }.
Repair/export hooks:
reclassifyrecomputes categories from current app and browser-domain rules.reclassify,repair-gaps,repair-titles,repair-urls, andrepair-contextaccept scoped windows.- Repair windows support
--from/--to,--since/--until, or--last-minutes. - JSON repair reports include
generated_atpluswindow. repair-gapsconverts audited gaps into explicitactivity_type: "untracked"sessions.repair-titlesbackfills native-app title gaps and exact-URL browser title gaps.repair-urlscanonicalizes safe URL-only fixes such as known browser blank tabs.repair-contextrepairs high-confidence browser context mismatches/missing fields.repair-contextconverts short unrecoverable context rows to untracked time.repair-mirrorrewrites JSONL and CSV mirrors from SQLite.export --jsonwrites CSV/JSONL and returns path, date scope, format, and session count.
Default root is a home dotdir, similar to Codex-style local state but separate from ~/.codex:
~/.activity_tracker
Files:
activity.db: SQLite source of truthsessions.jsonl: append-only mirror/fallbackusage_stats.csv: refreshed CSV viewexports/: explicit CLI exportslogs/: launchd stdout/stderr logs
Legacy data from ~/Library/Application Support/activity_tracker/sessions.jsonl is auto-migrated into SQLite on first service/doctor/write run.
The SQLite DB also keeps a single open_session heartbeat row while the tracker is running.
Clean shutdown clears it.
Restart recovery converts it into a completed session and then starts a fresh checkpoint.
SQLite uses WAL plus a short busy timeout so agent/SwiftUI reads do not fail during normal heartbeat writes.
Override per command:
ACTIVITY_TRACKER_HOME=/tmp/activity-tracker activity_tracker paths
activity_tracker --data-dir /tmp/activity-tracker day --jsonImport old/exported CSV into SQLite plus the JSONL mirror:
activity_tracker import-csv ~/Desktop/usage_stats.csv --jsonImports skip duplicates using session start/end/app/bundle/title/url/activity type. Categories are app-aware and domain-aware. Browser sessions can classify as Communication, Email, Calendar, Development, AI, Design, Productivity, Social, Writing, or Research based on URL domain.
Each JSONL record is one completed session:
{
"start_time": "2026-06-03T08:00:00+02:00",
"end_time": "2026-06-03T08:05:30+02:00",
"duration_seconds": 330.0,
"app_name": "Google Chrome",
"bundle_id": "com.google.Chrome",
"title": "Example Project",
"category": "Browser",
"activity_type": "active",
"url": "https://example.com/path"
}Idle sessions:
app_name: "Idle"bundle_id: "local.activity_tracker.idle"category: "Idle"activity_type: "idle"
Repaired gap sessions:
app_name: "Untracked"bundle_id: "local.activity_tracker.untracked"category: "Untracked"activity_type: "untracked"
Day summaries include sessions overlapping that local day and clip cross-midnight durations to the requested day.
Range queries include sessions overlapping the optional [from midnight, day after to midnight) local-date window, precise RFC3339 timestamp windows, or last-N-minute windows.
Live query commands include the current open session provisionally.
Persisted JSONL records only contain completed sessions.
activity_tracker service install
activity_tracker service install --interval-seconds 2 --idle-threshold-seconds 300
activity_tracker service status --json
activity_tracker service logs --lines 80 --json
activity_tracker service uninstallservice install validates that the selected binary is an absolute executable file, writes ~/Library/LaunchAgents/com.local.activity-tracker.plist, loads it, and starts:
activity_tracker --data-dir <root> track --quietThe LaunchAgent stores the data root, interval, and idle-threshold arguments.
service install --json returns:
- plist path
- binary path
- data root
- load state
- persisted interval and idle config
Invalid binaries fail with error.code: "invalid_service_binary".
service uninstall --json returns the plist path and whether the plist is removed or already absent.
Default idle threshold is 300 seconds and default sampling interval is 2 seconds. Foreground or service runs can override them:
activity_tracker track --idle-threshold-seconds 120
activity_tracker service install --idle-threshold-seconds 120cargo fmt
cargo test
cargo clippy --all-targets --all-features -- -D warningsProject rules for future agents live in AGENTS.md. Repo-local skill metadata lives in .codex/skills/activity-tracker-ai/.
All data is local. The app does not send logs to external services. Browser URLs are sensitive; keep exports private and do not commit logs.