timing-cli
Ships an embedded MCP server that lets the Hermes personal agent query activity and create Timing time entries without exposing the raw local SQLite database.
Reads Timing.app's local activity database on macOS and pushes aggregated time entries back to Timing via its Web API, enabling agents to query local app usage and create time entries.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@timing-clisuggest time entries for yesterday"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
timing-cli
A command-line interface and MCP server for Timing.app
on macOS. Unlike the Timing Web API — which cannot see your locally recorded app
usage — timing-cli reads Timing's local activity database directly
(read-only) and turns real app usage into aggregated time entries, which it can
then push back to Timing via the Web API.
Because it ships an embedded MCP server, agents (e.g. the Hermes personal agent)
can query your activity and create entries without ever copying or exposing
the raw SQLite.db — the server runs locally, next to the database.
Not affiliated with Timing. Independent tool. It only ever reads the local database; all writes go through the official Web API.
What it does
Reads the local Timing store (
~/Library/Application Support/info.eurocomp.Timing2/SQLite.db) read-only: automatic app activity, window titles, document paths, projects.Classifies unassigned activity onto projects with your own rules, plus a packaged default Cognovis ruleset that already covers most of a normal workday (Timing's built-in predicate rules only cover ~15% of activity). It also reuses decoded Timing project predicate rules from the local database when neither user nor default rules already assigned a slice.
Aggregates consecutive same-project slices into clean time blocks (gap-merging + minimum-duration filtering).
Pushes the resulting suggestions to Timing as real time entries via the Web API (with a safe dry-run default).
Serves all of this over MCP (
timing serve) for agents.Reconstructs bounded read-only work evidence from existing bookings and automatic activity without making a billing decision.
Related MCP server: WakaTime MCP Server
Quickstart
# Install (globally, via uv)
uv tool install timing-cli
# See what's in your local database
timing info
# Daily project summary and suggested entries (read-only)
timing summary --date 2026-07-05
timing suggest --date 2026-07-05
# Reconstruct a synthetic example month as machine-readable evidence
timing reconstruct --month 2026-07 --limit 50 --json
# Push suggestions to Timing (dry-run first, then --yes)
export TIMING_API_KEY=... # from https://web.timingapp.com/integrations/tokens
timing push --date 2026-07-05 # dry-run
timing push --date 2026-07-05 --yes # actually create entries
# Run the MCP server (for Hermes / other agents)
timing serve # stdio
export TIMING_MCP_TOKEN=...
timing serve --transport http # HTTP on 127.0.0.1:8321
# Install a LaunchAgent so the HTTP server auto-starts at login
timing serve --install # requires TIMING_MCP_TOKEN / mcp_http_token
timing serve --uninstalltiming push --yes resolves every non-unassigned suggestion to a unique Web-API
project before creating anything. If a project is ambiguous or unmapped, the push
fails before the first write; add a [project_mappings] override. Re-running the
same push skips matching existing entries unless --replace is passed.
Commands
Command | Description |
| Database location, recorded date range, token status |
| List projects (local DB or Web API) |
| Raw automatically tracked app usage |
| Total time per project |
| Aggregated time-entry suggestions (read-only) |
| Create entries via Web API (dry-run by default) |
`timing reconstruct (--month YYYY-MM | --from ISO --to ISO) [--limit N] [--cursor TOKEN] [--json]` |
| Run the MCP server |
| Install/remove a LaunchAgent that runs |
Machine-readable and non-interactive output
Every command that returns data accepts --json: info returns an object;
projects, usage, summary, and suggest return arrays; push returns an
object containing dry_run, created, skipped, and suggestions; and
reconstruct returns the versioned object documented below. --json changes
the output format only: push remains a dry-run unless --yes is also present.
JSON is written directly to stdout without Rich rendering, tables, or ANSI
codes. Datetimes are ISO-8601 strings, and local SQLite identifiers are decimal
strings so values larger than 2^53 remain lossless. The remote
projects --remote --json path returns the Timing Web API project payload.
projects --local-only makes local access explicit and rejects --remote; the
bounded Executive Pack acceptance resource uses this form.
Human-readable output uses Rich tables. Put the global option before the
subcommand, for example timing --no-color summary; ANSI color is also disabled
automatically whenever stdout is not a TTY. Non-TTY human output remains a
human-oriented table, and no progress indicator or spinner is started there, so
scripts and agents should select --json explicitly.
Window semantics
Date-based CLI queries and MCP tools interpret a local day as the half-open
interval from local midnight to the next local midnight using the system
timezone rules. Daylight-saving transitions therefore change elapsed duration:
in Europe/Berlin, 2026-03-29 runs from 2026-03-29T00:00:00+01:00 to
2026-03-30T00:00:00+02:00 and spans 23 elapsed hours. Adjacent ordinary days
span 24 hours. Explicit offset-aware --from/--to (CLI) or start/end
(MCP) endpoints preserve the instants supplied by the caller.
Daily workflow
# 1. Inspect the day without writing anything.
timing summary --date 2026-07-05
timing suggest --date 2026-07-05
# 2. If push projects are unmapped, inspect remote project references.
export TIMING_API_KEY=...
timing projects --remote
# 3. Add missing [project_mappings] entries, then dry-run again.
timing push --date 2026-07-05
# 4. Create entries once the dry-run looks right.
timing push --date 2026-07-05 --yesRe-running step 4 for the same day skips matching existing entries. Use
--replace only when you deliberately want Timing's API to replace overlapping
entries in the target window.
Reconstruction evidence contract
timing reconstruct and the MCP reconstruct_work tool share the
timing.reconstruction.v1 response schema. Both are strictly read-only. Supply
either a local calendar month or both endpoints of an explicit half-open window:
timing reconstruct --month 2026-07 --limit 50 --json
timing reconstruct \
--from 2026-07-14T09:00:00+02:00 \
--to 2026-07-14T18:00:00+02:00 \
--limit 50 --jsonThe equivalent MCP tool arguments are
reconstruct_work(month="2026-07", limit=50, cursor=None).
A booking in that response might identify project Synthetic Studio, carry the
title Synthetic design review, and expose a lossless source ID such as
"18014398509482083".
The top-level fields are:
Field | Meaning |
| Fixed value |
| Local timezone, exact |
| Requested limit, stable order ( |
| Whole-window duration metrics; |
| At most |
The maximum page size is 200. Continue with the returned cursor until
pagination.complete is true; this retrieves every source record in the month
without materializing the month as one list. Source rows use SQL keyset paging,
and whole-window calculations scan those rows in bounded batches. A cursor is
bound to a digest of its source-window snapshot; if local Timing data changes,
the continuation fails as stale and must be restarted from the first page.
Relationship lists on a record are also capped at 200 and carry their own *_complete flag,
so dense evidence is never silently presented as complete. Stable source
references have the form booking:<decimal-id> or activity:<decimal-id>.
Ordering uses the clipped source.start, then bookings before activities, then
the decimal source ID. Project title chains retain the 32 most-specific entries
and expose project_title_chain_complete; assignment alternatives are capped at
100 and expose alternatives_complete and conflicts_complete. Per-record
candidate_intervals are capped at 200 and expose
candidate_intervals_complete.
Every local SQLite identifier is serialized as a decimal string on CLI JSON and
MCP boundaries, including identifiers larger than JavaScript's safe integer
range; Python and SQLite continue to use integers internally.
Each assignment includes the current project, selected/proposed project,
origin (existing, user_rule, packaged_rule, timing_predicate, or
unresolved), stable rule identity, matched source field/value, bounded matching
alternatives with completeness metadata, conflict evidence, and an uncertainty
reason when applicable.
Existing assignments keep precedence, but a differently assigned booking stays
visible with its original project and proposed alternatives.
Duration fields are deliberately separate and never imply approval to charge:
Metric | Scope and definition |
| Window-scoped sum of every clipped automatic-activity duration; overlapping/identical slices count separately. |
| Window-scoped union of automatic-activity intervals; overlaps count once. |
| Window-scoped elapsed time from the first evidence start to the last evidence end. |
| Window-scoped evidence span not covered by either booking or automatic activity. |
| Window-scoped union of existing booking intervals; overlapping bookings count once. |
| Window-scoped union of automatic activity outside every existing booking. It is evidence for review, not an approved charge. |
| Record-scoped uncovered portion of one automatic-activity source. |
Automatic activity covered by a booking appears as supporting evidence for that
single recorded service. Only uncovered portions appear in candidate_intervals.
Cross-project overlaps are retained as explicit source references rather than
being assigned to whichever row sorts first. The schema contains no rates,
rounding, invoicing, or persistent review decisions.
Configuration
Optional config at ~/.config/timing-cli/config.toml:
# Override the database path if Timing lives elsewhere.
# db_path = "~/Library/Application Support/info.eurocomp.Timing2/SQLite.db"
api_base_url = "https://web.timingapp.com/api/v1"
# api_token = "..." # prefer the TIMING_API_KEY env var instead
# mcp_http_token = "..." # prefer the TIMING_MCP_TOKEN env var instead
min_block_seconds = 120 # drop aggregated blocks shorter than this
gap_merge_seconds = 300 # merge same-project slices split by a gap up to this
# Optional overrides from local Timing projects to Web-API project references.
# Keys can be a local id ("id:42"), a full title chain, or a leaf title.
[project_mappings]
"Client / MIRA" = "/projects/123"
"id:42" = "/projects/456"
# Classification rules: map unassigned activity onto projects.
# First match wins. `app`/`bundle_id` are case-insensitive substrings;
# `title`/`path` are regexes (already matched case-insensitively, so don't
# add an inline `(?i)`).
[[rules]]
project = "MIRA"
title = "polaris"
[[rules]]
project = "Cognovis"
path = "code/mira"Default classification rules
Even with no config file at all, timing-cli ships a ready-to-use ruleset
tuned for Malte's local setup (timing_cli/default_rules.py,
DEFAULT_COGNOVIS_RULES) that maps common cmux/editor window titles and repo
paths onto real Timing projects:
MIRA — Polaris/MIRA work: titles matching
polaris|mira|diagnos|isynet| patient|anonymiz|de-id|gc50, or paths containingpolaris/mira. (There is no Timing project literally named "Polaris" — it's tracked as "MIRA".)Client projects — title keywords for Syntegon, Romelag, MCN, ATR, C4B, NTS, BBW, Kolibri, FUD, Eubylon, Solutio, DST, Agiler Norden.
]project-open[— title/path containingproject-open.Home Electronic — title/path containing
home-infraoropen-brain.cognovis Verwaltung — internal tooling/admin keywords (
timing-cli,collmex,paperless,invoice,rechnung,library,beads,claude,codex,agent,skill,acp), repo paths undercode/(cli-tools| library|timing-cli|collmex-cli|mm-cli),.agents, or/skills/, and finally a catch-all for any remainingcmuxactivity.
These defaults are appended after your own [[rules]] (user rules always
win, since the first matching rule wins) and are controlled by:
use_default_rules = true # default; set to false to rely solely on your own [[rules]]Timing's own project predicates are loaded from the local Project.predicate
column automatically and applied after both explicit config rules and the
packaged defaults. Keep local rules for repository paths, editor titles, and
project-specific conventions that neither Timing nor the packaged defaults
classify well.
Cognovis example
This is a real-world shape for Malte's local setup. Fill the Web-API project
references from timing projects --remote; local project ids can be discovered
with timing projects. With use_default_rules left at its default true,
most of this is already covered — these overrides just fill in
project_mappings and add a couple of extra keywords the defaults don't know
about yet.
api_base_url = "https://web.timingapp.com/api/v1"
min_block_seconds = 120
gap_merge_seconds = 300
[project_mappings]
"MIRA" = "/projects/REMOTE_MIRA"
"cognovis Verwaltung" = "/projects/REMOTE_COGNOVIS_VERWALTUNG"
"]project-open[" = "/projects/REMOTE_PROJECT_OPEN"
"Home Electronic" = "/projects/REMOTE_HOME_ELECTRONIC"
# Extra project-specific keyword not covered by the packaged defaults.
[[rules]]
project = "cognovis Verwaltung"
title = "confluence|jira"MCP tools
timing serve exposes: list_timing_projects, list_app_usage_tool,
daily_project_summary, suggest_time_entries, create_time_entry (write),
recorded_date_range, and reconstruct_work (read-only, bounded).
HTTP transport requires bearer-token authentication via TIMING_MCP_TOKEN or
mcp_http_token in the config. Stdio transport remains local and does not require
an MCP token.
LaunchAgent (persistent local HTTP server)
timing serve --install writes a LaunchAgent plist
(~/Library/LaunchAgents/de.sussdorff.timing-serve.plist, label
de.sussdorff.timing-serve) that runs timing serve --transport http at login
(RunAtLoad/KeepAlive, LimitLoadToSessionType=Aqua). Logs go to
~/Library/Logs/timing-serve.log / .err.log. Installing requires
TIMING_MCP_TOKEN or mcp_http_token to already be set — the command raises an
error otherwise instead of silently generating a token. timing serve --uninstall
unloads the agent (launchctl bootout) and removes the plist.
Release
The repository includes a GitHub Actions release workflow at
.github/workflows/release.yml. Tag pushes run tests and lint only; published
GitHub releases or manual dispatches build the package and publish to PyPI using
Trusted Publishing. Configure a PyPI trusted publisher for the repository and
the pypi environment before running the publish job.
Requirements
macOS with Timing.app installed
Python 3.12+ (installed automatically by
uv tool install)A Timing Web API token for pushing entries (read-only commands need no token)
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
- TimequipOAuthcom.timequip
Manage Timequip projects, tasks, comments, members, and dashboards through MCP.
Read and write Mission Control state via MCP — projects, tasks, subtasks, templates, status updates.
Read time entries, projects, clients, tasks and invoices; log and update tracked time.
- REPSLogOAuthcom.reps-log
Log and read REPS time logs, properties, and categories via MCP. Requires REPSLog Premium.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceGenerates timesheets from activity data and automates submission to PSI Project Server (SharePoint-based systems) using browser automation. Works with Activity Collector MCP to fetch data from GitLab, GitHub, and Calendar services.48 npmMIT
- AlicenseNot gradedqualityDmaintenanceProvides access to WakaTime coding analytics data through MCP tools. Enables querying coding stats, activity summaries, project lists, and time tracking information from your WakaTime account.MIT
- AlicenseNot gradedqualityDmaintenanceThis MCP server enables AI assistants to interact with the Timing application for managing time tracking and tasks, including project and time entry operations.7MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for SolidTime — the open-source time tracking app. Enables start/stop timers, manage time entries, projects, clients, tags, and tasks directly from MCP-compatible clients.1MIT