Building CodePulse: A Privacy-First VS Code Time Tracker
I use time-tracking data the way a runner uses pace data. Not to justify my hours, but to see where the friction actually is. For a long time I used hosted tools for this, and they worked, but every one of them shipped my keystrokes, file paths, and project names off to a third party. When I started CodePulse, the plan was simple: keep everything local, keep the signals honest, and make the dashboard useful enough that I would actually open it.
Why Build Another One
The existing tools fall into two camps. The hosted ones are polished but opaque, and they keep improving their UX while slowly expanding what they collect. The self-hosted ones are privacy-respecting but fragile, and most of them stopped being maintained years ago. CodePulse sits in a third spot: everything runs inside VS Code, the data stays on disk, and a small local REST server exposes the numbers for anyone who wants to build their own views on top of it.
Architecture in Three Pieces
At the core is a SQLite database under the user's extension storage. Each coding session is a row, broken down by file, project, and language, with a heartbeat cadence that resumes after short breaks and closes out after real idle periods. Above the database is a small service layer that groups sessions into daily, weekly, and monthly rollups so the UI does not have to do aggregation in the critical path. On top of that is a dashboard that renders the rollups directly from the local store.
The heartbeat itself is deceptively simple. A timer ticks in the background, but the "is the user still here" logic is not just about keystrokes. It is about whether VS Code has focus, whether a file is open, and whether activity has happened inside a tolerance window. The idle detector is where most of the subtle bugs live, because a strict threshold punishes thinking time, and a loose one counts the time I walked away to get coffee.
How Claude Shaped the Early Design
I used Claude mostly to compress the decision loop. Instead of drafting three approaches to the heartbeat on my own, I would describe the constraint, ask for a couple of candidate implementations, and immediately prune the ones that ignored edge cases I knew mattered. That shortcut mattered most in the analytics layer, where the shape of the query defines what visualizations are even possible later.
I also leaned on Claude for the productivity scoring logic. Code churn, keystroke velocity, and focus blocks are trivial to compute in isolation, but combining them into a single score that is not misleading is harder. The first few variants were too punishing on days with a lot of reading. Several iterations later, the score treated reading as legitimate work when paired with light edits, and the numbers finally lined up with how a day actually felt.
The Boring Parts Still Matter
A tracker lives or dies on the small things. Starting cleanly when VS Code opens. Resuming correctly after sleep. Not spiking CPU during heavy edit sessions. Handling multi-root workspaces without double-counting. None of those are glamorous, but together they decide whether the extension stays installed.
I also exposed a local REST API with a minimal surface, so I could build ad-hoc views in other tools without touching the core. Having a stable, documented boundary between the tracker and the analytics on top of it kept both sides easy to change.
Shipping as a Tool I Actually Use
The real test was installing the release version and forgetting about it for a few weeks. When I finally opened the dashboard, the patterns matched my memory of the weeks, which is the first honest signal that the numbers mean something. After that, the small improvements came naturally: better categorization, sharper focus-block detection, cleaner export formats.
CodePulse is not trying to be a platform. It is trying to be a quiet, reliable instrument that tells me something true about my own workdays. With the data staying local and the logic staying inspectable, it finally does.