Build Memtime
KINDAreplaces $14/mosaves $168/yrback to the verdict
A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the agent and the review interface.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why This is platform-specific and on some systems requires an accessibility or screen recording permission, which changes what you must tell the user.
Get it Whatever your platform provides, and find out which permission it needs before designing the onboarding.
Verify
The focused application, its window title and idle seconds can all be read - decidefree
Why Deciding what not to record after you have a month of recordings is deciding it too late, since the data already exists.
Get it List the applications and title patterns that must never be stored: banking, health, private messaging, anything belonging to someone else. Err wide.
Verify
The list exists and is loaded by the sampler before the first run - have readyfree, and it takes a week
Why Segmentation thresholds and block summarisation can only be tuned against a real fragmented day, and a synthetic one is far tidier than reality.
Get it Run the capture for a week and reconstruct each day from memory to compare against.
Verify
A week of capture exists and you have tried to reconstruct at least three days from it
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DB_PATH | required | ~/.local/share/memtime/capture.db | Raw capture. The most sensitive file this build produces. Encrypted at rest. |
DB_KEYsecret | required | a passphrase or keychain reference | Encrypts the capture store. Without it the raw titles sit in plain text. |
SAMPLE_INTERVAL_SECONDS | required | 15 | How often focus is sampled. Shorter is more accurate and more intrusive. |
IDLE_THRESHOLD_SECONDS | required | 180 | When you are considered away. Blocks end at idle rather than continuing. |
RAW_RETENTION_DAYS | required | 30 | How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. |
EXCLUSIONS_PATH | required | ./config/exclusions.txt | Loaded by the sampler before the first write. Not a display filter. |
The build, in order
Capture, with the exclusions applied first
Your day is recorded, minus everything you said not to record.
Application, title, document, idle seconds. Nothing else.
Files
src/capture.tsIn the sampler. Display filtering leaves the data on disk.
Files
src/exclude.tssrc/exclude.test.tsNo sampling, and a visible gap rather than an interpolated one.
Files
src/capture.ts
done when · tick each as it passeswatch out- Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write.
Turning a fragmented day into blocks
A day of thousands of samples reads as a dozen recognisable blocks.
Tune it against your own day. This is the design work, not a constant to guess.
Files
src/segment.tssrc/segment.test.tsChecking a message does not end a block of work.
Files
src/segment.tsA block needs a label you recognise, not a list of forty window titles.
Files
src/summarise.ts
done when · tick each as it passeswatch out- Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day.
Reconstructing a day you did not track
You can sit down at five and rebuild the day accurately.
The point is recall. You need to see what was on screen at four o'clock.
Files
src/timeline.tsCombining several blocks is the normal case, not the exception.
Files
src/entries.tsAn unreviewed automatic timesheet gets invoiced. That is the failure.
Files
src/suggest.ts
done when · tick each as it passeswatch out- Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry.
Rounding and billing rules, stated honestly
Exported time matches the rule you agreed with the client.
Store actual. A client asking what the real figure was deserves an answer.
Files
src/rounding.tssrc/rounding.test.tsActual and rounded, side by side. Hiding the difference is how disputes start.
Files
src/export.tsBoth are ordinary. Both break naive duration arithmetic.
Files
src/duration.ts
done when · tick each as it passeswatch out- Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both.
Retention, and the boundary that must holdproduct builder
Raw capture expires, approved entries persist, and nothing raw ever leaves.
They are the sensitive data and their value drops to nothing within weeks.
Files
src/retention.tsScan export and log paths for raw fields. Fail the build if any appear.
Files
tests/no-raw-export.test.tsSamples, blocks, entries, backups. Everything.
Files
src/deletion.ts
done when · tick each as it passeswatch out- An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear.
That is the whole plan for Memtime. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Team deployment with manager visibility.
- Automatic project assignment from window content.
- Screenshot capture.
- Mobile and browser tab level detail.
- calendar and project integrations, team reporting, sync, and enterprise controls
- cross-device history
- team timesheets and payroll
- behavioral datasets
- blocking across every platform
- Calendar events shown alongside blocks, which fills the gaps capture cannot see
- A weekly reconstruction prompt, since the recall value decays quickly
- Invoice generation from approved entries, once the rounding rules are trustworthy
Need the files? The project pack on the verdict page hands your agent the whole brief · more time tracking.