Vibecode RescueTime
track this build5 phases, 15 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For RescueTime, measure opt-in application time, classify it, and run focused sessions locally. The hard boundary is cross-device history, website blocking, goals, reports, and long-term behavioral dataset, plus ubiquitous capture, coaching, and team workflow.
You are building a lean indie version of RescueTime. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # RescueTime · indie build A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift for macOS, or an equivalent native agent per platform | reading the frontmost application is a platform API and there is no cross-platform shortcut | | Sampling | A timer reading the frontmost application and window title | sampling is far simpler than event hooks and is accurate enough at this granularity | | Storage | SQLite locally, never a server | this is a record of everything you did on your computer and it should not leave it | | Classification | Rules you write, evaluated in order, with a visible fallback | someone else's productivity taxonomy is wrong about your work in ways you cannot correct | | Idle detection | System idle time, so away time is not counted | an hour at lunch recorded as an hour of work makes every number meaningless | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer, or an equivalent platform target** · free if you own one - Why: Reading the frontmost application is a platform API. The design carries across platforms; the implementation does not. - Get it: Apple menu, then About This Mac, if you are targeting macOS. - Verify: About This Mac reports macOS 14 or higher - [ ] **Xcode 16 or newer** · free - Why: Builds the agent and provides the profiler for the sampling loop. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **A decision about window titles** · free - Why: Application names need no permission. Titles need Accessibility and are considerably more revealing, so this is a deliberate choice rather than a default. - Get it: Decide before Phase 2 whether you want titles at all. If yes, the hashing default and the per-application allowlist are what make it defensible. - Verify: The decision is recorded in the README - [ ] **A week of your own real usage** · free, but it takes a week - Why: Rules cannot be written in the abstract. You need to see what applications actually appear before you can categorise them usefully. - Get it: Run Phases 1 and 2 for a week before writing rules in Phase 3. The uncategorised list is your rule backlog. - Verify: A week of samples exists across normal working days - [ ] **Apple Developer Program membership** (optional) · 99 USD per year - Why: Product phase only, and only if you distribute it. - Get it: Enrol at developer.apple.com/programs. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. - cross-device history, website blocking, goals, reports, and long-term behavioral dataset - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for RescueTime, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · RescueTime The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local time tracker that records which applications you used, classified by rules you wrote to replace RescueTime. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This measures your own time on your own machine, and nothing about it should be designed to be shown to anyone else. It stays local, it is pausable at any moment, and it classifies according to rules you wrote rather than a score somebody else defined. It also must not moralise. Recording that four hours went to a browser is a fact; calling it unproductive is a judgement, and one that is wrong often enough to be actively unhelpful. Phase 4 makes the neutrality a test rather than an intention. ### Stack (fixed, do not substitute) - Language: Swift for macOS, or an equivalent native agent per platform · reading the frontmost application is a platform API and there is no cross-platform shortcut - Sampling: A timer reading the frontmost application and window title · sampling is far simpler than event hooks and is accurate enough at this granularity - Storage: SQLite locally, never a server · this is a record of everything you did on your computer and it should not leave it - Classification: Rules you write, evaluated in order, with a visible fallback · someone else's productivity taxonomy is wrong about your work in ways you cannot correct - Idle detection: System idle time, so away time is not counted · an hour at lunch recorded as an hour of work makes every number meaningless ### Data model (create this before Phase 1) - samples(at, bundle_id, app_name, window_title_hash, idle_seconds) · the raw record, sampled on an interval - intervals(id, bundle_id, started_at, ended_at, category_id, title_sample) · samples collapsed into runs, which is what you actually look at - rules(id, priority, match_kind, pattern, category_id, enabled) · match on bundle identifier or title pattern - categories(id, name, color) · named by you, with no built-in productive or unproductive distinction - sessions(id, started_at, ended_at, intent, completed) · focus sessions, with the intent you typed - pauses(started_at, ended_at, reason) · recorded, because a tracker that hides its gaps is lying by omission - Window titles are hashed by default rather than stored, because a title often contains a document name, a customer name or a search query. ### Phase 1 · Sampling, with a pause you can always reach Build: A background agent sampling the frontmost application on an interval. A menu bar control showing that recording is on, with pause and resume. Record pauses as rows so gaps in the data are explained rather than mysterious. Trap: A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. Done when: The agent records samples while running and the menu bar shows recording is on. Pausing stops sampling within one interval and records a pause row. Resuming continues. Quitting the agent leaves the database consistent rather than mid-write. Do not build yet: No window titles, no classification, no reports. ### Phase 2 · Idle time and window titles, carefully Build: Read system idle time and stop attributing time after a threshold. Optionally capture window titles behind the Accessibility permission, hashing them by default and storing plain text only for applications you explicitly allow. Trap: Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. Done when: Leaving the machine for longer than the idle threshold stops accumulating time, verified against the wall clock. Window titles are stored hashed by default. An application added to the plain-title allowlist stores readable titles and others do not. Refusing Accessibility leaves application tracking working and says what is lost. Do not build yet: No classification or reports yet. ### Phase 3 · Rules you wrote Build: Rules matching on bundle identifier or title pattern, evaluated in priority order, with an explicit uncategorised fallback. Show which rule classified each interval. Reclassifying historical data when rules change must be a deliberate action. Trap: Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. Done when: An interval shows which rule classified it. Uncategorised time is visible as its own category rather than hidden. Changing a rule does not alter historical classification until reclassification is explicitly run. Reclassifying reports how many intervals changed before applying. Do not build yet: No reports or focus sessions yet. ### Phase 4 · Reports without a verdict Build: Daily and weekly views by category and application, with gaps shown as gaps. No productivity score, no grade, no streak, no comparison against other users. Add a copy lint with a banned word list that fails the build if judgement language appears. Trap: A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. Done when: Daily and weekly reports render from real data with gaps preserved rather than filled. The copy lint fails when a banned word is deliberately added and passes on shipped strings. No screen shows a score, grade, streak or comparison to anyone else. Uncategorised time appears in every total rather than being excluded. Do not build yet: No focus sessions or export yet. ### Phase 5 · Focus sessions Build: A session with a duration and a typed intent. During it, optionally show a gentle reminder when focus moves to an application outside the session's categories. Afterwards, show what the time actually contained without judging it. Trap: Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. Done when: A session records its intent and duration. Switching to an application outside the session's categories produces a dismissible reminder rather than a block. The session summary shows where the time went with no verdict. An abandoned session is recorded as incomplete rather than deleted. Do not build yet: Export and retention come last. ### Phase 6 · Export, retention and deletion Build: Export as JSON and CSV. Retention that removes raw samples while keeping aggregated intervals. Deletion of a time range or an application entirely, covering samples, intervals and sessions together. Trap: A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. Done when: Export produces JSON and CSV covering samples, intervals, categories and sessions. Retention removes raw samples past the window and keeps aggregates, verified by row counts. Deleting an application removes it from samples, intervals and sessions, verified by querying afterwards. Deleting a date range does the same. Do not build yet: Nothing after this. ### Out of scope (and why) - Cross-device history. Time on your phone and on a second computer are not here, which limits what the numbers mean and is worth stating. - Website blocking and enforcement, deliberately, because enforcement is worked around and it corrupts the measurement. - The long-term behavioural dataset and the comparisons that come from it, which need many users' data. - Team timesheets and anything that reports your time to someone else. This is a tool for you. ### README must contain - That everything stays on the machine and nothing is uploaded - That window titles are hashed by default and plain titles are opt-in per application - That classification uses rules you wrote, and that changing one does not rewrite history - That there is no productivity score, and that the copy lint enforces the neutral language - The retention window, and how to delete an application or a date range entirely ===== AGENTS.md ===== # Agent instructions · RescueTime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift for macOS, or an equivalent native agent per platform, A timer reading the frontmost application and window title, SQLite locally, never a server, Rules you write, evaluated in order, with a visible fallback, System idle time, so away time is not counted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ===== BUILD_PLAN.md ===== # Build plan · RescueTime A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sampling, with a pause you can always reach The agent records which application has focus, and stopping it takes one action. ### Steps 1. Sample the frontmost application On an interval through the workspace API. No permission is needed for the application name alone. Files: `Sampler.swift` 2. Make the state visible and pausable A menu bar item showing recording, with pause and resume one click away. Files: `MenuBar.swift` 3. Record pauses as data A gap with a reason is honest; an unexplained gap is a bug you will chase later. Files: `Store.swift` ### Done when - [ ] Samples are recorded while running - [ ] Pause stops sampling within one interval and records a row - [ ] The menu bar always shows whether recording is on - [ ] Quitting leaves the database consistent ### Watch out - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. ## Phase 2 · Idle time and window titles, carefully Time away is not counted, and titles do not become a log of everything you read. ### Steps 1. Detect idle and stop counting System idle time with a threshold you set. Verify against the clock rather than trusting the API. Files: `Idle.swift` 2. Hash titles by default Hashing gives you change detection without storing content. A plain-title allowlist is opt-in per application. Files: `Titles.swift` 3. Degrade without Accessibility Application names still work. Say what titles would have added rather than disabling everything. Files: `Permissions.swift` ### Done when - [ ] Idle time beyond the threshold is not counted - [ ] Titles are hashed by default - [ ] Only allowlisted applications store readable titles - [ ] Refusing Accessibility keeps application tracking working ### Watch out - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. ## Phase 3 · Rules you wrote Time is grouped into categories you defined, and you can see why each interval landed where it did. ### Steps 1. Evaluate rules in priority order First match wins, with the matching rule recorded on the interval. Files: `Rules.swift` 2. Make uncategorised visible Its own category. Hiding it makes the totals look complete when they are not. Files: `Categories.swift` 3. Make reclassification explicit With a count of affected intervals shown before it runs. Files: `Reclassify.swift` ### Done when - [ ] Each interval shows the rule that classified it - [ ] Uncategorised time is visible as a category - [ ] Changing a rule leaves history alone until reclassification runs - [ ] Reclassification reports the affected count first ### Watch out - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. ## Phase 4 · Reports without a verdict You can see where the time went, and the application does not tell you how to feel about it. ### Steps 1. Build daily and weekly views By category and by application, with gaps and pauses visible. Files: `Reports.swift` 2. Write the copy lint Banned words including productive, unproductive, wasted, distracted, score, grade and streak. Fails the build. Files: `scripts/copy-lint.swift`, `scripts/banned-words.json` 3. Include uncategorised everywhere In every total. Excluding it flatters the numbers and hides the rules you have not written. Files: `Reports.swift` ### Done when - [ ] Reports render with gaps preserved - [ ] The copy lint fails on a planted word and passes on shipped strings - [ ] No score, grade, streak or comparison appears - [ ] Uncategorised time is included in totals ### Watch out - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. ## Phase 5 · Focus sessions You can start a session with an intent and see afterwards what actually happened. ### Steps 1. Start a session with an intent Typed, in the user's words. It is what makes the summary meaningful afterwards. Files: `Sessions.swift` 2. Remind, never block Dismissible, and never repeated more than a configured rate. Blocking is worked around and loses the data. Files: `Sessions.swift` 3. Summarise without judging Where the time went against the intent. The user draws the conclusion. Files: `SessionSummary.swift` ### Done when - [ ] A session records intent and duration - [ ] Leaving the session's categories reminds rather than blocks - [ ] The summary shows time without a verdict - [ ] An abandoned session is recorded as incomplete ### Watch out - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ## Not in this build - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. ## After v1, if you want it - A weekly review that asks what you want to change rather than telling you - Per-project categories driven by the folder a project lives in - An annotation for a block of time, so a meeting is labelled rather than guessed at ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The local database. This is a detailed record of your computer use, so treat the file accordingly. DB_PATH=~/Library/Application Support/Time/time.db # Required. How often focus is sampled. Shorter is more accurate and produces more rows. SAMPLE_INTERVAL_SECONDS=15 # Required. How long without input before time stops being attributed. IDLE_THRESHOLD_SECONDS=180 # Required. Whether window titles are stored readable. False by default; the allowlist is the exception. STORE_PLAIN_TITLES=false # Optional. Applications whose titles may be stored readable, chosen deliberately. PLAIN_TITLE_ALLOWLIST=com.apple.dt.Xcode # Required. How long raw samples are kept. Aggregated intervals survive beyond this. SAMPLE_RETENTION_DAYS=90
You are building a lean indie version of RescueTime. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # RescueTime · indie build A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift for macOS, or an equivalent native agent per platform | reading the frontmost application is a platform API and there is no cross-platform shortcut | | Sampling | A timer reading the frontmost application and window title | sampling is far simpler than event hooks and is accurate enough at this granularity | | Storage | SQLite locally, never a server | this is a record of everything you did on your computer and it should not leave it | | Classification | Rules you write, evaluated in order, with a visible fallback | someone else's productivity taxonomy is wrong about your work in ways you cannot correct | | Idle detection | System idle time, so away time is not counted | an hour at lunch recorded as an hour of work makes every number meaningless | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer, or an equivalent platform target** · free if you own one - Why: Reading the frontmost application is a platform API. The design carries across platforms; the implementation does not. - Get it: Apple menu, then About This Mac, if you are targeting macOS. - Verify: About This Mac reports macOS 14 or higher - [ ] **Xcode 16 or newer** · free - Why: Builds the agent and provides the profiler for the sampling loop. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **A decision about window titles** · free - Why: Application names need no permission. Titles need Accessibility and are considerably more revealing, so this is a deliberate choice rather than a default. - Get it: Decide before Phase 2 whether you want titles at all. If yes, the hashing default and the per-application allowlist are what make it defensible. - Verify: The decision is recorded in the README - [ ] **A week of your own real usage** · free, but it takes a week - Why: Rules cannot be written in the abstract. You need to see what applications actually appear before you can categorise them usefully. - Get it: Run Phases 1 and 2 for a week before writing rules in Phase 3. The uncategorised list is your rule backlog. - Verify: A week of samples exists across normal working days - [ ] **Apple Developer Program membership** (optional) · 99 USD per year - Why: Product phase only, and only if you distribute it. - Get it: Enrol at developer.apple.com/programs. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. - cross-device history, website blocking, goals, reports, and long-term behavioral dataset - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for RescueTime, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · RescueTime The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local time tracker that records which applications you used, classified by rules you wrote to replace RescueTime. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This measures your own time on your own machine, and nothing about it should be designed to be shown to anyone else. It stays local, it is pausable at any moment, and it classifies according to rules you wrote rather than a score somebody else defined. It also must not moralise. Recording that four hours went to a browser is a fact; calling it unproductive is a judgement, and one that is wrong often enough to be actively unhelpful. Phase 4 makes the neutrality a test rather than an intention. ### Stack (fixed, do not substitute) - Language: Swift for macOS, or an equivalent native agent per platform · reading the frontmost application is a platform API and there is no cross-platform shortcut - Sampling: A timer reading the frontmost application and window title · sampling is far simpler than event hooks and is accurate enough at this granularity - Storage: SQLite locally, never a server · this is a record of everything you did on your computer and it should not leave it - Classification: Rules you write, evaluated in order, with a visible fallback · someone else's productivity taxonomy is wrong about your work in ways you cannot correct - Idle detection: System idle time, so away time is not counted · an hour at lunch recorded as an hour of work makes every number meaningless ### Data model (create this before Phase 1) - samples(at, bundle_id, app_name, window_title_hash, idle_seconds) · the raw record, sampled on an interval - intervals(id, bundle_id, started_at, ended_at, category_id, title_sample) · samples collapsed into runs, which is what you actually look at - rules(id, priority, match_kind, pattern, category_id, enabled) · match on bundle identifier or title pattern - categories(id, name, color) · named by you, with no built-in productive or unproductive distinction - sessions(id, started_at, ended_at, intent, completed) · focus sessions, with the intent you typed - pauses(started_at, ended_at, reason) · recorded, because a tracker that hides its gaps is lying by omission - Window titles are hashed by default rather than stored, because a title often contains a document name, a customer name or a search query. ### Phase 1 · Sampling, with a pause you can always reach Build: A background agent sampling the frontmost application on an interval. A menu bar control showing that recording is on, with pause and resume. Record pauses as rows so gaps in the data are explained rather than mysterious. Trap: A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. Done when: The agent records samples while running and the menu bar shows recording is on. Pausing stops sampling within one interval and records a pause row. Resuming continues. Quitting the agent leaves the database consistent rather than mid-write. Do not build yet: No window titles, no classification, no reports. ### Phase 2 · Idle time and window titles, carefully Build: Read system idle time and stop attributing time after a threshold. Optionally capture window titles behind the Accessibility permission, hashing them by default and storing plain text only for applications you explicitly allow. Trap: Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. Done when: Leaving the machine for longer than the idle threshold stops accumulating time, verified against the wall clock. Window titles are stored hashed by default. An application added to the plain-title allowlist stores readable titles and others do not. Refusing Accessibility leaves application tracking working and says what is lost. Do not build yet: No classification or reports yet. ### Phase 3 · Rules you wrote Build: Rules matching on bundle identifier or title pattern, evaluated in priority order, with an explicit uncategorised fallback. Show which rule classified each interval. Reclassifying historical data when rules change must be a deliberate action. Trap: Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. Done when: An interval shows which rule classified it. Uncategorised time is visible as its own category rather than hidden. Changing a rule does not alter historical classification until reclassification is explicitly run. Reclassifying reports how many intervals changed before applying. Do not build yet: No reports or focus sessions yet. ### Phase 4 · Reports without a verdict Build: Daily and weekly views by category and application, with gaps shown as gaps. No productivity score, no grade, no streak, no comparison against other users. Add a copy lint with a banned word list that fails the build if judgement language appears. Trap: A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. Done when: Daily and weekly reports render from real data with gaps preserved rather than filled. The copy lint fails when a banned word is deliberately added and passes on shipped strings. No screen shows a score, grade, streak or comparison to anyone else. Uncategorised time appears in every total rather than being excluded. Do not build yet: No focus sessions or export yet. ### Phase 5 · Focus sessions Build: A session with a duration and a typed intent. During it, optionally show a gentle reminder when focus moves to an application outside the session's categories. Afterwards, show what the time actually contained without judging it. Trap: Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. Done when: A session records its intent and duration. Switching to an application outside the session's categories produces a dismissible reminder rather than a block. The session summary shows where the time went with no verdict. An abandoned session is recorded as incomplete rather than deleted. Do not build yet: Export and retention come last. ### Phase 6 · Export, retention and deletion Build: Export as JSON and CSV. Retention that removes raw samples while keeping aggregated intervals. Deletion of a time range or an application entirely, covering samples, intervals and sessions together. Trap: A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. Done when: Export produces JSON and CSV covering samples, intervals, categories and sessions. Retention removes raw samples past the window and keeps aggregates, verified by row counts. Deleting an application removes it from samples, intervals and sessions, verified by querying afterwards. Deleting a date range does the same. Do not build yet: Nothing after this. ### Out of scope (and why) - Cross-device history. Time on your phone and on a second computer are not here, which limits what the numbers mean and is worth stating. - Website blocking and enforcement, deliberately, because enforcement is worked around and it corrupts the measurement. - The long-term behavioural dataset and the comparisons that come from it, which need many users' data. - Team timesheets and anything that reports your time to someone else. This is a tool for you. ### README must contain - That everything stays on the machine and nothing is uploaded - That window titles are hashed by default and plain titles are opt-in per application - That classification uses rules you wrote, and that changing one does not rewrite history - That there is no productivity score, and that the copy lint enforces the neutral language - The retention window, and how to delete an application or a date range entirely ===== AGENTS.md ===== # Agent instructions · RescueTime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift for macOS, or an equivalent native agent per platform, A timer reading the frontmost application and window title, SQLite locally, never a server, Rules you write, evaluated in order, with a visible fallback, System idle time, so away time is not counted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ===== BUILD_PLAN.md ===== # Build plan · RescueTime A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sampling, with a pause you can always reach The agent records which application has focus, and stopping it takes one action. ### Steps 1. Sample the frontmost application On an interval through the workspace API. No permission is needed for the application name alone. Files: `Sampler.swift` 2. Make the state visible and pausable A menu bar item showing recording, with pause and resume one click away. Files: `MenuBar.swift` 3. Record pauses as data A gap with a reason is honest; an unexplained gap is a bug you will chase later. Files: `Store.swift` ### Done when - [ ] Samples are recorded while running - [ ] Pause stops sampling within one interval and records a row - [ ] The menu bar always shows whether recording is on - [ ] Quitting leaves the database consistent ### Watch out - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. ## Phase 2 · Idle time and window titles, carefully Time away is not counted, and titles do not become a log of everything you read. ### Steps 1. Detect idle and stop counting System idle time with a threshold you set. Verify against the clock rather than trusting the API. Files: `Idle.swift` 2. Hash titles by default Hashing gives you change detection without storing content. A plain-title allowlist is opt-in per application. Files: `Titles.swift` 3. Degrade without Accessibility Application names still work. Say what titles would have added rather than disabling everything. Files: `Permissions.swift` ### Done when - [ ] Idle time beyond the threshold is not counted - [ ] Titles are hashed by default - [ ] Only allowlisted applications store readable titles - [ ] Refusing Accessibility keeps application tracking working ### Watch out - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. ## Phase 3 · Rules you wrote Time is grouped into categories you defined, and you can see why each interval landed where it did. ### Steps 1. Evaluate rules in priority order First match wins, with the matching rule recorded on the interval. Files: `Rules.swift` 2. Make uncategorised visible Its own category. Hiding it makes the totals look complete when they are not. Files: `Categories.swift` 3. Make reclassification explicit With a count of affected intervals shown before it runs. Files: `Reclassify.swift` ### Done when - [ ] Each interval shows the rule that classified it - [ ] Uncategorised time is visible as a category - [ ] Changing a rule leaves history alone until reclassification runs - [ ] Reclassification reports the affected count first ### Watch out - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. ## Phase 4 · Reports without a verdict You can see where the time went, and the application does not tell you how to feel about it. ### Steps 1. Build daily and weekly views By category and by application, with gaps and pauses visible. Files: `Reports.swift` 2. Write the copy lint Banned words including productive, unproductive, wasted, distracted, score, grade and streak. Fails the build. Files: `scripts/copy-lint.swift`, `scripts/banned-words.json` 3. Include uncategorised everywhere In every total. Excluding it flatters the numbers and hides the rules you have not written. Files: `Reports.swift` ### Done when - [ ] Reports render with gaps preserved - [ ] The copy lint fails on a planted word and passes on shipped strings - [ ] No score, grade, streak or comparison appears - [ ] Uncategorised time is included in totals ### Watch out - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. ## Phase 5 · Focus sessions You can start a session with an intent and see afterwards what actually happened. ### Steps 1. Start a session with an intent Typed, in the user's words. It is what makes the summary meaningful afterwards. Files: `Sessions.swift` 2. Remind, never block Dismissible, and never repeated more than a configured rate. Blocking is worked around and loses the data. Files: `Sessions.swift` 3. Summarise without judging Where the time went against the intent. The user draws the conclusion. Files: `SessionSummary.swift` ### Done when - [ ] A session records intent and duration - [ ] Leaving the session's categories reminds rather than blocks - [ ] The summary shows time without a verdict - [ ] An abandoned session is recorded as incomplete ### Watch out - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ## Not in this build - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. ## After v1, if you want it - A weekly review that asks what you want to change rather than telling you - Per-project categories driven by the folder a project lives in - An annotation for a block of time, so a meeting is labelled rather than guessed at ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The local database. This is a detailed record of your computer use, so treat the file accordingly. DB_PATH=~/Library/Application Support/Time/time.db # Required. How often focus is sampled. Shorter is more accurate and produces more rows. SAMPLE_INTERVAL_SECONDS=15 # Required. How long without input before time stops being attributed. IDLE_THRESHOLD_SECONDS=180 # Required. Whether window titles are stored readable. False by default; the allowlist is the exception. STORE_PLAIN_TITLES=false # Optional. Applications whose titles may be stored readable, chosen deliberately. PLAIN_TITLE_ALLOWLIST=com.apple.dt.Xcode # Required. How long raw samples are kept. Aggregated intervals survive beyond this. SAMPLE_RETENTION_DAYS=90
You are building a production product version of RescueTime. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # RescueTime · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For RescueTime, measure opt-in application time, classify it, and run focused sessions locally. The hard boundary is cross-device history, website blocking, goals, reports, and long-term behavioral dataset, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A time record that stays on your machine, never counts time you were away, classifies by rules you wrote and can inspect, hashes what it does not need to read, offers no verdict, and can be deleted in whole or in part. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. - cross-device history, website blocking, goals, reports, and long-term behavioral dataset - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - The copy lint passes and is proved to fail on a planted word - Idle detection is verified against the wall clock - Titles are confirmed hashed for non-allowlisted applications - Deleting an application leaves nothing findable in any table - Idle processor use is profiled and acceptable ===== BRIEF.md ===== # Build brief · RescueTime The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local time tracker that records which applications you used, classified by rules you wrote to replace RescueTime. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This measures your own time on your own machine, and nothing about it should be designed to be shown to anyone else. It stays local, it is pausable at any moment, and it classifies according to rules you wrote rather than a score somebody else defined. It also must not moralise. Recording that four hours went to a browser is a fact; calling it unproductive is a judgement, and one that is wrong often enough to be actively unhelpful. Phase 4 makes the neutrality a test rather than an intention. ### Stack (fixed, do not substitute) - Language: Swift for macOS, or an equivalent native agent per platform · reading the frontmost application is a platform API and there is no cross-platform shortcut - Sampling: A timer reading the frontmost application and window title · sampling is far simpler than event hooks and is accurate enough at this granularity - Storage: SQLite locally, never a server · this is a record of everything you did on your computer and it should not leave it - Classification: Rules you write, evaluated in order, with a visible fallback · someone else's productivity taxonomy is wrong about your work in ways you cannot correct - Idle detection: System idle time, so away time is not counted · an hour at lunch recorded as an hour of work makes every number meaningless ### Data model (create this before Phase 1) - samples(at, bundle_id, app_name, window_title_hash, idle_seconds) · the raw record, sampled on an interval - intervals(id, bundle_id, started_at, ended_at, category_id, title_sample) · samples collapsed into runs, which is what you actually look at - rules(id, priority, match_kind, pattern, category_id, enabled) · match on bundle identifier or title pattern - categories(id, name, color) · named by you, with no built-in productive or unproductive distinction - sessions(id, started_at, ended_at, intent, completed) · focus sessions, with the intent you typed - pauses(started_at, ended_at, reason) · recorded, because a tracker that hides its gaps is lying by omission - Window titles are hashed by default rather than stored, because a title often contains a document name, a customer name or a search query. ### Phase 1 · Sampling, with a pause you can always reach Build: A background agent sampling the frontmost application on an interval. A menu bar control showing that recording is on, with pause and resume. Record pauses as rows so gaps in the data are explained rather than mysterious. Trap: A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. Done when: The agent records samples while running and the menu bar shows recording is on. Pausing stops sampling within one interval and records a pause row. Resuming continues. Quitting the agent leaves the database consistent rather than mid-write. Do not build yet: No window titles, no classification, no reports. ### Phase 2 · Idle time and window titles, carefully Build: Read system idle time and stop attributing time after a threshold. Optionally capture window titles behind the Accessibility permission, hashing them by default and storing plain text only for applications you explicitly allow. Trap: Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. Done when: Leaving the machine for longer than the idle threshold stops accumulating time, verified against the wall clock. Window titles are stored hashed by default. An application added to the plain-title allowlist stores readable titles and others do not. Refusing Accessibility leaves application tracking working and says what is lost. Do not build yet: No classification or reports yet. ### Phase 3 · Rules you wrote Build: Rules matching on bundle identifier or title pattern, evaluated in priority order, with an explicit uncategorised fallback. Show which rule classified each interval. Reclassifying historical data when rules change must be a deliberate action. Trap: Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. Done when: An interval shows which rule classified it. Uncategorised time is visible as its own category rather than hidden. Changing a rule does not alter historical classification until reclassification is explicitly run. Reclassifying reports how many intervals changed before applying. Do not build yet: No reports or focus sessions yet. ### Phase 4 · Reports without a verdict Build: Daily and weekly views by category and application, with gaps shown as gaps. No productivity score, no grade, no streak, no comparison against other users. Add a copy lint with a banned word list that fails the build if judgement language appears. Trap: A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. Done when: Daily and weekly reports render from real data with gaps preserved rather than filled. The copy lint fails when a banned word is deliberately added and passes on shipped strings. No screen shows a score, grade, streak or comparison to anyone else. Uncategorised time appears in every total rather than being excluded. Do not build yet: No focus sessions or export yet. ### Phase 5 · Focus sessions Build: A session with a duration and a typed intent. During it, optionally show a gentle reminder when focus moves to an application outside the session's categories. Afterwards, show what the time actually contained without judging it. Trap: Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. Done when: A session records its intent and duration. Switching to an application outside the session's categories produces a dismissible reminder rather than a block. The session summary shows where the time went with no verdict. An abandoned session is recorded as incomplete rather than deleted. Do not build yet: Export and retention come last. ### Phase 6 · Export, retention and deletion Build: Export as JSON and CSV. Retention that removes raw samples while keeping aggregated intervals. Deletion of a time range or an application entirely, covering samples, intervals and sessions together. Trap: A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. Done when: Export produces JSON and CSV covering samples, intervals, categories and sessions. Retention removes raw samples past the window and keeps aggregates, verified by row counts. Deleting an application removes it from samples, intervals and sessions, verified by querying afterwards. Deleting a date range does the same. Do not build yet: Nothing after this. ### Out of scope (and why) - Cross-device history. Time on your phone and on a second computer are not here, which limits what the numbers mean and is worth stating. - Website blocking and enforcement, deliberately, because enforcement is worked around and it corrupts the measurement. - The long-term behavioural dataset and the comparisons that come from it, which need many users' data. - Team timesheets and anything that reports your time to someone else. This is a tool for you. ### README must contain - That everything stays on the machine and nothing is uploaded - That window titles are hashed by default and plain titles are opt-in per application - That classification uses rules you wrote, and that changing one does not rewrite history - That there is no productivity score, and that the copy lint enforces the neutral language - The retention window, and how to delete an application or a date range entirely ===== ARCHITECTURE.md ===== # Architecture · RescueTime ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift for macOS, or an equivalent native agent per platform | reading the frontmost application is a platform API and there is no cross-platform shortcut | | Sampling | A timer reading the frontmost application and window title | sampling is far simpler than event hooks and is accurate enough at this granularity | | Storage | SQLite locally, never a server | this is a record of everything you did on your computer and it should not leave it | | Classification | Rules you write, evaluated in order, with a visible fallback | someone else's productivity taxonomy is wrong about your work in ways you cannot correct | | Idle detection | System idle time, so away time is not counted | an hour at lunch recorded as an hour of work makes every number meaningless | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sampler | Reading focus and detecting idle. | Platform-specific. Idle handling must survive any change, since it is what makes every number meaningful. | | Titles | Hashing and the plain-title allowlist. | The hashing default is a privacy property rather than an implementation choice. | | Rules | Classification and its explanation. | Pure evaluation over intervals, testable with no sampling at all. Showing which rule matched is what makes it correctable. | | Reports | Presenting time without interpreting it. | Any renderer, provided gaps stay gaps and no score appears. | | Retention and deletion | Forgetting. | Any implementation, provided deletion covers every table. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · The local database. This is a detailed record of your computer use, so treat the file accordingly. - `SAMPLE_INTERVAL_SECONDS` · required · How often focus is sampled. Shorter is more accurate and produces more rows. - `IDLE_THRESHOLD_SECONDS` · required · How long without input before time stops being attributed. - `STORE_PLAIN_TITLES` · required · Whether window titles are stored readable. False by default; the allowlist is the exception. - `PLAIN_TITLE_ALLOWLIST` · optional · Applications whose titles may be stored readable, chosen deliberately. - `SAMPLE_RETENTION_DAYS` · required · How long raw samples are kept. Aggregated intervals survive beyond this. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · RescueTime product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift for macOS, or an equivalent native agent per platform, A timer reading the frontmost application and window title, SQLite locally, never a server, Rules you write, evaluated in order, with a visible fallback, System idle time, so away time is not counted. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. - A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. ===== MILESTONES.md ===== # Delivery milestones · RescueTime Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sampling, with a pause you can always reach The agent records which application has focus, and stopping it takes one action. ### Steps 1. Sample the frontmost application On an interval through the workspace API. No permission is needed for the application name alone. Files: `Sampler.swift` 2. Make the state visible and pausable A menu bar item showing recording, with pause and resume one click away. Files: `MenuBar.swift` 3. Record pauses as data A gap with a reason is honest; an unexplained gap is a bug you will chase later. Files: `Store.swift` ### Done when - [ ] Samples are recorded while running - [ ] Pause stops sampling within one interval and records a row - [ ] The menu bar always shows whether recording is on - [ ] Quitting leaves the database consistent ### Watch out - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. ## M2 · Idle time and window titles, carefully Time away is not counted, and titles do not become a log of everything you read. ### Steps 1. Detect idle and stop counting System idle time with a threshold you set. Verify against the clock rather than trusting the API. Files: `Idle.swift` 2. Hash titles by default Hashing gives you change detection without storing content. A plain-title allowlist is opt-in per application. Files: `Titles.swift` 3. Degrade without Accessibility Application names still work. Say what titles would have added rather than disabling everything. Files: `Permissions.swift` ### Done when - [ ] Idle time beyond the threshold is not counted - [ ] Titles are hashed by default - [ ] Only allowlisted applications store readable titles - [ ] Refusing Accessibility keeps application tracking working ### Watch out - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. ## M3 · Rules you wrote Time is grouped into categories you defined, and you can see why each interval landed where it did. ### Steps 1. Evaluate rules in priority order First match wins, with the matching rule recorded on the interval. Files: `Rules.swift` 2. Make uncategorised visible Its own category. Hiding it makes the totals look complete when they are not. Files: `Categories.swift` 3. Make reclassification explicit With a count of affected intervals shown before it runs. Files: `Reclassify.swift` ### Done when - [ ] Each interval shows the rule that classified it - [ ] Uncategorised time is visible as a category - [ ] Changing a rule leaves history alone until reclassification runs - [ ] Reclassification reports the affected count first ### Watch out - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. ## M4 · Reports without a verdict You can see where the time went, and the application does not tell you how to feel about it. ### Steps 1. Build daily and weekly views By category and by application, with gaps and pauses visible. Files: `Reports.swift` 2. Write the copy lint Banned words including productive, unproductive, wasted, distracted, score, grade and streak. Fails the build. Files: `scripts/copy-lint.swift`, `scripts/banned-words.json` 3. Include uncategorised everywhere In every total. Excluding it flatters the numbers and hides the rules you have not written. Files: `Reports.swift` ### Done when - [ ] Reports render with gaps preserved - [ ] The copy lint fails on a planted word and passes on shipped strings - [ ] No score, grade, streak or comparison appears - [ ] Uncategorised time is included in totals ### Watch out - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. ## M5 · Focus sessions You can start a session with an intent and see afterwards what actually happened. ### Steps 1. Start a session with an intent Typed, in the user's words. It is what makes the summary meaningful afterwards. Files: `Sessions.swift` 2. Remind, never block Dismissible, and never repeated more than a configured rate. Blocking is worked around and loses the data. Files: `Sessions.swift` 3. Summarise without judging Where the time went against the intent. The user draws the conclusion. Files: `SessionSummary.swift` ### Done when - [ ] A session records intent and duration - [ ] Leaving the session's categories reminds rather than blocks - [ ] The summary shows time without a verdict - [ ] An abandoned session is recorded as incomplete ### Watch out - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ## M6 · Export, retention and deletion (production only) The record is yours, it does not accumulate forever, and you can delete any part of it. ### Steps 1. Export completely JSON for everything, CSV for spreadsheets. The record is yours and should be portable. Files: `Export.swift` 2. Retain aggregates, drop raw Samples are large and detailed; intervals are what you look at. Drop the first, keep the second. Files: `Retention.swift` 3. Make deletion complete One function covering every table. Verify by querying for the deleted application after. Files: `Delete.swift` ### Done when - [ ] Export covers samples, intervals, categories and sessions - [ ] Retention drops raw samples and keeps aggregates - [ ] Deleting an application leaves nothing findable in any table - [ ] Deleting a date range removes it completely ### Watch out - A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. ===== OPERATIONS.md ===== # Operations · RescueTime ## Backup The database is a detailed record of your computer use. Back it up if you want the history, and understand what the backup contains. Many people will reasonably choose not to. ## Restore Replace the database and relaunch. Rules and categories restore with it, which is most of the configuration value. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Idle processor use from the sampling loop and database growth. Both are small, and both are worth profiling before release because a background agent that costs battery gets uninstalled. ## Incident checklist If something sensitive was captured in a title, deletion must cover samples as well as intervals, which is why the deletion function is one operation across every table and is verified by querying afterwards. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The copy lint passes and is proved to fail on a planted word - [ ] Idle detection is verified against the wall clock - [ ] Titles are confirmed hashed for non-allowlisted applications - [ ] Deleting an application leaves nothing findable in any table - [ ] Idle processor use is profiled and acceptable ## Launch constraint Do not market omitted RescueTime capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The local database. This is a detailed record of your computer use, so treat the file accordingly. DB_PATH=~/Library/Application Support/Time/time.db # Required. How often focus is sampled. Shorter is more accurate and produces more rows. SAMPLE_INTERVAL_SECONDS=15 # Required. How long without input before time stops being attributed. IDLE_THRESHOLD_SECONDS=180 # Required. Whether window titles are stored readable. False by default; the allowlist is the exception. STORE_PLAIN_TITLES=false # Optional. Applications whose titles may be stored readable, chosen deliberately. PLAIN_TITLE_ALLOWLIST=com.apple.dt.Xcode # Required. How long raw samples are kept. Aggregated intervals survive beyond this. SAMPLE_RETENTION_DAYS=90
# RescueTime · indie build A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift for macOS, or an equivalent native agent per platform | reading the frontmost application is a platform API and there is no cross-platform shortcut | | Sampling | A timer reading the frontmost application and window title | sampling is far simpler than event hooks and is accurate enough at this granularity | | Storage | SQLite locally, never a server | this is a record of everything you did on your computer and it should not leave it | | Classification | Rules you write, evaluated in order, with a visible fallback | someone else's productivity taxonomy is wrong about your work in ways you cannot correct | | Idle detection | System idle time, so away time is not counted | an hour at lunch recorded as an hour of work makes every number meaningless | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer, or an equivalent platform target** · free if you own one - Why: Reading the frontmost application is a platform API. The design carries across platforms; the implementation does not. - Get it: Apple menu, then About This Mac, if you are targeting macOS. - Verify: About This Mac reports macOS 14 or higher - [ ] **Xcode 16 or newer** · free - Why: Builds the agent and provides the profiler for the sampling loop. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **A decision about window titles** · free - Why: Application names need no permission. Titles need Accessibility and are considerably more revealing, so this is a deliberate choice rather than a default. - Get it: Decide before Phase 2 whether you want titles at all. If yes, the hashing default and the per-application allowlist are what make it defensible. - Verify: The decision is recorded in the README - [ ] **A week of your own real usage** · free, but it takes a week - Why: Rules cannot be written in the abstract. You need to see what applications actually appear before you can categorise them usefully. - Get it: Run Phases 1 and 2 for a week before writing rules in Phase 3. The uncategorised list is your rule backlog. - Verify: A week of samples exists across normal working days - [ ] **Apple Developer Program membership** (optional) · 99 USD per year - Why: Product phase only, and only if you distribute it. - Get it: Enrol at developer.apple.com/programs. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. - cross-device history, website blocking, goals, reports, and long-term behavioral dataset - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for RescueTime, and the README should say so rather than pretend.
# Build brief · RescueTime The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local time tracker that records which applications you used, classified by rules you wrote to replace RescueTime. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This measures your own time on your own machine, and nothing about it should be designed to be shown to anyone else. It stays local, it is pausable at any moment, and it classifies according to rules you wrote rather than a score somebody else defined. It also must not moralise. Recording that four hours went to a browser is a fact; calling it unproductive is a judgement, and one that is wrong often enough to be actively unhelpful. Phase 4 makes the neutrality a test rather than an intention. ### Stack (fixed, do not substitute) - Language: Swift for macOS, or an equivalent native agent per platform · reading the frontmost application is a platform API and there is no cross-platform shortcut - Sampling: A timer reading the frontmost application and window title · sampling is far simpler than event hooks and is accurate enough at this granularity - Storage: SQLite locally, never a server · this is a record of everything you did on your computer and it should not leave it - Classification: Rules you write, evaluated in order, with a visible fallback · someone else's productivity taxonomy is wrong about your work in ways you cannot correct - Idle detection: System idle time, so away time is not counted · an hour at lunch recorded as an hour of work makes every number meaningless ### Data model (create this before Phase 1) - samples(at, bundle_id, app_name, window_title_hash, idle_seconds) · the raw record, sampled on an interval - intervals(id, bundle_id, started_at, ended_at, category_id, title_sample) · samples collapsed into runs, which is what you actually look at - rules(id, priority, match_kind, pattern, category_id, enabled) · match on bundle identifier or title pattern - categories(id, name, color) · named by you, with no built-in productive or unproductive distinction - sessions(id, started_at, ended_at, intent, completed) · focus sessions, with the intent you typed - pauses(started_at, ended_at, reason) · recorded, because a tracker that hides its gaps is lying by omission - Window titles are hashed by default rather than stored, because a title often contains a document name, a customer name or a search query. ### Phase 1 · Sampling, with a pause you can always reach Build: A background agent sampling the frontmost application on an interval. A menu bar control showing that recording is on, with pause and resume. Record pauses as rows so gaps in the data are explained rather than mysterious. Trap: A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. Done when: The agent records samples while running and the menu bar shows recording is on. Pausing stops sampling within one interval and records a pause row. Resuming continues. Quitting the agent leaves the database consistent rather than mid-write. Do not build yet: No window titles, no classification, no reports. ### Phase 2 · Idle time and window titles, carefully Build: Read system idle time and stop attributing time after a threshold. Optionally capture window titles behind the Accessibility permission, hashing them by default and storing plain text only for applications you explicitly allow. Trap: Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. Done when: Leaving the machine for longer than the idle threshold stops accumulating time, verified against the wall clock. Window titles are stored hashed by default. An application added to the plain-title allowlist stores readable titles and others do not. Refusing Accessibility leaves application tracking working and says what is lost. Do not build yet: No classification or reports yet. ### Phase 3 · Rules you wrote Build: Rules matching on bundle identifier or title pattern, evaluated in priority order, with an explicit uncategorised fallback. Show which rule classified each interval. Reclassifying historical data when rules change must be a deliberate action. Trap: Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. Done when: An interval shows which rule classified it. Uncategorised time is visible as its own category rather than hidden. Changing a rule does not alter historical classification until reclassification is explicitly run. Reclassifying reports how many intervals changed before applying. Do not build yet: No reports or focus sessions yet. ### Phase 4 · Reports without a verdict Build: Daily and weekly views by category and application, with gaps shown as gaps. No productivity score, no grade, no streak, no comparison against other users. Add a copy lint with a banned word list that fails the build if judgement language appears. Trap: A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. Done when: Daily and weekly reports render from real data with gaps preserved rather than filled. The copy lint fails when a banned word is deliberately added and passes on shipped strings. No screen shows a score, grade, streak or comparison to anyone else. Uncategorised time appears in every total rather than being excluded. Do not build yet: No focus sessions or export yet. ### Phase 5 · Focus sessions Build: A session with a duration and a typed intent. During it, optionally show a gentle reminder when focus moves to an application outside the session's categories. Afterwards, show what the time actually contained without judging it. Trap: Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. Done when: A session records its intent and duration. Switching to an application outside the session's categories produces a dismissible reminder rather than a block. The session summary shows where the time went with no verdict. An abandoned session is recorded as incomplete rather than deleted. Do not build yet: Export and retention come last. ### Phase 6 · Export, retention and deletion Build: Export as JSON and CSV. Retention that removes raw samples while keeping aggregated intervals. Deletion of a time range or an application entirely, covering samples, intervals and sessions together. Trap: A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. Done when: Export produces JSON and CSV covering samples, intervals, categories and sessions. Retention removes raw samples past the window and keeps aggregates, verified by row counts. Deleting an application removes it from samples, intervals and sessions, verified by querying afterwards. Deleting a date range does the same. Do not build yet: Nothing after this. ### Out of scope (and why) - Cross-device history. Time on your phone and on a second computer are not here, which limits what the numbers mean and is worth stating. - Website blocking and enforcement, deliberately, because enforcement is worked around and it corrupts the measurement. - The long-term behavioural dataset and the comparisons that come from it, which need many users' data. - Team timesheets and anything that reports your time to someone else. This is a tool for you. ### README must contain - That everything stays on the machine and nothing is uploaded - That window titles are hashed by default and plain titles are opt-in per application - That classification uses rules you wrote, and that changing one does not rewrite history - That there is no productivity score, and that the copy lint enforces the neutral language - The retention window, and how to delete an application or a date range entirely
# Agent instructions · RescueTime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift for macOS, or an equivalent native agent per platform, A timer reading the frontmost application and window title, SQLite locally, never a server, Rules you write, evaluated in order, with a visible fallback, System idle time, so away time is not counted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest.
# Build plan · RescueTime A background agent sampling which application and window has focus, storing that locally, classifying it with rules you write and can edit, showing where the time went without scoring it, supporting focus sessions with a pause you can always reach, and exporting everything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sampling, with a pause you can always reach The agent records which application has focus, and stopping it takes one action. ### Steps 1. Sample the frontmost application On an interval through the workspace API. No permission is needed for the application name alone. Files: `Sampler.swift` 2. Make the state visible and pausable A menu bar item showing recording, with pause and resume one click away. Files: `MenuBar.swift` 3. Record pauses as data A gap with a reason is honest; an unexplained gap is a bug you will chase later. Files: `Store.swift` ### Done when - [ ] Samples are recorded while running - [ ] Pause stops sampling within one interval and records a row - [ ] The menu bar always shows whether recording is on - [ ] Quitting leaves the database consistent ### Watch out - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. ## Phase 2 · Idle time and window titles, carefully Time away is not counted, and titles do not become a log of everything you read. ### Steps 1. Detect idle and stop counting System idle time with a threshold you set. Verify against the clock rather than trusting the API. Files: `Idle.swift` 2. Hash titles by default Hashing gives you change detection without storing content. A plain-title allowlist is opt-in per application. Files: `Titles.swift` 3. Degrade without Accessibility Application names still work. Say what titles would have added rather than disabling everything. Files: `Permissions.swift` ### Done when - [ ] Idle time beyond the threshold is not counted - [ ] Titles are hashed by default - [ ] Only allowlisted applications store readable titles - [ ] Refusing Accessibility keeps application tracking working ### Watch out - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. ## Phase 3 · Rules you wrote Time is grouped into categories you defined, and you can see why each interval landed where it did. ### Steps 1. Evaluate rules in priority order First match wins, with the matching rule recorded on the interval. Files: `Rules.swift` 2. Make uncategorised visible Its own category. Hiding it makes the totals look complete when they are not. Files: `Categories.swift` 3. Make reclassification explicit With a count of affected intervals shown before it runs. Files: `Reclassify.swift` ### Done when - [ ] Each interval shows the rule that classified it - [ ] Uncategorised time is visible as a category - [ ] Changing a rule leaves history alone until reclassification runs - [ ] Reclassification reports the affected count first ### Watch out - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. ## Phase 4 · Reports without a verdict You can see where the time went, and the application does not tell you how to feel about it. ### Steps 1. Build daily and weekly views By category and by application, with gaps and pauses visible. Files: `Reports.swift` 2. Write the copy lint Banned words including productive, unproductive, wasted, distracted, score, grade and streak. Fails the build. Files: `scripts/copy-lint.swift`, `scripts/banned-words.json` 3. Include uncategorised everywhere In every total. Excluding it flatters the numbers and hides the rules you have not written. Files: `Reports.swift` ### Done when - [ ] Reports render with gaps preserved - [ ] The copy lint fails on a planted word and passes on shipped strings - [ ] No score, grade, streak or comparison appears - [ ] Uncategorised time is included in totals ### Watch out - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. ## Phase 5 · Focus sessions You can start a session with an intent and see afterwards what actually happened. ### Steps 1. Start a session with an intent Typed, in the user's words. It is what makes the summary meaningful afterwards. Files: `Sessions.swift` 2. Remind, never block Dismissible, and never repeated more than a configured rate. Blocking is worked around and loses the data. Files: `Sessions.swift` 3. Summarise without judging Where the time went against the intent. The user draws the conclusion. Files: `SessionSummary.swift` ### Done when - [ ] A session records intent and duration - [ ] Leaving the session's categories reminds rather than blocks - [ ] The summary shows time without a verdict - [ ] An abandoned session is recorded as incomplete ### Watch out - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ## Not in this build - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. ## After v1, if you want it - A weekly review that asks what you want to change rather than telling you - Per-project categories driven by the folder a project lives in - An annotation for a block of time, so a meeting is labelled rather than guessed at
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The local database. This is a detailed record of your computer use, so treat the file accordingly. DB_PATH=~/Library/Application Support/Time/time.db # Required. How often focus is sampled. Shorter is more accurate and produces more rows. SAMPLE_INTERVAL_SECONDS=15 # Required. How long without input before time stops being attributed. IDLE_THRESHOLD_SECONDS=180 # Required. Whether window titles are stored readable. False by default; the allowlist is the exception. STORE_PLAIN_TITLES=false # Optional. Applications whose titles may be stored readable, chosen deliberately. PLAIN_TITLE_ALLOWLIST=com.apple.dt.Xcode # Required. How long raw samples are kept. Aggregated intervals survive beyond this. SAMPLE_RETENTION_DAYS=90
# RescueTime · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For RescueTime, measure opt-in application time, classify it, and run focused sessions locally. The hard boundary is cross-device history, website blocking, goals, reports, and long-term behavioral dataset, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A time record that stays on your machine, never counts time you were away, classifies by rules you wrote and can inspect, hashes what it does not need to read, offers no verdict, and can be deleted in whole or in part. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Cross-device history covering phones and other computers. - Website blocking and enforcement, deliberately omitted. - The long-term behavioural dataset and cross-user comparisons. - Team timesheets and reporting to anyone else. - cross-device history, website blocking, goals, reports, and long-term behavioral dataset - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - The copy lint passes and is proved to fail on a planted word - Idle detection is verified against the wall clock - Titles are confirmed hashed for non-allowlisted applications - Deleting an application leaves nothing findable in any table - Idle processor use is profiled and acceptable
# Build brief · RescueTime The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local time tracker that records which applications you used, classified by rules you wrote to replace RescueTime. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This measures your own time on your own machine, and nothing about it should be designed to be shown to anyone else. It stays local, it is pausable at any moment, and it classifies according to rules you wrote rather than a score somebody else defined. It also must not moralise. Recording that four hours went to a browser is a fact; calling it unproductive is a judgement, and one that is wrong often enough to be actively unhelpful. Phase 4 makes the neutrality a test rather than an intention. ### Stack (fixed, do not substitute) - Language: Swift for macOS, or an equivalent native agent per platform · reading the frontmost application is a platform API and there is no cross-platform shortcut - Sampling: A timer reading the frontmost application and window title · sampling is far simpler than event hooks and is accurate enough at this granularity - Storage: SQLite locally, never a server · this is a record of everything you did on your computer and it should not leave it - Classification: Rules you write, evaluated in order, with a visible fallback · someone else's productivity taxonomy is wrong about your work in ways you cannot correct - Idle detection: System idle time, so away time is not counted · an hour at lunch recorded as an hour of work makes every number meaningless ### Data model (create this before Phase 1) - samples(at, bundle_id, app_name, window_title_hash, idle_seconds) · the raw record, sampled on an interval - intervals(id, bundle_id, started_at, ended_at, category_id, title_sample) · samples collapsed into runs, which is what you actually look at - rules(id, priority, match_kind, pattern, category_id, enabled) · match on bundle identifier or title pattern - categories(id, name, color) · named by you, with no built-in productive or unproductive distinction - sessions(id, started_at, ended_at, intent, completed) · focus sessions, with the intent you typed - pauses(started_at, ended_at, reason) · recorded, because a tracker that hides its gaps is lying by omission - Window titles are hashed by default rather than stored, because a title often contains a document name, a customer name or a search query. ### Phase 1 · Sampling, with a pause you can always reach Build: A background agent sampling the frontmost application on an interval. A menu bar control showing that recording is on, with pause and resume. Record pauses as rows so gaps in the data are explained rather than mysterious. Trap: A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. Done when: The agent records samples while running and the menu bar shows recording is on. Pausing stops sampling within one interval and records a pause row. Resuming continues. Quitting the agent leaves the database consistent rather than mid-write. Do not build yet: No window titles, no classification, no reports. ### Phase 2 · Idle time and window titles, carefully Build: Read system idle time and stop attributing time after a threshold. Optionally capture window titles behind the Accessibility permission, hashing them by default and storing plain text only for applications you explicitly allow. Trap: Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. Done when: Leaving the machine for longer than the idle threshold stops accumulating time, verified against the wall clock. Window titles are stored hashed by default. An application added to the plain-title allowlist stores readable titles and others do not. Refusing Accessibility leaves application tracking working and says what is lost. Do not build yet: No classification or reports yet. ### Phase 3 · Rules you wrote Build: Rules matching on bundle identifier or title pattern, evaluated in priority order, with an explicit uncategorised fallback. Show which rule classified each interval. Reclassifying historical data when rules change must be a deliberate action. Trap: Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. Done when: An interval shows which rule classified it. Uncategorised time is visible as its own category rather than hidden. Changing a rule does not alter historical classification until reclassification is explicitly run. Reclassifying reports how many intervals changed before applying. Do not build yet: No reports or focus sessions yet. ### Phase 4 · Reports without a verdict Build: Daily and weekly views by category and application, with gaps shown as gaps. No productivity score, no grade, no streak, no comparison against other users. Add a copy lint with a banned word list that fails the build if judgement language appears. Trap: A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. Done when: Daily and weekly reports render from real data with gaps preserved rather than filled. The copy lint fails when a banned word is deliberately added and passes on shipped strings. No screen shows a score, grade, streak or comparison to anyone else. Uncategorised time appears in every total rather than being excluded. Do not build yet: No focus sessions or export yet. ### Phase 5 · Focus sessions Build: A session with a duration and a typed intent. During it, optionally show a gentle reminder when focus moves to an application outside the session's categories. Afterwards, show what the time actually contained without judging it. Trap: Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. Done when: A session records its intent and duration. Switching to an application outside the session's categories produces a dismissible reminder rather than a block. The session summary shows where the time went with no verdict. An abandoned session is recorded as incomplete rather than deleted. Do not build yet: Export and retention come last. ### Phase 6 · Export, retention and deletion Build: Export as JSON and CSV. Retention that removes raw samples while keeping aggregated intervals. Deletion of a time range or an application entirely, covering samples, intervals and sessions together. Trap: A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards. Done when: Export produces JSON and CSV covering samples, intervals, categories and sessions. Retention removes raw samples past the window and keeps aggregates, verified by row counts. Deleting an application removes it from samples, intervals and sessions, verified by querying afterwards. Deleting a date range does the same. Do not build yet: Nothing after this. ### Out of scope (and why) - Cross-device history. Time on your phone and on a second computer are not here, which limits what the numbers mean and is worth stating. - Website blocking and enforcement, deliberately, because enforcement is worked around and it corrupts the measurement. - The long-term behavioural dataset and the comparisons that come from it, which need many users' data. - Team timesheets and anything that reports your time to someone else. This is a tool for you. ### README must contain - That everything stays on the machine and nothing is uploaded - That window titles are hashed by default and plain titles are opt-in per application - That classification uses rules you wrote, and that changing one does not rewrite history - That there is no productivity score, and that the copy lint enforces the neutral language - The retention window, and how to delete an application or a date range entirely
# Architecture · RescueTime ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift for macOS, or an equivalent native agent per platform | reading the frontmost application is a platform API and there is no cross-platform shortcut | | Sampling | A timer reading the frontmost application and window title | sampling is far simpler than event hooks and is accurate enough at this granularity | | Storage | SQLite locally, never a server | this is a record of everything you did on your computer and it should not leave it | | Classification | Rules you write, evaluated in order, with a visible fallback | someone else's productivity taxonomy is wrong about your work in ways you cannot correct | | Idle detection | System idle time, so away time is not counted | an hour at lunch recorded as an hour of work makes every number meaningless | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sampler | Reading focus and detecting idle. | Platform-specific. Idle handling must survive any change, since it is what makes every number meaningful. | | Titles | Hashing and the plain-title allowlist. | The hashing default is a privacy property rather than an implementation choice. | | Rules | Classification and its explanation. | Pure evaluation over intervals, testable with no sampling at all. Showing which rule matched is what makes it correctable. | | Reports | Presenting time without interpreting it. | Any renderer, provided gaps stay gaps and no score appears. | | Retention and deletion | Forgetting. | Any implementation, provided deletion covers every table. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · The local database. This is a detailed record of your computer use, so treat the file accordingly. - `SAMPLE_INTERVAL_SECONDS` · required · How often focus is sampled. Shorter is more accurate and produces more rows. - `IDLE_THRESHOLD_SECONDS` · required · How long without input before time stops being attributed. - `STORE_PLAIN_TITLES` · required · Whether window titles are stored readable. False by default; the allowlist is the exception. - `PLAIN_TITLE_ALLOWLIST` · optional · Applications whose titles may be stored readable, chosen deliberately. - `SAMPLE_RETENTION_DAYS` · required · How long raw samples are kept. Aggregated intervals survive beyond this. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · RescueTime product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift for macOS, or an equivalent native agent per platform, A timer reading the frontmost application and window title, SQLite locally, never a server, Rules you write, evaluated in order, with a visible fallback, System idle time, so away time is not counted. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. - A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards.
# Delivery milestones · RescueTime Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sampling, with a pause you can always reach The agent records which application has focus, and stopping it takes one action. ### Steps 1. Sample the frontmost application On an interval through the workspace API. No permission is needed for the application name alone. Files: `Sampler.swift` 2. Make the state visible and pausable A menu bar item showing recording, with pause and resume one click away. Files: `MenuBar.swift` 3. Record pauses as data A gap with a reason is honest; an unexplained gap is a bug you will chase later. Files: `Store.swift` ### Done when - [ ] Samples are recorded while running - [ ] Pause stops sampling within one interval and records a row - [ ] The menu bar always shows whether recording is on - [ ] Quitting leaves the database consistent ### Watch out - A tracker without an obvious, immediate pause becomes something people work around by quitting it, which loses the data anyway and teaches them not to trust it. Make pause one click from the menu bar and make the recording state always visible. ## M2 · Idle time and window titles, carefully Time away is not counted, and titles do not become a log of everything you read. ### Steps 1. Detect idle and stop counting System idle time with a threshold you set. Verify against the clock rather than trusting the API. Files: `Idle.swift` 2. Hash titles by default Hashing gives you change detection without storing content. A plain-title allowlist is opt-in per application. Files: `Titles.swift` 3. Degrade without Accessibility Application names still work. Say what titles would have added rather than disabling everything. Files: `Permissions.swift` ### Done when - [ ] Idle time beyond the threshold is not counted - [ ] Titles are hashed by default - [ ] Only allowlisted applications store readable titles - [ ] Refusing Accessibility keeps application tracking working ### Watch out - Window titles are far more sensitive than application names: a browser title is the page you are reading, a document title is the client you are working for, and an editor title is the file. Hash by default, store plain text only where the user opted in per application, and say so where the setting lives. ## M3 · Rules you wrote Time is grouped into categories you defined, and you can see why each interval landed where it did. ### Steps 1. Evaluate rules in priority order First match wins, with the matching rule recorded on the interval. Files: `Rules.swift` 2. Make uncategorised visible Its own category. Hiding it makes the totals look complete when they are not. Files: `Categories.swift` 3. Make reclassification explicit With a count of affected intervals shown before it runs. Files: `Reclassify.swift` ### Done when - [ ] Each interval shows the rule that classified it - [ ] Uncategorised time is visible as a category - [ ] Changing a rule leaves history alone until reclassification runs - [ ] Reclassification reports the affected count first ### Watch out - Silently reclassifying history whenever a rule changes rewrites last month's numbers and makes any comparison over time meaningless. Apply new rules going forward, and make retroactive reclassification an explicit action that says how many intervals it will change. ## M4 · Reports without a verdict You can see where the time went, and the application does not tell you how to feel about it. ### Steps 1. Build daily and weekly views By category and by application, with gaps and pauses visible. Files: `Reports.swift` 2. Write the copy lint Banned words including productive, unproductive, wasted, distracted, score, grade and streak. Fails the build. Files: `scripts/copy-lint.swift`, `scripts/banned-words.json` 3. Include uncategorised everywhere In every total. Excluding it flatters the numbers and hides the rules you have not written. Files: `Reports.swift` ### Done when - [ ] Reports render with gaps preserved - [ ] The copy lint fails on a planted word and passes on shipped strings - [ ] No score, grade, streak or comparison appears - [ ] Uncategorised time is included in totals ### Watch out - A single productivity score is the feature people remember and the one that is most often wrong, because reading documentation in a browser and reading social media in a browser are the same application. If a category feels wrong, the fix is a rule, not a score that overrides your own knowledge of your work. ## M5 · Focus sessions You can start a session with an intent and see afterwards what actually happened. ### Steps 1. Start a session with an intent Typed, in the user's words. It is what makes the summary meaningful afterwards. Files: `Sessions.swift` 2. Remind, never block Dismissible, and never repeated more than a configured rate. Blocking is worked around and loses the data. Files: `Sessions.swift` 3. Summarise without judging Where the time went against the intent. The user draws the conclusion. Files: `SessionSummary.swift` ### Done when - [ ] A session records intent and duration - [ ] Leaving the session's categories reminds rather than blocks - [ ] The summary shows time without a verdict - [ ] An abandoned session is recorded as incomplete ### Watch out - Blocking applications during a focus session turns a measurement tool into an enforcement tool, and enforcement is worked around within a week. A reminder that can be dismissed respects that you sometimes genuinely need the other application, and it keeps the data honest. ## M6 · Export, retention and deletion (production only) The record is yours, it does not accumulate forever, and you can delete any part of it. ### Steps 1. Export completely JSON for everything, CSV for spreadsheets. The record is yours and should be portable. Files: `Export.swift` 2. Retain aggregates, drop raw Samples are large and detailed; intervals are what you look at. Drop the first, keep the second. Files: `Retention.swift` 3. Make deletion complete One function covering every table. Verify by querying for the deleted application after. Files: `Delete.swift` ### Done when - [ ] Export covers samples, intervals, categories and sessions - [ ] Retention drops raw samples and keeps aggregates - [ ] Deleting an application leaves nothing findable in any table - [ ] Deleting a date range removes it completely ### Watch out - A tracker's database is a minute-by-minute record of everything you did on your computer, and deleting an interval without the underlying samples leaves the detail recoverable. Make deletion cover every table and verify by querying for the deleted application afterwards.
# Operations · RescueTime ## Backup The database is a detailed record of your computer use. Back it up if you want the history, and understand what the backup contains. Many people will reasonably choose not to. ## Restore Replace the database and relaunch. Rules and categories restore with it, which is most of the configuration value. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Idle processor use from the sampling loop and database growth. Both are small, and both are worth profiling before release because a background agent that costs battery gets uninstalled. ## Incident checklist If something sensitive was captured in a title, deletion must cover samples as well as intervals, which is why the deletion function is one operation across every table and is verified by querying afterwards. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The copy lint passes and is proved to fail on a planted word - [ ] Idle detection is verified against the wall clock - [ ] Titles are confirmed hashed for non-allowlisted applications - [ ] Deleting an application leaves nothing findable in any table - [ ] Idle processor use is profiled and acceptable ## Launch constraint Do not market omitted RescueTime capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The local database. This is a detailed record of your computer use, so treat the file accordingly. DB_PATH=~/Library/Application Support/Time/time.db # Required. How often focus is sampled. Shorter is more accurate and produces more rows. SAMPLE_INTERVAL_SECONDS=15 # Required. How long without input before time stops being attributed. IDLE_THRESHOLD_SECONDS=180 # Required. Whether window titles are stored readable. False by default; the allowlist is the exception. STORE_PLAIN_TITLES=false # Optional. Applications whose titles may be stored readable, chosen deliberately. PLAIN_TITLE_ALLOWLIST=com.apple.dt.Xcode # Required. How long raw samples are kept. Aggregated intervals survive beyond this. SAMPLE_RETENTION_DAYS=90
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for RescueTime because the timer is simple, but people pay for frictionless capture and a history they trust enough to invoice or change habits from. The recurring cost buys idle detection, permissions, app metadata, privacy, notifications, sync, reports, integrations, and backups, not just the visible interface.
xcross-device history, website blocking, goals, reports, and long-term behavioral dataset
xcross-device history
xteam timesheets and payroll
xbehavioral datasets
xblocking across every platform
RescueTime pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| solo focus | — | $7/user | 1 user; focus sessions and productivity reports; no numeric activity-history cap published. |
| solo+ | — | $12/user | 1 user; expanded coaching and productivity features; no numeric usage cap published. |
| team focus | — | $10/user | Team reporting and focus tools; no public numeric seat minimum. |
| team+ | — | $16/user | Expanded team analytics and coaching; no public numeric seat minimum. |
free tierno free tier
billingmonthly + annual; exact monthly dollar amounts were not exposed; 14-day trial
hidden costsInvoice billing is generally limited to teams of 100+; prorated refunds are generally not offered.
verified 2026-08-14 · source ↗
Vibecode RescueTime
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal RescueTime replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does RescueTime cost?
RescueTime costs about $12/month (Premium, checked 2026-07-31), which is $144 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing RescueTime?
Honestly: cross-device history, website blocking, goals, reports, and long-term behavioral dataset; cross-device history; team timesheets and payroll; behavioral datasets; blocking across every platform. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to RescueTime?
Yes: Kimai (Mature open-source time-tracking application with projects, reports, and exports.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.