Remote Work Tools

Productivity tracking for remote teams sits on a spectrum from surveillance tools that screenshot every 5 minutes to outcome-based metrics that track shipped work. The tools you choose signal what you trust about your team.

This guide covers the practical end of the spectrum: time tracking that helps individuals understand their own work patterns, project-level metrics that help managers spot blockers, and the activity data worth paying attention to versus the data that creates anxiety without insight.

What to Track (and What Not To)

Worth tracking:

Not worth tracking:

Surveillance tools destroy trust faster than they surface any useful data. Opt for transparency: tools where each team member sees their own data and controls what managers can see.

Time Doctor

Time Doctor is one of the more feature-complete team time trackers. It has optional screenshot capture (which you should probably disable), time tracking, project/task allocation, and reports.

Best for: Client-billing agencies that need detailed time allocation across projects.

Pricing: $5.90/user/month (Basic). $8.40/user/month (Standard — most features). $16.70/user/month (Premium).

Setup:

# Time Doctor CLI for developers who prefer terminal
# Install the Time Doctor desktop app, then use the API

# Pull time data for a date range via API
curl -X GET "https://api.timedoctor.com/v1.1/worklog" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -G \
  --data-urlencode "start_date=2026-03-15" \
  --data-urlencode "end_date=2026-03-21" \
  --data-urlencode "user_ids=USER_ID" \
  --data-urlencode "company_id=COMPANY_ID"

Key settings for healthy remote teams:

Hubstaff

Hubstaff is similar to Time Doctor with stronger GPS tracking (irrelevant for remote) and better integrations with Asana, Linear, and GitHub.

Best for: Teams who want time tracking that syncs automatically with their project tracker.

Pricing: $7/user/month (Starter). $10/user/month (Grow). $20/user/month (Team).

GitHub integration setup:

# Hubstaff GitHub integration syncs commits to tasks automatically
# Configure in Hubstaff → Integrations → GitHub
# Maps GitHub repos to Hubstaff projects
# When a developer commits, time gets logged against the linked task

# Pull Hubstaff reports via API
curl -X GET "https://api.hubstaff.com/v2/organizations/ORG_ID/activities" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -G \
  --data-urlencode "time_slot[start]=2026-03-15T00:00:00Z" \
  --data-urlencode "time_slot[stop]=2026-03-21T23:59:59Z"

RescueTime

RescueTime runs in the background and categorizes time automatically based on the apps and sites you use. It’s personal-first — individuals get their own dashboard, managers get aggregate anonymized data.

Best for: Teams who want productivity insight without the surveillance feeling. Each person owns their data.

Pricing: Free (basic). $6.50/month (Premium, per person).

Setup and API usage:

# RescueTime API — pull your own productivity data
RESCUETIME_API_KEY="your_api_key"

# Get daily productivity summary
curl "https://www.rescuetime.com/anapi/daily_summary_feed" \
  -G \
  --data-urlencode "key=${RESCUETIME_API_KEY}" \
  --data-urlencode "format=json" \
  --data-urlencode "restrict_begin=2026-03-15" \
  --data-urlencode "restrict_end=2026-03-21"

# Get time by category (Communication, Development, Reference, etc.)
curl "https://www.rescuetime.com/anapi/data" \
  -G \
  --data-urlencode "key=${RESCUETIME_API_KEY}" \
  --data-urlencode "format=json" \
  --data-urlencode "perspective=interval" \
  --data-urlencode "resolution_time=day" \
  --data-urlencode "restrict_begin=2026-03-15" \
  --data-urlencode "restrict_end=2026-03-21" \
  --data-urlencode "restrict_kind=category"

Building Your Own Light Metrics Dashboard

For teams who want outcome-based metrics without buying a surveillance tool, pull data from the tools you already use:

#!/usr/bin/env python3
# Weekly engineering metrics report
# Pulls from GitHub and Linear, posts to Slack

import requests
from datetime import datetime, timedelta

GITHUB_TOKEN = "ghp_XXXXXX"
LINEAR_API_KEY = "lin_api_XXXXXX"
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK"

ORG = "your-org"
TEAM = "ENG"
SINCE = (datetime.now() - timedelta(days=7)).isoformat() + "Z"

def get_github_metrics():
    """PRs merged, review time, deploy events"""
    headers = {"Authorization": f"token {GITHUB_TOKEN}"}

    # PRs merged this week
    prs = requests.get(
        f"https://api.github.com/search/issues",
        params={
            "q": f"org:{ORG} is:pr is:merged merged:>{SINCE[:10]}",
            "per_page": 100
        },
        headers=headers
    ).json()

    return {
        "prs_merged": prs.get("total_count", 0),
    }

def get_linear_metrics():
    """Issues completed, cycle time"""
    query = """
    query {
      issues(
        filter: {
          completedAt: { gte: "%s" }
          team: { key: { eq: "%s" } }
        }
      ) {
        nodes {
          identifier
          title
          createdAt
          completedAt
          cycleTime
        }
      }
    }
    """ % (SINCE, TEAM)

    resp = requests.post(
        "https://api.linear.app/graphql",
        json={"query": query},
        headers={"Authorization": LINEAR_API_KEY}
    )
    issues = resp.json().get("data", {}).get("issues", {}).get("nodes", [])

    if not issues:
        return {"issues_completed": 0, "avg_cycle_time_days": 0}

    cycle_times = [i.get("cycleTime", 0) for i in issues if i.get("cycleTime")]
    avg_cycle = sum(cycle_times) / len(cycle_times) / 86400 if cycle_times else 0

    return {
        "issues_completed": len(issues),
        "avg_cycle_time_days": round(avg_cycle, 1)
    }

def post_to_slack(github, linear):
    payload = {
        "blocks": [
            {
                "type": "header",
                "text": {"type": "plain_text", "text": "Weekly Engineering Metrics"}
            },
            {
                "type": "section",
                "fields": [
                    {"type": "mrkdwn", "text": f"*PRs Merged*\n{github['prs_merged']}"},
                    {"type": "mrkdwn", "text": f"*Issues Completed*\n{linear['issues_completed']}"},
                    {"type": "mrkdwn", "text": f"*Avg Cycle Time*\n{linear['avg_cycle_time_days']} days"},
                ]
            }
        ]
    }
    requests.post(SLACK_WEBHOOK, json=payload)

github = get_github_metrics()
linear = get_linear_metrics()
post_to_slack(github, linear)
print(f"Posted metrics: {github} | {linear}")

Meeting Load Tracking

One of the highest-value things to track for remote teams: how many hours per week each person spends in meetings. High meeting load is the #1 killer of deep work for remote engineers.

# Pull meeting hours from Google Calendar via CLI (gcalcli)
pip install gcalcli

# Weekly meeting summary
gcalcli --calendar "Work" agenda \
  --start "$(date -d 'last monday' +%Y-%m-%d)" \
  --end "$(date +%Y-%m-%d)" \
  --tsv | awk -F'\t' '{
    # Calculate duration and sum
    # Output: date, title, duration
    print $1, $4, $5
  }'

Alert threshold: If anyone on your team is in meetings more than 15 hours per week, that’s a problem worth addressing before tracking anything else.

Built by theluckystrike — More at zovo.one