Reconstruct and record weekly timesheets by pulling data from Google Calendar, Slack, Granola meeting notes, and Gmail. Categorises time into client projects and internal allocations based on meeting attendees, email domains, Slack channels, and meeting titles. Use this skill whenever the user mentions timesheets, time tracking, time allocation, logging hours, recording work hours, weekly hours, billable hours, or asks "what did I work on". Also trigger when the user asks to reconstruct their week, figure out where their time went, or produce a timesheet for any period. Even if the user just says something like "I need to do my timesheets" or "log last week", use this skill.
This skill reconstructs weekly timesheets from calendar, Slack, Granola, and email data, then writes them to a markdown file with per-day hour breakdowns, category allocations, and notes.
The core workflow:
The goal is ~40 hours/week (or whatever the user confirms), with hours rounded to the nearest half hour. The skill should feel like a knowledgeable assistant reconstructing the week alongside the user, not a rigid form-filler.
Ask the user what period to cover. If they don't specify, suggest the previous Monday-Sunday work week as the default (Monday-Friday are the core work days, but Saturday and Sunday are included because work sometimes spills into the weekend), and confirm before proceeding. If they give a range spanning multiple weeks, process each week sequentially.
Use ISO week numbering for internal tracking, but display dates in human-readable format (e.g., "Week of 24-28 Mar 2026"). Weekend work hours are attributed to that week's totals.
Look for an existing timesheet markdown file in the user's workspace folder. Check for files named timesheet.md, timesheets.md, or similar. If found, read it to extract:
@airbnb.com attendees = an Airbnb project, @kcl.ac.uk = Kindred)If no file exists, tell the user you'll create one after gathering enough data to establish categories. Don't create the file until after the first week's data has been categorised and confirmed.
For each week, pull data from all available sources in parallel. Not all sources may be available; use whatever the user has connected.
Use gcal_list_events with:
timeZone: Ask the user their timezone on first run, or infer from calendar settingscondenseEventDetails: true to keep responses manageablemaxResults: 100 (increase if the user has very busy weeks)If the response is too large and gets persisted to a JSON file, parse it with a Python script. The persisted format is [{"type": "text", "text": "<JSON string>"}] at the top level.
For each event, extract:
workingLocation and focusTime events)Use slack_search_public_and_private with:
from:<@USER_ID> to find the user's messagessort: timestamp, sort_dir: ascinclude_context: true for surrounding conversationSlack messages help fill gaps: they reveal what someone was working on between meetings, show async collaboration, and surface things like "I worked late on X" or overtime indicators. They also help disambiguate meetings (e.g., a 1:1 where the Slack context reveals it was about a specific project).
Exclude personal/social messages from time allocation.
Use list_meetings with a custom date range covering the target week (Monday through Sunday). This provides meeting titles, participants with email addresses, and dates. Cross-reference with calendar events to validate attendee lists.
For meetings that are ambiguous or particularly important, use get_meetings to pull the full meeting notes. Granola notes often contain rich context about what was discussed, decisions made, and action items assigned, all of which help categorise the meeting more accurately than the title alone. Prioritise pulling detailed notes for: meetings with vague titles (e.g., "Quick sync", "Catch up"), meetings where the attendee list doesn't clearly indicate a project, and meetings that appear in multiple potential categories.
Use gmail_search_messages to search for work-related email threads during the period. Email can reveal work that doesn't show up in calendar or Slack, like vendor communications, client correspondence, or document reviews.
This is the most important and nuanced step. The goal is to assign every working hour to a category.
Use these signals in priority order:
Attendee email domains: External domains strongly indicate client work. Build a domain-to-client mapping from the existing timesheet or by asking the user. Internal company domains (e.g., @raredays.com, @coursestudio.com, or whatever the user's org uses) indicate internal time. When you see a mix of internal and external attendees, the external domain usually determines the client project. For example:
@clientcompany.com attendees = that client's projectMeeting title patterns: Many recurring meetings have clear category signals:
Slack channel context: Messages in #int-projectname or #ext-projectname channels help attribute async work.
Granola participant lists: Confirm who was actually in a meeting (calendar invites don't always reflect attendance).
Round meeting durations up to the nearest half hour. A 25-minute meeting = 0.5hrs. A 35-minute meeting = 0.5hrs. A 50-minute meeting = 1hr. A 65-minute meeting = 1.5hrs.
Calendar frequently shows overlapping events. The user can only attend one at a time. When you spot overlaps, ask the user which they attended, or if they split time between them.
Calendar meetings rarely account for a full 8-hour day. The gap between meeting hours and the daily target represents unstructured work time: deep work, code reviews, document writing, Slack discussions, email, etc.
Distribute unstructured time based on:
Two-person meetings where both attendees are from the user's company default to "Internal - 1:1". However, flag any 1:1 where Slack context suggests it was primarily about a specific project, and ask the user if it should be recategorised.
Batch your questions efficiently using the AskUserQuestion tool (up to 4 questions per batch). Typical things to ask:
On the first run with no existing mappings, also confirm the full category list with the user before writing anything.
Ask the user to confirm:
If creating a new file:
# [User's Name]'s Timesheet - [Company]
Started: [First date]
## Categories
- [Category 1]
- [Category 2] (with any clarifying notes)
- ...
## Mappings
These mappings help categorise future weeks automatically:
- @clientdomain.com = Client Project Name
- "Meeting Title Pattern" = Category
- channel:#slack-channel = Category
- person:[email protected] = Category (for people strongly associated with one project)
---
## Week N (DD-DD Mon YYYY)
| | Mon DD | Tue DD | Wed DD | Thu DD | Fri DD | Total |
|---|---|---|---|---|---|---|
| Category 1 | X | X | X | X | X | XX |
| Category 2 | X | X | X | X | X | XX |
| **Total** | **8** | **8** | **8** | **8** | **8** | **40** |
**Notes:**
- Key activities, meetings, and context for the week
- What was discussed, who was involved, any notable events
- Excluded personal items
The Mappings section is important because it persists learned associations between runs. Each time a new client domain, meeting pattern, or person-to-project association is confirmed by the user, add it to this section. On subsequent runs, read these mappings first to reduce the number of questions needed.
Write concise but informative notes that explain what the numbers represent. Include: