About work-hours
A skill for estimating working hours from a person’s existing activity timestamps. It compares message-based and device-presence estimates, with guidance for calibrating idle periods and interpreting evidence.
Key features
- 01Combines activity from multiple sources
- 02Compares message and presence models
- 03Calibrates idle thresholds with evidence
- 04Attributes time to customers
- 05Accounts for leisure and breaks
Use cases
- 01Estimating hours worked from activity logs
- 02Comparing work-time estimates across methods
- 03Separating breaks and leisure from work
- 04Preparing weekday and customer-level reports
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ marks97/ work-hours --skill work-hoursAfter installing, type/work-hoursin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/config.example.jsonmac-upload-prompt.mdmethodology.mdsources.mdscripts/calibrate.pycommon.pyextract_chess.pyextract_claude.pyextract_mac.pyextract_meetings.pyextract_whatsapp.pymodel.pyslack_parse.pyDetails
From the project READMEWork hours
Reconstructs working time after the fact from timestamps the person already produces. The answer is only as good as two things: telling the person's own actions from automation, and deciding what a silence means. Everything below is about getting those two right.
Read references/methodology.md before trusting any number — it lists the traps that each moved the answer
by hours. references/sources.md has the per-source how-to.
Ground rules
- Personal data, temporary by default. Everything goes in the run dir from the config
(
run_dir, default~/.cache/work-hours/run). Never write extracted events into a repo. When the person says they are done, delete the run dir and every upload (browser histories, chat exports, screen-time databases); confirm the paths are gone. - Read-only everywhere. Slack, GitHub, trackers, mail: search and read, never post or react.
- Throttle. Transcript folders run to gigabytes:
nice -n 19 ionice -c3, stream line by line, one or two readers,python3 -I. - Ask for data, don't guess it. The person can give you things you cannot reach (chat exports, laptop files, a chess username, their meeting schedule). Ask early; see "What to ask for".
Workflow
S=<this skill>/scripts
# 0. config: copy references/config.example.json to ~/.config/work-hours/config.json and fill it in
# (timezone, window, customer rules, Slack user id, chess usernames, meeting schedule, categories).
# The person's real config never lives inside the skill.
python3 -I $S/extract_claude.py # human prompts + answers/interrupts from transcripts
# Slack: run the MCP search loop (references/sources.md), then:
python3 -I $S/slack_parse.py <saved result files...>
python3 -I $S/extract_whatsapp.py _chat.txt --me "<name>" --customer <c> --tag <who>
python3 -I $S/extract_chess.py # chess.com / lichess → leisure intervals
python3 -I $S/extract_meetings.py # recordings + recurring calls (run after the point extractors)
python3 -I $S/extract_mac.py <upload-dir> # laptop presence, foreground apps, browsing
# any other human-only source (PR approvals, tracker moves made in the UI): write events.<name>.ndjson
python3 -I $S/calibrate.py # silence survival, bout criterion, ground-truth check
python3 -I $S/model.py --idle 25 --tail 5 # → report.md
Point events are events.<source>.ndjson (ts, src, customer, kind); intervals are
intervals.<source>.ndjson (start, end, category). model.py picks up every file matching those names,
so a new source is just a new file.
The model
Two independent methods. Report both; their agreement is the accuracy claim.
- B. Messages: one merged timeline of every human action across every source. A silence of up to
idleminutes is work; a longer one is a break that still creditstailminutes after the last action. Leisure intervals (chess games, leisure browsing) are subtracted. - A. Presence: laptop present (unlocked, display-sleep tail removed), minus leisure, plus off-laptop work
only where there is evidence: phone messages while the laptop was away, bridged when under
idleminutes apart, and calls. Do not let the message model's gap-filling run while the laptop says the person left — that double-counts about 1.5 h/day.
Measure across all customers combined first; the per-customer split comes from attributing each worked minute to the nearest action's customer. Cut workdays at a fixed local hour (default 05:00), so a 02:00 session belongs to the day before.
Choosing the idle threshold (the number that matters most)
On one real dataset the answer swung from 6.0 to 13.9 h/day between a 5- and 60-minute threshold, and the gap histogram had no natural break. Calibrate it; don't pick it.
calibrate.pygives the bout criterion: a lower bound, usually around 10 minutes.- Ground truth from the person's own words. Read their messages for "lunch", "brb", "back",
"going to sleep", "on the train, working from my phone", in every language they use. Read each hit
yourself: a word list must not decide meaning. Write
labels.ndjsonwithawayandphone_workstretches. The right threshold separates the two. In the calibration run, stated breaks always left a silence of 27 minutes or more and phone work never exceeded about 26, so the answer was 25 minutes. - Attention check. In agent-driven work, see whether the person replies seconds after an agent finishes (they were watching) or long after (they were away). If replies come about 16 minutes after the agent finished and almost never within 2 minutes, a silence is not attentive waiting, so do not stretch the threshold to cover it.
What to ask for
Ranked by value:
- Laptop activity. Send
references/mac-upload-prompt.mdto an agent on their Mac. It uploads browser histories,knowledgeC.db(screen on, foreground app) and the lock/power logs. This is the strongest signal. - Chat exports with the people they work with (WhatsApp "Export chat").
- Usernames for leisure that has an API (chess.com, lichess).
- Recurring meetings: day, time, length, and when they changed.
- Slow exports, if they want more precision: Google Takeout (YouTube and Maps), Apple Health (sleep). iPhone Screen Time only reaches the Mac if "Share Across Devices" is on, and even then it sits in system stores that need Full Disk Access.
- Vacations and other periods to exclude, as config
exclude.
Reporting
Lead with the combined hours per weekday and weekend day, then the range across methods and thresholds. Then cover the per-customer split, the clock-time routine (start, meal dips, second shift, end) and how much leisure was found inside "work". Name what is still invisible: phone screen time, unrecorded calls, thinking away from the keyboard.