time-tracking

Time Tracking Integration Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "time-tracking" with this command: npx skills add vamseeachanta/workspace-hub/vamseeachanta-workspace-hub-time-tracking

Time Tracking Integration Skill

Master time tracking integrations with RescueTime and Toggl Track APIs for automated time entry, comprehensive reporting, productivity analytics, and project/task attribution. Build powerful time management automations and dashboards.

When to Use This Skill

USE Time Tracking APIs when:

  • Automating time entries - Log time from scripts, CI/CD, or other tools

  • Building productivity dashboards - Aggregate time data for analysis

  • Project billing - Track billable hours automatically

  • Focus time analysis - Understand productivity patterns

  • Team time management - Aggregate team time data

  • Integration pipelines - Connect time tracking with task managers

  • Automated reporting - Generate time reports programmatically

  • Custom analytics - Build specialized productivity insights

DON'T USE Time Tracking APIs when:

  • Simple manual tracking - Use native apps instead

  • Real-time monitoring - APIs have rate limits

  • Invasive employee surveillance - Ethical concerns

  • Complex invoicing - Use dedicated billing software

Prerequisites

Toggl Track Setup

Get API token from:

https://track.toggl.com/profile (scroll to API Token)

Set environment variable

export TOGGL_API_TOKEN="your-api-token"

Toggl uses HTTP Basic Auth with token as username, "api_token" as password

Or you can use the token directly

Verify authentication

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/me" | jq '.fullname'

RescueTime Setup

Get API Key from:

https://www.rescuetime.com/anapi/manage

Set environment variable

export RESCUETIME_API_KEY="your-api-key"

Verify authentication

curl -s "https://www.rescuetime.com/anapi/data?key=$RESCUETIME_API_KEY&format=json&restrict_kind=overview" | jq

Python Setup

Install dependencies

pip install requests python-dateutil pandas

Using uv

uv pip install requests python-dateutil pandas

Optional: For Toggl SDK

pip install toggl-python

Verify

python -c "import requests; print('Ready for time tracking integration!')"

Core Capabilities

  1. Toggl Track - Time Entries

REST API - Time Entries:

Get current running time entry

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/me/time_entries/current" | jq

Get recent time entries (last 7 days by default)

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/me/time_entries" | jq

Get time entries with date range

START_DATE=$(date -d "7 days ago" +%Y-%m-%dT00:00:00Z) END_DATE=$(date +%Y-%m-%dT23:59:59Z)

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/me/time_entries?start_date=$START_DATE&end_date=$END_DATE" | jq

Start a time entry (timer)

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/time_entries"
-d '{ "created_with": "api", "description": "Working on project documentation", "tags": ["documentation", "writing"], "billable": false, "workspace_id": WORKSPACE_ID, "project_id": PROJECT_ID, "start": "'$(date -u +%Y-%m-%dT%H:%M:%S.000Z)'", "duration": -1 }' | jq

Stop running time entry

curl -s -X PATCH -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/time_entries/TIME_ENTRY_ID/stop" | jq

Create completed time entry

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/time_entries"
-d '{ "created_with": "api", "description": "Code review session", "workspace_id": WORKSPACE_ID, "project_id": PROJECT_ID, "start": "2025-01-15T09:00:00.000Z", "duration": 3600, "tags": ["review", "development"] }' | jq

Update time entry

curl -s -X PUT -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/time_entries/TIME_ENTRY_ID"
-d '{ "description": "Updated description", "tags": ["updated-tag"] }' | jq

Delete time entry

curl -s -X DELETE -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/time_entries/TIME_ENTRY_ID"

Python - Toggl Time Entries:

import requests from datetime import datetime, timedelta from base64 import b64encode import os

class TogglClient: """Toggl Track API client."""

BASE_URL = "https://api.track.toggl.com/api/v9"

def __init__(self, api_token=None):
    self.api_token = api_token or os.environ.get("TOGGL_API_TOKEN")
    self.auth = (self.api_token, "api_token")

def _request(self, method, endpoint, data=None):
    """Make API request."""
    url = f"{self.BASE_URL}{endpoint}"
    response = requests.request(
        method,
        url,
        auth=self.auth,
        json=data,
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response.json() if response.text else None

def get_me(self):
    """Get current user info."""
    return self._request("GET", "/me")

def get_workspaces(self):
    """Get user's workspaces."""
    return self._request("GET", "/workspaces")

def get_current_entry(self):
    """Get currently running time entry."""
    return self._request("GET", "/me/time_entries/current")

def get_time_entries(self, start_date=None, end_date=None):
    """Get time entries within date range."""
    params = []
    if start_date:
        params.append(f"start_date={start_date.isoformat()}Z")
    if end_date:
        params.append(f"end_date={end_date.isoformat()}Z")

    query = f"?{'&'.join(params)}" if params else ""
    return self._request("GET", f"/me/time_entries{query}")

def start_timer(self, workspace_id, description, project_id=None, tags=None, billable=False):
    """Start a new timer."""
    data = {
        "created_with": "python_api",
        "description": description,
        "workspace_id": workspace_id,
        "billable": billable,
        "start": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z"),
        "duration": -1  # -1 indicates running timer
    }

    if project_id:
        data["project_id"] = project_id
    if tags:
        data["tags"] = tags

    return self._request(
        "POST",
        f"/workspaces/{workspace_id}/time_entries",
        data
    )

def stop_timer(self, workspace_id, entry_id):
    """Stop a running timer."""
    return self._request(
        "PATCH",
        f"/workspaces/{workspace_id}/time_entries/{entry_id}/stop"
    )

def create_time_entry(
    self,
    workspace_id,
    description,
    start_time,
    duration_seconds,
    project_id=None,
    tags=None,
    billable=False
):
    """Create a completed time entry."""
    data = {
        "created_with": "python_api",
        "description": description,
        "workspace_id": workspace_id,
        "start": start_time.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
        "duration": duration_seconds,
        "billable": billable
    }

    if project_id:
        data["project_id"] = project_id
    if tags:
        data["tags"] = tags

    return self._request(
        "POST",
        f"/workspaces/{workspace_id}/time_entries",
        data
    )

def update_time_entry(self, workspace_id, entry_id, **kwargs):
    """Update an existing time entry."""
    return self._request(
        "PUT",
        f"/workspaces/{workspace_id}/time_entries/{entry_id}",
        kwargs
    )

def delete_time_entry(self, workspace_id, entry_id):
    """Delete a time entry."""
    return self._request(
        "DELETE",
        f"/workspaces/{workspace_id}/time_entries/{entry_id}"
    )

Example usage

if name == "main": client = TogglClient()

# Get user info
user = client.get_me()
print(f"User: {user['fullname']}")

# Get workspaces
workspaces = client.get_workspaces()
workspace_id = workspaces[0]["id"]
print(f"Workspace: {workspaces[0]['name']}")

# Start timer
entry = client.start_timer(
    workspace_id=workspace_id,
    description="Working on API integration",
    tags=["development", "api"]
)
print(f"Started timer: {entry['id']}")

# Get current timer
current = client.get_current_entry()
if current:
    print(f"Running: {current['description']}")

# Stop timer
stopped = client.stop_timer(workspace_id, entry["id"])
print(f"Stopped: Duration {stopped['duration']} seconds")

2. Toggl Track - Projects and Clients

REST API - Projects:

Get workspace projects

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/projects" | jq

Create project

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/projects"
-d '{ "name": "Client Website Redesign", "color": "#0b83d9", "is_private": false, "active": true, "client_id": CLIENT_ID, "billable": true, "estimated_hours": 100 }' | jq

Get clients

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/clients" | jq

Create client

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/clients"
-d '{ "name": "Acme Corporation", "notes": "Primary contact: John Doe" }' | jq

Get tags

curl -s -u "$TOGGL_API_TOKEN:api_token"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/tags" | jq

Create tag

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/api/v9/workspaces/WORKSPACE_ID/tags"
-d '{"name": "urgent"}' | jq

Python - Projects and Clients:

def get_projects(self, workspace_id): """Get all projects in workspace.""" return self._request("GET", f"/workspaces/{workspace_id}/projects")

def create_project( self, workspace_id, name, client_id=None, color="#0b83d9", billable=False, estimated_hours=None ): """Create a new project.""" data = { "name": name, "color": color, "billable": billable, "active": True }

if client_id:
    data["client_id"] = client_id
if estimated_hours:
    data["estimated_hours"] = estimated_hours

return self._request(
    "POST",
    f"/workspaces/{workspace_id}/projects",
    data
)

def get_clients(self, workspace_id): """Get all clients in workspace.""" return self._request("GET", f"/workspaces/{workspace_id}/clients")

def create_client(self, workspace_id, name, notes=None): """Create a new client.""" data = {"name": name} if notes: data["notes"] = notes

return self._request(
    "POST",
    f"/workspaces/{workspace_id}/clients",
    data
)

3. Toggl Track - Reports

Reports API:

Summary report

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/reports/api/v3/workspace/WORKSPACE_ID/summary/time_entries"
-d '{ "start_date": "2025-01-01", "end_date": "2025-01-31", "grouping": "projects", "sub_grouping": "users" }' | jq

Detailed report

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/reports/api/v3/workspace/WORKSPACE_ID/search/time_entries"
-d '{ "start_date": "2025-01-01", "end_date": "2025-01-31", "page_size": 50, "first_row_number": 0 }' | jq

Weekly report

curl -s -X POST -u "$TOGGL_API_TOKEN:api_token"
-H "Content-Type: application/json"
"https://api.track.toggl.com/reports/api/v3/workspace/WORKSPACE_ID/weekly/time_entries"
-d '{ "start_date": "2025-01-06", "end_date": "2025-01-12", "grouping": "projects" }' | jq

Python - Reports:

class TogglReports: """Toggl Reports API client."""

REPORTS_URL = "https://api.track.toggl.com/reports/api/v3"

def __init__(self, api_token=None):
    self.api_token = api_token or os.environ.get("TOGGL_API_TOKEN")
    self.auth = (self.api_token, "api_token")

def _request(self, method, endpoint, data=None):
    """Make API request."""
    url = f"{self.REPORTS_URL}{endpoint}"
    response = requests.request(
        method,
        url,
        auth=self.auth,
        json=data,
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response.json()

def summary_report(
    self,
    workspace_id,
    start_date,
    end_date,
    grouping="projects",
    sub_grouping=None,
    project_ids=None,
    user_ids=None
):
    """Generate summary report."""
    data = {
        "start_date": start_date.strftime("%Y-%m-%d"),
        "end_date": end_date.strftime("%Y-%m-%d"),
        "grouping": grouping
    }

    if sub_grouping:
        data["sub_grouping"] = sub_grouping
    if project_ids:
        data["project_ids"] = project_ids
    if user_ids:
        data["user_ids"] = user_ids

    return self._request(
        "POST",
        f"/workspace/{workspace_id}/summary/time_entries",
        data
    )

def detailed_report(
    self,
    workspace_id,
    start_date,
    end_date,
    page_size=50,
    first_row=0
):
    """Generate detailed report with pagination."""
    data = {
        "start_date": start_date.strftime("%Y-%m-%d"),
        "end_date": end_date.strftime("%Y-%m-%d"),
        "page_size": page_size,
        "first_row_number": first_row
    }

    return self._request(
        "POST",
        f"/workspace/{workspace_id}/search/time_entries",
        data
    )

def weekly_report(
    self,
    workspace_id,
    start_date,
    end_date,
    grouping="projects"
):
    """Generate weekly report."""
    data = {
        "start_date": start_date.strftime("%Y-%m-%d"),
        "end_date": end_date.strftime("%Y-%m-%d"),
        "grouping": grouping
    }

    return self._request(
        "POST",
        f"/workspace/{workspace_id}/weekly/time_entries",
        data
    )

Example usage

if name == "main": reports = TogglReports() workspace_id = 12345678

# Get summary report
start = datetime(2025, 1, 1)
end = datetime(2025, 1, 31)

summary = reports.summary_report(
    workspace_id=workspace_id,
    start_date=start,
    end_date=end,
    grouping="projects"
)

print("Summary Report:")
for group in summary.get("groups", []):
    total_hours = group.get("seconds", 0) / 3600
    print(f"  {group.get('id')}: {total_hours:.1f} hours")

4. RescueTime - Data Retrieval

REST API - Analytics:

Get summary data

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "perspective=rank"
-d "resolution_time=day"
-d "restrict_kind=overview" | jq

Get data for specific date range

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "restrict_begin=2025-01-01"
-d "restrict_end=2025-01-31"
-d "restrict_kind=overview" | jq

Get activity data by category

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "restrict_kind=category"
-d "resolution_time=week" | jq

Get productivity data

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "restrict_kind=productivity" | jq

Get activity details

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "restrict_kind=activity"
-d "resolution_time=day" | jq

Get efficiency data (requires Premium)

curl -s "https://www.rescuetime.com/anapi/data"
-d "key=$RESCUETIME_API_KEY"
-d "format=json"
-d "restrict_kind=efficiency" | jq

Python - RescueTime Client:

import requests from datetime import datetime, timedelta import os

class RescueTimeClient: """RescueTime API client."""

BASE_URL = "https://www.rescuetime.com/anapi"

def __init__(self, api_key=None):
    self.api_key = api_key or os.environ.get("RESCUETIME_API_KEY")

def _request(self, endpoint, params=None):
    """Make API request."""
    params = params or {}
    params["key"] = self.api_key
    params["format"] = "json"

    response = requests.get(
        f"{self.BASE_URL}/{endpoint}",
        params=params
    )
    response.raise_for_status()
    return response.json()

def get_daily_summary(self, date=None):
    """Get daily summary data."""
    params = {
        "perspective": "rank",
        "resolution_time": "day",
        "restrict_kind": "overview"
    }

    if date:
        params["restrict_begin"] = date.strftime("%Y-%m-%d")
        params["restrict_end"] = date.strftime("%Y-%m-%d")

    return self._request("data", params)

def get_date_range_data(
    self,
    start_date,
    end_date,
    restrict_kind="overview",
    resolution="day"
):
    """Get data for date range."""
    params = {
        "restrict_begin": start_date.strftime("%Y-%m-%d"),
        "restrict_end": end_date.strftime("%Y-%m-%d"),
        "restrict_kind": restrict_kind,
        "resolution_time": resolution
    }

    return self._request("data", params)

def get_productivity_data(self, start_date=None, end_date=None):
    """Get productivity scores."""
    params = {"restrict_kind": "productivity"}

    if start_date:
        params["restrict_begin"] = start_date.strftime("%Y-%m-%d")
    if end_date:
        params["restrict_end"] = end_date.strftime("%Y-%m-%d")

    return self._request("data", params)

def get_category_data(
    self,
    start_date=None,
    end_date=None,
    resolution="day"
):
    """Get data grouped by category."""
    params = {
        "restrict_kind": "category",
        "resolution_time": resolution
    }

    if start_date:
        params["restrict_begin"] = start_date.strftime("%Y-%m-%d")
    if end_date:
        params["restrict_end"] = end_date.strftime("%Y-%m-%d")

    return self._request("data", params)

def get_activity_data(
    self,
    start_date=None,
    end_date=None,
    resolution="day"
):
    """Get detailed activity data."""
    params = {
        "restrict_kind": "activity",
        "resolution_time": resolution
    }

    if start_date:
        params["restrict_begin"] = start_date.strftime("%Y-%m-%d")
    if end_date:
        params["restrict_end"] = end_date.strftime("%Y-%m-%d")

    return self._request("data", params)

def get_focus_time(self, start_date=None, end_date=None):
    """Calculate focus time from activities."""
    productivity = self.get_productivity_data(start_date, end_date)

    focus_time = 0
    total_time = 0

    for row in productivity.get("rows", []):
        # row format: [rank, time_seconds, productivity]
        time_seconds = row[1]
        productivity_score = row[2]  # -2 to 2

        total_time += time_seconds
        if productivity_score >= 1:  # Productive or very productive
            focus_time += time_seconds

    return {
        "focus_time_seconds": focus_time,
        "focus_time_hours": focus_time / 3600,
        "total_time_seconds": total_time,
        "total_time_hours": total_time / 3600,
        "focus_percentage": (focus_time / total_time * 100) if total_time > 0 else 0
    }

Example usage

if name == "main": client = RescueTimeClient()

# Get today's summary
today = client.get_daily_summary()
print("Today's Activity:")
for row in today.get("rows", [])[:5]:
    print(f"  {row[3]}: {row[1] / 60:.0f} minutes")

# Get focus time for last week
start = datetime.now() - timedelta(days=7)
end = datetime.now()

focus = client.get_focus_time(start, end)
print(f"\nFocus Time (Last 7 Days):")
print(f"  Focus: {focus['focus_time_hours']:.1f} hours")
print(f"  Total: {focus['total_time_hours']:.1f} hours")
print(f"  Focus Rate: {focus['focus_percentage']:.1f}%")

5. RescueTime - FocusTime Triggers

FocusTime API:

Start FocusTime session (requires Premium)

curl -s -X POST "https://www.rescuetime.com/anapi/focustime/start"
-d "key=$RESCUETIME_API_KEY"
-d "duration=60" | jq # 60 minutes

End FocusTime session

curl -s -X POST "https://www.rescuetime.com/anapi/focustime/end"
-d "key=$RESCUETIME_API_KEY" | jq

Get current FocusTime status

curl -s "https://www.rescuetime.com/anapi/focustime/status"
-d "key=$RESCUETIME_API_KEY" | jq

Python - FocusTime:

class RescueTimeFocusTime: """RescueTime FocusTime API client (Premium required)."""

BASE_URL = "https://www.rescuetime.com/anapi/focustime"

def __init__(self, api_key=None):
    self.api_key = api_key or os.environ.get("RESCUETIME_API_KEY")

def start_focus(self, duration_minutes=60):
    """Start a FocusTime session."""
    response = requests.post(
        f"{self.BASE_URL}/start",
        data={
            "key": self.api_key,
            "duration": duration_minutes
        }
    )
    return response.json()

def end_focus(self):
    """End current FocusTime session."""
    response = requests.post(
        f"{self.BASE_URL}/end",
        data={"key": self.api_key}
    )
    return response.json()

def get_status(self):
    """Get current FocusTime status."""
    response = requests.get(
        f"{self.BASE_URL}/status",
        params={"key": self.api_key}
    )
    return response.json()

6. Automated Time Logging

Log Time from Scripts:

#!/usr/bin/env python3 """auto_time_logger.py - Automated time logging"""

import os import subprocess from datetime import datetime, timedelta from toggl_client import TogglClient # From earlier example

def log_git_commit_time(repo_path, workspace_id, project_id=None): """ Log time entries based on git commits. Estimates time between commits. """ client = TogglClient()

# Get recent commits
result = subprocess.run(
    ["git", "-C", repo_path, "log", "--format=%H|%s|%ai", "-n", "20"],
    capture_output=True,
    text=True
)

commits = []
for line in result.stdout.strip().split("\n"):
    if "|" in line:
        hash_val, message, date_str = line.split("|", 2)
        commit_date = datetime.strptime(
            date_str.strip()[:19],
            "%Y-%m-%d %H:%M:%S"
        )
        commits.append({
            "hash": hash_val,
            "message": message,
            "date": commit_date
        })

# Create time entries between commits
for i in range(len(commits) - 1):
    current = commits[i]
    previous = commits[i + 1]

    duration = (current["date"] - previous["date"]).total_seconds()

    # Cap at 4 hours max
    duration = min(duration, 4 * 3600)

    # Skip very short intervals
    if duration < 300:  # Less than 5 minutes
        continue

    entry = client.create_time_entry(
        workspace_id=workspace_id,
        description=f"Git: {current['message'][:100]}",
        start_time=previous["date"],
        duration_seconds=int(duration),
        project_id=project_id,
        tags=["git", "auto-logged"]
    )

    print(f"Logged: {current['message'][:50]}... ({duration/3600:.1f}h)")

def log_pomodoro_session( workspace_id, description, project_id=None, duration_minutes=25 ): """Log a completed Pomodoro session.""" client = TogglClient()

end_time = datetime.utcnow()
start_time = end_time - timedelta(minutes=duration_minutes)

entry = client.create_time_entry(
    workspace_id=workspace_id,
    description=description,
    start_time=start_time,
    duration_seconds=duration_minutes * 60,
    project_id=project_id,
    tags=["pomodoro"]
)

print(f"Logged Pomodoro: {description} ({duration_minutes} min)")
return entry

def log_meeting( workspace_id, title, start_time, end_time, project_id=None ): """Log a meeting time entry.""" client = TogglClient()

duration = (end_time - start_time).total_seconds()

entry = client.create_time_entry(
    workspace_id=workspace_id,
    description=f"Meeting: {title}",
    start_time=start_time,
    duration_seconds=int(duration),
    project_id=project_id,
    tags=["meeting"]
)

print(f"Logged meeting: {title} ({duration/3600:.1f}h)")
return entry

Complete Examples

Example 1: Weekly Time Report Generator

#!/usr/bin/env python3 """weekly_report.py - Generate weekly time reports"""

import os from datetime import datetime, timedelta from collections import defaultdict import json

Assuming TogglClient and TogglReports from earlier examples

class WeeklyReportGenerator: """Generate comprehensive weekly time reports."""

def __init__(self, toggl_token=None, rescuetime_key=None):
    self.toggl = TogglClient(toggl_token)
    if rescuetime_key:
        self.rescuetime = RescueTimeClient(rescuetime_key)
    else:
        self.rescuetime = None

def get_week_dates(self, weeks_ago=0):
    """Get start and end of a week."""
    today = datetime.now().date()
    start = today - timedelta(days=today.weekday() + (7 * weeks_ago))
    end = start + timedelta(days=6)
    return datetime.combine(start, datetime.min.time()), \
           datetime.combine(end, datetime.max.time())

def generate_toggl_report(self, start_date, end_date):
    """Generate Toggl time report."""
    entries = self.toggl.get_time_entries(start_date, end_date)

    # Aggregate by project and day
    by_project = defaultdict(float)
    by_day = defaultdict(float)
    by_tag = defaultdict(float)
    total_seconds = 0

    for entry in entries or []:
        duration = entry.get("duration", 0)
        if duration < 0:  # Running entry
            duration = (datetime.utcnow() - datetime.fromisoformat(
                entry["start"].replace("Z", "")
            )).total_seconds()

        total_seconds += duration

        # By project
        project_id = entry.get("project_id", "No Project")
        by_project[project_id] += duration

        # By day
        day = entry["start"][:10]
        by_day[day] += duration

        # By tags
        for tag in entry.get("tags", []):
            by_tag[tag] += duration

    return {
        "total_hours": total_seconds / 3600,
        "by_project": {k: v / 3600 for k, v in by_project.items()},
        "by_day": {k: v / 3600 for k, v in sorted(by_day.items())},
        "by_tag": {k: v / 3600 for k, v in by_tag.items()},
        "entry_count": len(entries or [])
    }

def generate_rescuetime_report(self, start_date, end_date):
    """Generate RescueTime productivity report."""
    if not self.rescuetime:
        return None

    # Get category data
    category_data = self.rescuetime.get_category_data(start_date, end_date)

    categories = defaultdict(float)
    for row in category_data.get("rows", []):
        category = row[3]  # Category name
        seconds = row[1]
        categories[category] += seconds / 3600

    # Get productivity data
    focus = self.rescuetime.get_focus_time(start_date, end_date)

    return {
        "focus_hours": focus["focus_time_hours"],
        "total_hours": focus["total_time_hours"],
        "focus_percentage": focus["focus_percentage"],
        "by_category": dict(sorted(
            categories.items(),
            key=lambda x: x[1],
            reverse=True
        )[:10])
    }

def generate_full_report(self, weeks_ago=0):
    """Generate complete weekly report."""
    start, end = self.get_week_dates(weeks_ago)

    report = {
        "period": {
            "start": start.strftime("%Y-%m-%d"),
            "end": end.strftime("%Y-%m-%d"),
            "week_number": start.isocalendar()[1]
        },
        "generated_at": datetime.now().isoformat(),
        "toggl": self.generate_toggl_report(start, end),
        "rescuetime": self.generate_rescuetime_report(start, end)
    }

    return report

def generate_markdown_report(self, report):
    """Generate markdown formatted report."""
    period = report["period"]

    md = f"""# Weekly Time Report

Week {period['week_number']}: {period['start']} to {period['end']} Generated: {report['generated_at'][:10]}

Tracked Time (Toggl)

MetricValue
Total Hours{report['toggl']['total_hours']:.1f}
Time Entries{report['toggl']['entry_count']}

Hours by Day

DayHours
"""
    for day, hours in report["toggl"]["by_day"].items():
        md += f"| {day} | {hours:.1f} |\n"

    md += "\n### Hours by Project\n\n"
    for project, hours in sorted(
        report["toggl"]["by_project"].items(),
        key=lambda x: x[1],
        reverse=True
    )[:5]:
        md += f"- **{project}**: {hours:.1f} hours\n"

    if report["rescuetime"]:
        rt = report["rescuetime"]
        md += f"""

Productivity (RescueTime)

MetricValue
Focus Time{rt['focus_hours']:.1f} hours
Total Tracked{rt['total_hours']:.1f} hours
Focus Rate{rt['focus_percentage']:.1f}%

Top Categories

""" for category, hours in list(rt["by_category"].items())[:5]: md += f"- {category}: {hours:.1f} hours\n"

    return md

Example usage

if name == "main": generator = WeeklyReportGenerator( toggl_token=os.environ.get("TOGGL_API_TOKEN"), rescuetime_key=os.environ.get("RESCUETIME_API_KEY") )

# Generate report for current week
report = generator.generate_full_report(weeks_ago=0)

# Save JSON
with open("weekly_report.json", "w") as f:
    json.dump(report, f, indent=2)

# Generate markdown
md_report = generator.generate_markdown_report(report)
with open("weekly_report.md", "w") as f:
    f.write(md_report)

print("Weekly report generated!")
print(f"Total tracked: {report['toggl']['total_hours']:.1f} hours")

Example 2: Project Time Attribution

#!/usr/bin/env python3 """project_attribution.py - Attribute time to projects automatically"""

import os import re from datetime import datetime, timedelta from collections import defaultdict

class ProjectTimeAttributor: """Automatically attribute time entries to projects."""

def __init__(self, toggl_token):
    self.toggl = TogglClient(toggl_token)
    self.workspace_id = None
    self.projects = {}
    self.rules = []

def load_workspace(self):
    """Load workspace and projects."""
    workspaces = self.toggl.get_workspaces()
    self.workspace_id = workspaces[0]["id"]

    projects = self.toggl.get_projects(self.workspace_id)
    self.projects = {p["name"]: p["id"] for p in projects}

    print(f"Loaded {len(self.projects)} projects")

def add_rule(self, pattern, project_name, tags=None):
    """
    Add attribution rule.

    Args:
        pattern: Regex pattern to match description
        project_name: Project to assign
        tags: Optional tags to add
    """
    if project_name not in self.projects:
        print(f"Warning: Project '{project_name}' not found")
        return

    self.rules.append({
        "pattern": re.compile(pattern, re.IGNORECASE),
        "project_id": self.projects[project_name],
        "project_name": project_name,
        "tags": tags or []
    })

def process_entries(self, days_back=7, dry_run=True):
    """
    Process time entries and apply attribution rules.

    Args:
        days_back: Number of days to process
        dry_run: If True, don't make changes

    Returns:
        Summary of changes
    """
    start = datetime.utcnow() - timedelta(days=days_back)
    end = datetime.utcnow()

    entries = self.toggl.get_time_entries(start, end)

    changes = {
        "processed": 0,
        "attributed": 0,
        "skipped": 0,
        "details": []
    }

    for entry in entries or []:
        changes["processed"] += 1

        # Skip if already has project
        if entry.get("project_id"):
            changes["skipped"] += 1
            continue

        description = entry.get("description", "")

        # Try each rule
        for rule in self.rules:
            if rule["pattern"].search(description):
                change = {
                    "entry_id": entry["id"],
                    "description": description[:50],
                    "project": rule["project_name"],
                    "tags": rule["tags"]
                }

                if not dry_run:
                    update_data = {"project_id": rule["project_id"]}
                    if rule["tags"]:
                        existing_tags = entry.get("tags", [])
                        update_data["tags"] = list(set(existing_tags + rule["tags"]))

                    self.toggl.update_time_entry(
                        self.workspace_id,
                        entry["id"],
                        **update_data
                    )

                changes["attributed"] += 1
                changes["details"].append(change)
                break

    return changes

Example usage

if name == "main": attributor = ProjectTimeAttributor(os.environ["TOGGL_API_TOKEN"]) attributor.load_workspace()

# Define attribution rules
attributor.add_rule(
    r"(meeting|standup|sync)",
    "Internal - Meetings",
    tags=["meeting"]
)

attributor.add_rule(
    r"(code review|pr review|review)",
    "Development",
    tags=["review"]
)

attributor.add_rule(
    r"(bug|fix|hotfix)",
    "Development",
    tags=["bugfix"]
)

attributor.add_rule(
    r"(docs|documentation|readme)",
    "Documentation",
    tags=["docs"]
)

attributor.add_rule(
    r"(email|slack|communication)",
    "Internal - Communication",
    tags=["communication"]
)

# Process entries (dry run first)
print("\n=== DRY RUN ===")
changes = attributor.process_entries(days_back=7, dry_run=True)

print(f"Processed: {changes['processed']}")
print(f"Would attribute: {changes['attributed']}")
print(f"Already assigned: {changes['skipped']}")

if changes["details"]:
    print("\nChanges:")
    for change in changes["details"][:10]:
        print(f"  {change['description'][:30]}... -> {change['project']}")

# Uncomment to apply changes
# print("\n=== APPLYING CHANGES ===")
# changes = attributor.process_entries(days_back=7, dry_run=False)

Example 3: Productivity Dashboard Data

#!/usr/bin/env python3 """productivity_dashboard.py - Generate dashboard data"""

import os from datetime import datetime, timedelta import json

class ProductivityDashboard: """Generate data for productivity dashboard."""

def __init__(self, toggl_token, rescuetime_key=None):
    self.toggl = TogglClient(toggl_token)
    self.rescuetime = RescueTimeClient(rescuetime_key) if rescuetime_key else None

def get_daily_metrics(self, date=None):
    """Get metrics for a single day."""
    if date is None:
        date = datetime.now().date()

    start = datetime.combine(date, datetime.min.time())
    end = datetime.combine(date, datetime.max.time())

    metrics = {
        "date": date.isoformat(),
        "toggl": {},
        "rescuetime": {}
    }

    # Toggl metrics
    entries = self.toggl.get_time_entries(start, end) or []
    total_seconds = sum(
        e.get("duration", 0) for e in entries
        if e.get("duration", 0) > 0
    )

    metrics["toggl"] = {
        "total_hours": total_seconds / 3600,
        "entry_count": len(entries),
        "billable_hours": sum(
            e.get("duration", 0) / 3600
            for e in entries
            if e.get("billable") and e.get("duration", 0) > 0
        )
    }

    # RescueTime metrics
    if self.rescuetime:
        focus = self.rescuetime.get_focus_time(
            datetime.combine(date, datetime.min.time()),
            datetime.combine(date, datetime.max.time())
        )

        metrics["rescuetime"] = {
            "focus_hours": focus["focus_time_hours"],
            "total_hours": focus["total_time_hours"],
            "focus_rate": focus["focus_percentage"]
        }

    return metrics

def get_weekly_trend(self, weeks=4):
    """Get weekly trend data."""
    trends = []
    today = datetime.now().date()

    for week_offset in range(weeks):
        week_start = today - timedelta(
            days=today.weekday() + (7 * week_offset)
        )
        week_end = week_start + timedelta(days=6)

        start_dt = datetime.combine(week_start, datetime.min.time())
        end_dt = datetime.combine(week_end, datetime.max.time())

        # Toggl data
        entries = self.toggl.get_time_entries(start_dt, end_dt) or []
        total_hours = sum(
            e.get("duration", 0) / 3600
            for e in entries
            if e.get("duration", 0) > 0
        )

        week_data = {
            "week_start": week_start.isoformat(),
            "week_number": week_start.isocalendar()[1],
            "toggl_hours": round(total_hours, 1)
        }

        # RescueTime data
        if self.rescuetime:
            focus = self.rescuetime.get_focus_time(start_dt, end_dt)
            week_data["focus_hours"] = round(focus["focus_time_hours"], 1)
            week_data["focus_rate"] = round(focus["focus_percentage"], 1)

        trends.append(week_data)

    return list(reversed(trends))

def get_project_breakdown(self, days=30):
    """Get time breakdown by project."""
    start = datetime.utcnow() - timedelta(days=days)
    end = datetime.utcnow()

    entries = self.toggl.get_time_entries(start, end) or []

    by_project = defaultdict(float)
    for entry in entries:
        duration = entry.get("duration", 0)
        if duration > 0:
            project = entry.get("project_id", "No Project")
            by_project[project] += duration / 3600

    # Sort by hours
    sorted_projects = sorted(
        by_project.items(),
        key=lambda x: x[1],
        reverse=True
    )

    return [
        {"project_id": p, "hours": round(h, 1)}
        for p, h in sorted_projects
    ]

def generate_dashboard_data(self):
    """Generate complete dashboard data."""
    today = datetime.now().date()

    return {
        "generated_at": datetime.now().isoformat(),
        "today": self.get_daily_metrics(today),
        "yesterday": self.get_daily_metrics(today - timedelta(days=1)),
        "weekly_trend": self.get_weekly_trend(weeks=4),
        "project_breakdown": self.get_project_breakdown(days=30)
    }

Example usage

if name == "main": dashboard = ProductivityDashboard( toggl_token=os.environ["TOGGL_API_TOKEN"], rescuetime_key=os.environ.get("RESCUETIME_API_KEY") )

data = dashboard.generate_dashboard_data()

# Save dashboard data
with open("dashboard_data.json", "w") as f:
    json.dump(data, f, indent=2)

# Print summary
print("Dashboard Data Generated")
print(f"\nToday: {data['today']['toggl']['total_hours']:.1f} hours tracked")

if data['today'].get('rescuetime'):
    print(f"Focus rate: {data['today']['rescuetime']['focus_rate']:.1f}%")

print(f"\nWeekly Trend:")
for week in data['weekly_trend']:
    print(f"  Week {week['week_number']}: {week['toggl_hours']} hours")

Integration Examples

Time Tracking with GitHub Actions

.github/workflows/log-time.yml

name: Log Development Time

on: push: branches: [main, develop]

jobs: log-time: runs-on: ubuntu-latest steps: - name: Log commit time to Toggl env: TOGGL_API_TOKEN: ${{ secrets.TOGGL_API_TOKEN }} TOGGL_WORKSPACE_ID: ${{ secrets.TOGGL_WORKSPACE_ID }} TOGGL_PROJECT_ID: ${{ secrets.TOGGL_PROJECT_ID }} run: | # Log 30 minute entry for each commit DESCRIPTION="Commit: ${{ github.event.head_commit.message }}" START_TIME=$(date -u -d '30 minutes ago' +%Y-%m-%dT%H:%M:%S.000Z)

      curl -s -X POST -u "$TOGGL_API_TOKEN:api_token" \
        -H "Content-Type: application/json" \
        "https://api.track.toggl.com/api/v9/workspaces/$TOGGL_WORKSPACE_ID/time_entries" \
        -d '{
          "created_with": "github_actions",
          "description": "'"${DESCRIPTION:0:100}"'",
          "workspace_id": '"$TOGGL_WORKSPACE_ID"',
          "project_id": '"$TOGGL_PROJECT_ID"',
          "start": "'"$START_TIME"'",
          "duration": 1800,
          "tags": ["github", "automated"]
        }'

Slack Daily Summary

#!/usr/bin/env python3 """slack_time_summary.py - Post daily time summary to Slack"""

import os import requests from datetime import datetime, timedelta

def post_daily_summary(toggl_token, slack_webhook): """Post daily time summary to Slack.""" toggl = TogglClient(toggl_token)

today = datetime.now().date()
start = datetime.combine(today, datetime.min.time())
end = datetime.combine(today, datetime.max.time())

entries = toggl.get_time_entries(start, end) or []

total_hours = sum(
    e.get("duration", 0) / 3600
    for e in entries
    if e.get("duration", 0) > 0
)

# Build message
message = {
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": f"Daily Time Summary - {today}"
            }
        },
        {
            "type": "section",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": f"*Total Hours:*\n{total_hours:.1f}"
                },
                {
                    "type": "mrkdwn",
                    "text": f"*Entries:*\n{len(entries)}"
                }
            ]
        }
    ]
}

# Add top activities
if entries:
    activities = "\n".join([
        f"- {e.get('description', 'No description')[:40]}: "
        f"{e.get('duration', 0)/3600:.1f}h"
        for e in sorted(entries, key=lambda x: x.get("duration", 0), reverse=True)[:5]
    ])

    message["blocks"].append({
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": f"*Top Activities:*\n{activities}"
        }
    })

# Post to Slack
response = requests.post(slack_webhook, json=message)
return response.status_code == 200

if name == "main": success = post_daily_summary( toggl_token=os.environ["TOGGL_API_TOKEN"], slack_webhook=os.environ["SLACK_WEBHOOK_URL"] ) print("Posted to Slack" if success else "Failed to post")

Best Practices

  1. Handle Rate Limits

import time from functools import wraps

def rate_limited(max_per_second=1): """Rate limiting decorator.""" min_interval = 1.0 / max_per_second last_call = [0.0]

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        elapsed = time.time() - last_call[0]
        if elapsed < min_interval:
            time.sleep(min_interval - elapsed)
        result = func(*args, **kwargs)
        last_call[0] = time.time()
        return result
    return wrapper
return decorator

2. Cache API Responses

from functools import lru_cache from datetime import datetime

@lru_cache(maxsize=100) def get_projects_cached(workspace_id, cache_key=None): """Get projects with caching.""" return toggl.get_projects(workspace_id)

Invalidate cache daily

cache_key = datetime.now().strftime("%Y-%m-%d") projects = get_projects_cached(workspace_id, cache_key)

  1. Validate Time Entries

def validate_time_entry(entry): """Validate time entry before creating.""" if not entry.get("description"): raise ValueError("Description is required")

duration = entry.get("duration", 0)
if duration > 12 * 3600:  # More than 12 hours
    raise ValueError("Duration exceeds 12 hours")

if duration < 60:  # Less than 1 minute
    raise ValueError("Duration too short")

return True

Troubleshooting

Common Issues

Issue: 403 Forbidden (Toggl)

Check API token

curl -u "YOUR_TOKEN:api_token" "https://api.track.toggl.com/api/v9/me"

Ensure token has proper permissions

Regenerate token at: https://track.toggl.com/profile

Issue: Empty response (RescueTime)

Check if data exists for date range

RescueTime only has data when the client is running

Verify API key

curl "https://www.rescuetime.com/anapi/data?key=YOUR_KEY&format=json"

Issue: Time zone issues

Always use UTC for Toggl API

from datetime import datetime, timezone

start = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")

Issue: Duration calculation

Running entries have negative duration

entry = toggl.get_current_entry() if entry and entry.get("duration", 0) < 0: # Calculate actual duration start = datetime.fromisoformat(entry["start"].replace("Z", "+00:00")) duration = (datetime.now(timezone.utc) - start).total_seconds()

Version History

  • 1.0.0 (2026-01-17): Initial release

  • Toggl Track API integration

  • RescueTime API integration

  • Time entry automation

  • Reports and analytics

  • Project attribution

  • Weekly report generator

  • Productivity dashboard

  • GitHub Actions integration

  • Slack integration

Resources

Automate your time tracking workflows with Toggl and RescueTime APIs!

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

echarts

No summary provided by upstream source.

Repository SourceNeeds Review
General

pandoc

No summary provided by upstream source.

Repository SourceNeeds Review
General

mkdocs

No summary provided by upstream source.

Repository SourceNeeds Review
General

gis

No summary provided by upstream source.

Repository SourceNeeds Review