| name | time-lens |
| description | Analyze and visualize time spent on software projects by combining data from multiple sources: WakaTime coding time, git commit session detection, Claude Code usage, Codex CLI usage, and Cursor IDE usage. Produces both an interactive HTML dashboard (dark-themed, Chart.js) and a Markdown report with ASCII charts. Use when the user asks to: analyze work hours, calculate time spent on a project, generate a work hours report, visualize coding activity, create a project time breakdown, or summarize development effort across date ranges.
|
Project Time Tracker
Combines five data sources → reconciles → produces HTML dashboard + Markdown report.
Scripts
All scripts live in scripts/ next to this SKILL.md. Run them with python3:
| Script | Purpose | Key flags |
|---|
git_sessions.py | Parse git history → sessions → hours | <repo> --since YYYY-MM-DD --until YYYY-MM-DD [--raw] |
wakatime_fetch.py | WakaTime API → daily hours, filtered to project | --start YYYY-MM-DD --end YYYY-MM-DD --project name |
claude_messages.py | Claude Code user prompts + activity timestamps per day | --project-path /abs/path or --filter name |
codex_messages.py | Codex CLI user prompts + activity timestamps per day | --project-path /abs/path or --filter name |
cursor_messages.py | Cursor IDE user prompts + activity timestamps per day | --project-path /abs/path or --filter name |
reconcile.py | Merges the JSON output of the scripts above into a single hour estimate, with an optional supervised-vs-unattended split | --git f.json --claude f.json --codex f.json --since Y-M-D --until Y-M-D [--gap-hours 0.5] [--presence-window-min 10] [--tz Area/City] [--daily] |
claude_messages.py, codex_messages.py, and cursor_messages.py each output two timestamp arrays: timestamps (user prompts only — for prompt-count metrics) and activity_timestamps (every timestamped event, including tool-call/tool-result cycles and assistant replies — use this one for reconciliation). See "Use activity_timestamps, not timestamps" in references/reconciliation.md for why this distinction matters — using prompt-only timestamps for hour reconciliation silently drops real work time.
Use reconcile.py instead of hand-rolling the merge in Python. It's a deterministic implementation of the algorithm below — run it on the JSON files the other scripts already produced rather than re-deriving the merge/sessionize/intersect logic inline each time. Hand-rolling it live is exactly how a real bug happened once: two different gap-threshold runs (15min vs 30min "engine time") got compared against each other as if they were the same base, producing a nonsensical number. reconcile.py fixes the gap threshold and presence window as explicit flags so every run is reproducible and comparable.
Workflow
1. Determine scope
Ask for or infer:
- Project directory/directories (git repos)
- Date range (first commit → last commit, or user-specified)
- Output location for HTML + markdown files
Auto-discover sub-repos: By default, scan the project directory for .git folders in subdirectories (not just the root). Each parent of a .git directory is a sub-repo to analyze.
find /path/to/project -name ".git" -type d 2>/dev/null | sort
This produces a list like:
/path/to/project/frontend/.git
/path/to/project/backend/.git
/path/to/project/libs/shared/.git
Each of these (minus the /.git suffix) is a repo to run git_sessions.py, claude_messages.py, and codex_messages.py on. Also run these scripts on the root project directory itself (for Claude/Codex messages sent from the root, which is common when using monorepo-style workflows).
2. Extract data
Run all five scripts on every discovered repo. For git, Claude, Codex, and Cursor, run per sub-repo. For WakaTime, use the multi-project discovery approach described below.
python3 git_sessions.py /path/to/project/frontend --since 2026-01-15 --until 2026-02-02
python3 git_sessions.py /path/to/project/backend --since 2026-01-15 --until 2026-02-02
python3 claude_messages.py --project-path /path/to/project
python3 claude_messages.py --project-path /path/to/project/frontend
python3 claude_messages.py --project-path /path/to/project/backend
python3 codex_messages.py --project-path /path/to/project
python3 codex_messages.py --project-path /path/to/project/frontend
python3 codex_messages.py --project-path /path/to/project/backend
python3 cursor_messages.py --project-path /path/to/project
python3 cursor_messages.py --project-path /path/to/project/frontend
python3 cursor_messages.py --project-path /path/to/project/backend
WakaTime multi-project discovery: WakaTime often tracks sub-directories as separate projects (e.g., a monorepo at my-project/ may have WakaTime projects named my-project, frontend, backend, shared). A single --project query will miss the others.
-
First, run wakatime_fetch.py without --project to get the full project list for the date range:
python3 wakatime_fetch.py --start 2026-01-15 --end 2026-02-02
-
Filter the returned projects list for names matching any of:
- The root project directory basename (e.g.,
my-project)
- Any sub-repo directory basename (e.g.,
frontend, backend)
- Any intermediate directory basename that contains a sub-repo (e.g.,
libs)
-
Fetch intervals for each matching project:
python3 wakatime_fetch.py --start 2026-01-15 --end 2026-02-02 --project my-project
python3 wakatime_fetch.py --start 2026-01-15 --end 2026-02-02 --project frontend
python3 wakatime_fetch.py --start 2026-01-15 --end 2026-02-02 --project backend
-
Combine all intervals from all matching WakaTime projects into a single list for reconciliation.
Why this matters: In a project with 4 sub-repos, a single --project query captured only 9h of the actual 26.5h of WakaTime data. The other 17.5h was tracked under sub-directory project names.
Folder move detection: If claude_messages.py, codex_messages.py, or cursor_messages.py return 0 results, check the output for alternate_paths. If present, ask the user:
"No Claude/Codex history found at /current/path, but found sessions for project-name at /old/path. Was this project moved? Should I include that history too?"
If confirmed, re-run with --project-path /old/path and merge timestamps from both paths.
See references/folder-move-detection.md for full detection logic and edge cases.
3. Reconcile hours
Merged total = best estimate (git ∪ Claude ∪ Codex ∪ Cursor ∪ WakaTime intervals, no double-counting). Run reconcile.py on the JSON files from step 2 rather than hand-rolling the merge:
python3 reconcile.py \
--git git_backend.json --git git_frontend.json \
--claude claude_backend.json --claude claude_frontend.json \
--codex codex_backend.json --codex codex_frontend.json \
--cursor cursor_backend.json \
--waka waka.json \
--since 2026-06-29 --until 2026-08-19 \
--tz Europe/Berlin --daily
(--git/--claude/--codex/--cursor/--waka are all repeatable — pass one per sub-repo for multi-repo projects; reconcile.py merges them together.)
This does, deterministically:
- Take git's raw commit
timestamps, and Claude/Codex/Cursor's activity_timestamps (not timestamps — see below) as point events.
- Sessionize each source independently via the gap threshold (
--gap-hours, default 0.5h; use 0.25 for a stricter/conservative estimate).
- Union WakaTime's ready-made
[start, end] intervals in alongside the sessionized point sources.
- Merge everything that overlaps or sits within the gap threshold into final sessions.
- Sum raw durations — no
+0.5h buffer, no 0.5h floor (legacy behavior; see reconciliation.md if you need it, e.g. for a timestamps-only source with no activity-level granularity).
- If
--presence-window-min > 0 (default 10): also compute a supervised vs. unattended split — how much of the merged engine time falls within that window of an actual user prompt or git commit, vs. how much is the agent running with no human check-in nearby. See "Supervised vs. unattended time" below.
- If
--daily: bucket per day using an 08:00 local-time boundary (--day-boundary-hour, default 8) so an overnight session doesn't get split across two days in the table.
Use activity_timestamps, never timestamps, for engine-time reconciliation (this is what reconcile.py does automatically). timestamps is prompt-only and will make an actively-worked session look like a single 0-duration point whenever the agent kept running after the last recorded prompt. activity_timestamps includes tool-call/tool-result cycles and assistant replies, so it reflects when the agent was actually still working. See references/reconciliation.md for the concrete example (a "0-duration" session that turned out to be ~50 minutes of continuous work) and why the gap threshold dropped from the old 1.5h default to 0.5h.
Don't compare engine_hours across different --gap-hours runs as if they're the same measurement — a 15min-threshold total and a 30min-threshold total are two different quantities. If you need both, run reconcile.py twice and label each result with its threshold.
Supervised vs. unattended time
engine_hours measures when the agent was active — not necessarily when the human was watching. If a task ran autonomously for 20+ minutes with no new prompt, that time still counts as engine time but may not reflect real hands-on-keyboard effort. reconcile.py's supervised split addresses this: it builds a "presence window" of ± --presence-window-min minutes around every real user prompt or git commit, and reports how much of engine_hours falls inside vs. outside that window.
The window size is a real sensitivity knob, not a solved parameter — on one real project, ±5min gave 72% supervised, ±30min gave 99%. ±10min is the recommended default: long enough to cover a normal "read the output, think, reply" pause, tight enough that a genuine away-from-keyboard stretch still shows up as unattended. Always report which window was used alongside the number.
Why merge matters: AI agent prompts (Claude/Codex/Cursor) often appear minutes before/after git commits in the same work session. WakaTime captures IDE keystrokes that may fall between commits. A user might research with Claude, use Cursor's AI, write code (WakaTime), then commit (git) — all one session. Union of all five sources captures the true session boundaries without double-counting.
- The merged total replaces "git-only" as the primary estimate
- WakaTime hours shown for reference (active keystrokes only, always lower)
See references/reconciliation.md for the full underlying algorithm reconcile.py implements, the day-boundary bucketing rationale, and the legacy buffer/floor formula.
4. Generate HTML dashboard
Write a single-file HTML with inline Chart.js (CDN). Dark theme (#0a0a0a bg, #1a1a1a cards).
Required sections:
- Stat cards — Merged total (git∪claude∪codex∪cursor∪waka), Git estimate, WakaTime, Sessions, Commits, Claude prompts, Codex prompts, Cursor prompts
- Daily activity chart — Overlapping bars (git + WakaTime + merged) + AI prompts line on secondary axis
- Gantt timeline — UTC horizontal bars; git, Claude, Codex, and Cursor as separate colored datasets on same chart (separate swimlane rows when they overlap on same day)
- Data table — Session | Time (UTC) | Active | Est. | WakaTime | Claude | Codex | Cursor | Commits
Chart.js essentials:
Chart.defaults.color = '#888';
Chart.defaults.borderColor = '#2a2a2a';
{
type: 'line',
yAxisID: 'yClaude',
}
Save as <project-dir>/work-hours-analysis.html.
5. Generate Markdown report
# [Project] - Work Hours Analysis
## Summary
**Estimated Total Working Hours: Xh** (based on commit timing analysis)
**Supervised: Xh (Y%) · Unattended: Xh** (±10min presence window — see Methodology)
| Date | Sessions | Time Range | Git Est. | WakaTime | Commits | Project |
...
## Timeline
- Start: [date], End: [date], Duration: N days
## Per-Project Breakdown
[sub-project sections with commit/session counts]
## Charts
### Daily Activity (ASCII)
Jan 15 ██░░░░░░░░░░░░░░░░░░ 0.5h
...
### Project Distribution
project-a ████████████████████ 45% (~18h)
...
## Methodology
- Session Detection: events within 0.5h gap = same session (git commits + Claude/Codex/Cursor activity_timestamps, not just prompts)
- Hour Estimate: Σ(session_duration), no buffer, no floor — activity_timestamps already captures real start/end
- Supervised vs. Unattended: hours within ±10min of a real user prompt/commit count as supervised; the rest is the agent running with no recent human check-in
- Why Git > WakaTime: WakaTime only tracks active IDE typing; git includes thinking/research/AI prompting
ASCII bar: blocks = round(hours / max_hours * 20), █ filled, ░ empty, 20 cols wide.
Save as <project-dir>/total_hours.md.
Validation Checklist
Before running
After generation
Examples
Example 1: Single repo, standard report
Trigger phrase: "How many hours did I spend on the api-server project this month? Generate the full report."
Actions:
- Determine scope: project at
/Users/alice/code/api-server, date range inferred as 2026-02-01 → 2026-02-23 (current month to today).