# Querying work time from ActivityWatch for use with Beeminder

**URL:** <https://forum.beeminder.com/t/querying-work-time-from-activitywatch-for-use-with-beeminder/12659>\
**Category:** Tech\
**Created:** [December 15, 2025, 6:10pm UTC](https://forum.beeminder.com/t/querying-work-time-from-activitywatch-for-use-with-beeminder/12659 "2025-12-15T18:10:14Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![narthur](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/narthur/32/5113_2.png) [@narthur](https://forum.beeminder.com/u/narthur)\
**Post date:** [December 15, 2025, 6:10pm UTC](https://forum.beeminder.com/t/querying-work-time-from-activitywatch-for-use-with-beeminder/12659/1 "2025-12-15T18:10:15Z")

</div>

I’m currently in the process of [switching dev machines](https://narthur.substack.com/p/publishing-my-dotfiles), and as a part of that I had to revisit how I track my work time.

Last time I rolled my own script that just tracked the time I was actively using my work machine. But this time around I decided to instead track the time that [ActivityWatch](https://activitywatch.net/) says I’m actually working.

Turns out querying this data isn’t at all intuitive, so thought I’d share the script I ended up with here:

> **The Bash Script**
>
> ```bash
> #!/bin/bash
> 
> # Accept optional date argument (format: YYYY-MM-DD), defaults to today
> target_date="${1:-$(date +%Y-%m-%d)}"
> 
> # Get timestamps for the target date
> day_start=$(date -d "$target_date" +%Y-%m-%dT00:00:00%:z)
> day_end=$(date -d "$target_date + 1 day" +%Y-%m-%dT00:00:00%:z)
> timeperiod="${day_start}/${day_end}"
> 
> # Fetch settings including categories
> settings=$(curl -s 'http://localhost:5600/api/0/settings')
> 
> # Extract categories from settings and transform to query format
> # Settings format: {"name": [...], "rule": {...}} -> Query format: [[...], {...}]
> # Filter out categories with null type (like "Uncategorized")
> categories=$(echo "$settings" | jq -c '[.classes[] | select(.rule.type != null) | [.name, .rule]]')
> 
> # Build the query with dynamic categories
> query=$(jq -n --argjson cats "$categories" --arg tp "$timeperiod" '{
> "query": [
> "afk_events = query_bucket(find_bucket(\"aw-watcher-afk_\"));",
> "window_events = query_bucket(find_bucket(\"aw-watcher-window_\"));",
> "window_events = filter_period_intersect(window_events, filter_keyvals(afk_events, \"status\", [\"not-afk\"]));",
> "merged_events = merge_events_by_keys(window_events, [\"app\", \"title\"]);",
> ("merged_events = categorize(merged_events, " + ($cats | tostring) + ");"),
> "RETURN = merged_events;",
> ";"
> ],
> "timeperiods": [$tp]
> }')
> 
> # Run query and filter for Work category, then sum durations
> curl -s 'http://localhost:5600/api/0/query/' \
> -X POST \
> -H 'Content-Type: application/json' \
> --data-raw "$query" | jq '[.[0][] | select(.data["$category"][0] == "Work") | .duration] | add'
> 
> ```

In my case, I turn around and use [another script](https://github.com/narthur/dotfiles/blob/main/bin/sync-work-time) to then sync that data to Beeminder using [Buzz](https://github.com/pinepeakdigital/buzz).

You can find [this script](https://github.com/narthur/dotfiles/blob/main/bin/get-work-time) plus other Beeminder-related scripts and Linux mods in [my dotfiles repository](https://github.com/narthur/dotfiles).
