Personal workflow software. Timebox is built for the way I do timeblocking, time tracking, billing follow-up, and weekly capacity planning as a freelancer. It codifies a subjective, personal process; it is not trying to be a generic productivity system.
Vibe coding project. This project is developed through iterative, AI-assisted coding: the human steers intent and product decisions, while AI agents write and revise much of the implementation. It works and has tests, but expect fast evolution and rough edges.
Timebox is a local-first desktop app for personal capacity planning. It is macOS-first in daily use, with packaging scripts and platform-aware local tooling for Windows and Linux where Electron supports the workflow. It combines a weekly timeblocking board, per-project time tracking, optional billable-hour review for client work, budget and capacity alerts, Todoist task sync, a local HTTP API, a standalone CLI, and an MCP server for coding agents.
Stack: Electron 31 · React 18 · Vite 5 · better-sqlite3 12
- Install and Run
- Core Concepts
- Main Screens
- Todoist Integration
- Local Data and Privacy
- Database Schema
- HTTP API
- CLI
- MCP Server
- Build and Packaging
- Development Notes
Requirements: Node.js 20+ and npm 10+.
# First install. Node 25 has no better-sqlite3 prebuilds.
npm install --ignore-scripts
npm run rebuild
# Development
npm start
# Production renderer build
npm run buildnpm run rebuild compiles better-sqlite3 against Electron 31 headers. Do not skip it after installing dependencies if you want the Electron app to open correctly.
The app database is created automatically in Electron's per-user app-data directory. Typical defaults are:
macOS: ~/Library/Application Support/Timebox/timebox.db
Windows: %APPDATA%\Timebox\timebox.db
Linux: ~/.config/Timebox/timebox.db
When the database is empty, Timebox seeds demo areas, projects, recurring blocks, entries, and cached Todoist rows so the interface can be explored immediately.
- Areas: the app's top-level capacity buckets. In the UI they behave like clients, but they can also represent retainers, internal work, personal maintenance, study, health, or any area that needs planned time.
- Projects: work streams inside an area. Projects can have a total budget, a weekly limit, a description, an order, and an archived state.
- Recurring blocks: the default weekly timeblocking template, defined Monday-Friday and split into AM (<13:00), PM (13:00–18:00) and Sera (from 18:00) slots.
- Week overrides: per-week replacements for recurring blocks. Editing a block in the weekly view changes that week only.
- Weekly area status: per-week markers for areas: active, minimal, or closed. They make the weekly planning decision visible without changing the recurring template.
- Entries: logged work, one project/date record managed by application logic, with slot, hours, optional billable-hours override, and billed status.
- Todoist cache: local task snapshots grouped by due date and used to overlay planned Todoist work onto Timebox blocks.
Some client work is sold as prepaid hour packages: the client commissions work for a fixed amount, which is converted into a fixed number of hours (for example a 60-hour package, later followed by a separate 40-hour package). The packages are not cumulative — they must not merge into a single 100-hour pool — and unused hours in a package are lost, not carried over. The recurring need is only to see, per package, how much has been consumed and whether it is over or under its quota.
Timebox intentionally does not implement a dedicated hour-package model (a project_budget_packages table, sequential active-package accounting, per-package variance views). The supported workflow is to model one project per package under the client's area:
- Area = client, project = package/order (for example
API Integration — Ord.1withbudgetHours = 60, thenAPI Integration — Ord.2withbudgetHours = 40). - Non-cumulative by construction: each project keeps its own budget and logged total; the area aggregates them when the cumulative view is wanted.
- Over/under is already visible per project via logged-vs-
budgetHoursand the budget alert banners. - Auditable per package: each project is one package, with its own entries and history.
- Manual package switch: start logging on the new project when the new order arrives; archive the finished project (archiving preserves historical entries), so unused hours are simply dropped with no carryover.
- Re-attaching a package is covered by the existing move-project-between-areas and merge-projects actions.
This keeps the app's existing Area → Project → budgetHours hierarchy as the package model and avoids building new schema, accounting logic, and a dedicated view for a personal tool. The decision can be revisited only if real usage shows measured friction, such as package fragmentation making a single logical work stream hard to follow, or a genuine need for a "packages of this project" audit view that the per-project view does not cover.
The Oggi screen surfaces daily operational diagnostics from the same logic used by the MCP tools: free capacity after tracked work and Todoist estimates, planned blocks still missing ready Todoist work, and sync mismatches.
The primary screen combines planning and tracking:
- A weekly grid with AM, PM, and Extra rows.
- Planned area blocks that fill as hours are logged.
- Extra blocks for work logged against areas not planned that day.
- Inline
hh:mmediting for project entries. - A green
€billed badge and hover toggle for billable entries. - Weekly navigation, current-day highlighting, and keyboard shortcuts.
- A per-area weekly status selector for active, minimal, or closed areas.
- Budget and capacity alert banners.
- Drag and drop for moving planned blocks between days and slots.
- A "reset to template" action that deletes the current week's overrides.
- Todoist overlays for today and future days only.
The dashboard summarizes weekly workload, billable value, area status, and project budget usage. It compares planned capacity against logged work and highlights overload or underload.
The billing screen is a supporting workflow for billable areas, not the center of the product. It reviews billable entries by month, quarter, or custom range, groups entries by area and project, distinguishes billed from unbilled work, supports single-row and bulk billed toggles, and respects the billableHours override when present.
The entries screen is a tabular log of tracked work. It supports date, area, and project filters; inline editing; billed-state changes; deletion; and live totals for the active filter.
The areas screen manages areas and their projects:
- Area name, color, billing type, hourly rate, and hour limits.
- Project name, description, total budget, weekly limit, ordering, and archive state.
- Drag-and-drop ordering for areas and projects.
- Moving projects between areas.
- Project archive/restore without deleting historical entries.
The recurring screen edits the weekly template. Changes automatically freeze previous weeks that do not yet have overrides, preserving historical planning state.
The Todoist log shows cached Todoist tasks grouped by date, including matched Timebox project, slot, estimated hours, completion state, and the latest sync timestamp.
Settings cover:
- Appearance: light, dark, or system theme.
- Todoist: API token storage, debug logging, and project import.
- CLI and MCP installation for local tools and agents.
- Database path, selecting an existing
.db, creating a new database, and saving a database copy. - Data reset and demo-data seeding.
- Update status and update installation in packaged builds.
Timebox reads open Todoist tasks through the Todoist REST API v1.
- Save a Todoist API token in Settings.
- From the weekly view, run the Todoist sync action.
- Timebox fetches open tasks with due dates matching today or future days in the visible week.
- Todoist projects are matched to Timebox projects by name.
- Task duration is converted into Timebox hours.
- Results are cached in
todoist_cache, one row per date. - Matched tasks appear as blue overlays on planned blocks; leftover task time appears in Extra.
Todoist tasks are shown only for today and future days in the weekly view. Past cached data remains available in the Todoist log.
Timebox intentionally does not implement a separate manual mapping table between Todoist projects and Timebox projects.
The idea was to add an explicit mapping layer so a Todoist project could be linked to a specific Timebox project without relying on equal names. A possible design would have introduced a table such as todoist_project_mappings, a settings UI to connect Todoist project IDs to Timebox project IDs, and sync logic that preferred the explicit mapping over the current name-based match.
That design is not implemented for now. The current name-based matching is deliberately simpler and fits the app's personal workflow better:
- It keeps project setup lightweight. Creating a Timebox project with the same name as the Todoist project is enough.
- It avoids adding another maintenance screen for a problem that is not causing regular friction.
- It preserves the current local-first sync model without storing additional Todoist relationship metadata.
- It keeps planning visible in the weekly board instead of turning Todoist sync into a separate configuration system.
The decision can be revisited only if real usage shows repeated sync friction, such as frequent ambiguous project names, renamed Todoist projects breaking planning, or multiple Todoist projects needing to feed one Timebox project. Until then, the supported workflow is: keep Todoist and Timebox project names aligned when automatic matching is desired, and inspect mismatches with the Oggi diagnostics or MCP daily tools.
Timebox is local-first:
- The SQLite database stays on the user's machine unless the user copies it elsewhere.
- Todoist tokens are encrypted with Electron
safeStorage. - The HTTP bridge binds to
127.0.0.1:37373and exists only while the app is open. - The CLI and MCP server talk only to that local HTTP bridge.
- Todoist sync stores only the task data needed by Timebox in the local cache.
Do not publish personal databases, exports, screenshots, Todoist tokens, or real client data in issues, releases, or support requests.
clients (id, name, color, billable, billing, rate, limitType, limitHours, position)
projects (id, clientId, name, description, budgetHours, weeklyHours, position, archived)
recurring (id, clientId, slot, day, hours, position)
entries (id, projectId, date, hours, billableHours, slot, billed)
week_overrides (id, weekKey, dayIndex, slot, blocksJson)
week_area_status (id, weekKey, areaId, status)
settings (key, value)
todoist_cache (dateStr, tasksJson, syncedAt)The database uses WAL mode and an exclusive lock to reduce conflicts with iCloud Drive sync. Migrations run at startup from db/schema.js.
While the app is open, a local API is available at http://127.0.0.1:37373.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ping |
Health check. |
GET |
/today?date=YYYY-MM-DD |
Logged hours for one day. |
GET |
/day/insights?date=YYYY-MM-DD |
Aggregated daily diagnostics for the Oggi screen. |
GET |
/week?offset=N |
Weekly summary; 0 is current week, -1 is last week. |
GET |
/area-statuses?week=YYYY-MM-DD |
Weekly area statuses for a Monday week key. |
POST |
/area-statuses |
Save an area status: { weekKey, areaId, status }; active is implicit. |
GET |
/projects?area=&client=&search=&all=1 |
Project list with budgets and logged totals. |
GET |
/clients?search= |
Area/client list. |
GET |
/areas?search= |
Alias for /clients. |
GET |
/status |
Today, week, and budget-alert overview. |
POST |
/log |
Log hours: { project, hours, slot, date, add, billableHours }. |
POST |
/projects |
Create a project in an area. |
PATCH |
/projects/:id |
Update project name, area, description, budget, or weekly limit. |
DELETE |
/projects/:id |
Delete a project with no entries. |
POST |
/projects/merge |
Merge entries from one project into another, then delete the source project. |
PATCH |
/areas/:id |
Rename an area. |
PATCH |
/clients/:id |
Alias for renaming an area. |
curl http://127.0.0.1:37373/ping
curl http://127.0.0.1:37373/week?offset=-1
curl -X POST http://127.0.0.1:37373/log \
-H 'Content-Type: application/json' \
-d '{"project":"website","hours":"2:30","slot":"pm","billableHours":"2"}'The installable CLI is cli/standalone.js. It has no npm runtime dependencies and requires the app to be open.
Install it from Settings -> CLI and MCP -> Install CLI. In development the generated command points to the repository file; in packaged builds it points to the bundled timebox resource.
The app installs commands in a user-writable directory:
macOS/Linux: ~/.local/bin
Windows: %APPDATA%\Timebox\bin
Add that directory to PATH if your shell cannot find timebox after installation. Existing macOS installs in /usr/local/bin are still detected, but new installs use the per-user directory.
| Command | Description |
|---|---|
timebox today [--date YYYY-MM-DD] |
Logged hours for a day. |
timebox week [--offset N] |
Weekly summary. |
timebox projects [--area <name>] [--client <name>] [--all] |
Project list. |
timebox areas |
Area list. |
timebox clients |
Alias for areas in older workflows. |
timebox status |
Today, week, and alerts. |
timebox log <project> <hours> |
Log or replace hours. |
All commands support --json. timebox log also supports --slot, --date, --add, and --billable.
timebox today
timebox week --offset -1
timebox log "website" 2:30 --slot pm
timebox log "website" 1 --add
timebox projects --jsonUse TIMEBOX_PORT=37373 to target a non-default local port.
The MCP server is cli/mcp-server.js. It speaks JSON-RPC over stdio, requires the Timebox app to be open, and communicates only with the local HTTP API.
Install it from Settings -> CLI and MCP. The app can install:
- the
timebox-mcpexecutable; - a Codex MCP config;
- a Claude Code MCP config;
- a Claude Desktop MCP config on macOS.
Codex and Claude Code are configured with the absolute path to the installed timebox-mcp command. Claude Desktop automatic config currently targets the macOS config file; on Windows and Linux, use your client-specific MCP configuration and point it at the installed command path shown in Settings.
Manual Codex configuration:
codex mcp add timebox -- timebox-mcpManual Claude Code configuration:
claude mcp add -s user timebox -- timebox-mcpExposed tools:
| Tool | Purpose |
|---|---|
today |
Logged hours for a day. |
day_summary |
Daily plan/tracking summary with planned blocks, tracked work, residual capacity, and extra work. |
day_free_capacity |
Daily free-capacity analysis after planned blocks, tracked hours, and imported Todoist tasks. |
day_ready_blocks |
Blocks that still need enough ready Todoist intention, grouped by area/project. |
todoist_imported_tasks |
Imported Todoist tasks for a day, with match status and Timebox mapping. |
day_mismatches |
Operational mismatches between Timebox planning and imported Todoist tasks. |
week |
Weekly summary. |
projects |
List projects. |
areas |
List areas. |
status |
Quick status and alerts. |
log_hours |
Log hours on a project. |
find_area |
Search areas by name. |
find_project |
Search projects by name or description. |
rename_area |
Rename an area. |
rename_project |
Rename a project. |
update_project |
Update project metadata. |
move_project |
Move a project to another area. |
create_project |
Create a project. |
delete_project |
Delete a project with no entries. |
merge_project_entries |
Move entries into another project and delete the source project. |
get_recurring |
List all recurring template blocks (day, slot, area, hours). |
set_recurring_slot |
Replace all recurring blocks for one day+slot (e.g. Mon AM). |
get_week_overrides |
List overrides for a specific week (weekKey = Monday ISO date). |
set_week_override |
Set an override for one slot of one day of a specific week. |
clear_week_override |
Remove an override, reverting that slot to the recurring template. |
These tools are the MCP-oriented daily layer on top of the weekly board and Todoist cache. They are read-only diagnostics except for log_hours.
| Tool | Input | Output |
|---|---|---|
today |
{ date? } |
Logged entries for one day, grouped by AM/PM, with total tracked and billable hours. |
day_summary |
{ date? } |
Planned capacity, tracked hours, residual capacity, per-slot block source (template or override), and extra work by area. |
day_free_capacity |
{ date? } |
Split between capacity still reserved to planned areas and capacity that is truly free after tracked work and imported Todoist tasks. |
day_ready_blocks |
{ date? } |
AM/PM blocks that still lack enough ready Todoist work, grouped by area and then by Timebox project. |
todoist_imported_tasks |
{ date? } |
Imported tasks with Todoist project, matched Timebox project, area, slot, due date, estimate, and match status. |
day_mismatches |
{ date? } |
Unmapped tasks, tasks outside planned areas, tasks over block capacity, and blocks with insufficient ready-task coverage. |
All date inputs use YYYY-MM-DD and default to today when omitted.
day_summaryreports each slot asAM [template]orPM [override]to show whether the plan came from the recurring template or from a week-specific override.todoist_imported_tasksand the Todoist-related sections ofday_free_capacity/day_mismatcheswork from the cached tasks imported into Timebox for that date, not directly from a live Todoist call.Residualmeans planned capacity minus tracked hours for the day.Available after tracked + taskssubtracts both tracked work and imported Todoist estimates from planned capacity.Reserved without tasksis capacity still assigned to planned areas that do not yet have enough matching Todoist intention.Actually free (unallocated)is capacity not reserved by planned blocks after considering tracked work and imported tasks.day_mismatchesis the main diagnostic tool when planning and imported task data disagree.
day_summary({ "date": "2026-06-17" })
day_free_capacity({ "date": "2026-06-17" })
day_ready_blocks({ "date": "2026-06-17" })
todoist_imported_tasks({ "date": "2026-06-17" })
day_mismatches({ "date": "2026-06-17" })
log_hours({ "project": "website", "hours": "2:30", "slot": "pm", "date": "2026-06-17" })
- Review the plan source and residual capacity with
day_summary. - Check whether the remaining time is really free or still reserved with
day_free_capacity. - Inspect missing next actions with
day_ready_blocks. - Audit imported Todoist data with
todoist_imported_tasks. - Use
day_mismatcheswhen tasks do not fit the planned blocks cleanly.
See BUILD.md for packaging details.
npm run dist:mac
npm run dist:win
npm run dist:linuxArtifacts are generated with electron-builder. GitHub Releases are configured as the electron-updater provider. macOS packages are intended for personal installation without Apple notarization; if Gatekeeper reports the app as damaged after installing it in /Applications, remove the download quarantine with xattr -dr com.apple.quarantine /Applications/Timebox.app. Windows packages should be Authenticode-signed for smoother installation; Linux AppImage builds are unsigned local artifacts unless the release workflow adds distribution-specific signing.
Update behavior depends on the platform, because in-place auto-update requires code signing on macOS:
- macOS: an unsigned/ad-hoc build cannot auto-update (Squirrel.Mac rejects it without an Apple Developer ID). The app instead checks the latest GitHub release and, when a newer version exists, shows a notice offering to open the download page; you then install manually (
xattr -dr com.apple.quarantine ...as above). - Windows and Linux:
electron-updaterupdates without signing. The app does not download silently: it asks for confirmation before downloading the update and again before restarting to install it.
Local commits do not build or publish anything. The CI workflow runs on pull requests and pushes to main; the release workflow runs only when a v* tag is pushed. For regular development, work on a feature branch and avoid creating version tags until a release is intentional.
window.apiexists only in Electron and is exposed bypreload.js.index.htmlincludes a browser-only mockwindow.apifor quick Vite previews and browser-based tests. Do not remove it.cli/index.jsis the developer CLI that accesses SQLite directly throughbetter-sqlite3.cli/standalone.jsandcli/mcp-server.jsavoid the Node/Electron ABI problem by using only built-in Node modules and the HTTP bridge.npm testrebuildsbetter-sqlite3for Node.js before runningnode --test cli/__tests__/*.test.js.- After running tests, run
npm run rebuildagain before opening the Electron app if the native module was rebuilt for Node.
npm test
npm run rebuild