Querying work time from ActivityWatch for use with Beeminder

I’m currently in the process of switching dev machines, 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 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
#!/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 to then sync that data to Beeminder using Buzz.

You can find this script plus other Beeminder-related scripts and Linux mods in my dotfiles repository.

2 Likes