Fill Intervals Online time entries from daily notes with GitHub and Outlook calendar correlation. Use when asked to fill time entries, timesheets, or submit hours to Intervals. Requires chrome-devtools MCP with browser open to Intervals.
Resources
2Install
npx skillscat add olivoil/om-skills/intervals-time-entry Install via the SkillsCat registry.
Intervals Time Entry Automation
Fill time entries in Intervals Online from Obsidian daily notes using MCP chrome-devtools, with GitHub activity and Outlook calendar correlation.
Prerequisites
- Chrome/Chromium running with
--remote-debugging-port=9222 - Intervals page open:
https://bhi.intervalsonline.com/time/multiple/ - chrome-devtools MCP server configured
Cache Location
IMPORTANT: Cached files are stored in the PROJECT, not the plugin:
<project-root>/.claude/intervals-cache/
├── project-mappings.md # Project→workType mappings
├── github-mappings.md # Repo→project mappings
├── outlook-mappings.md # Calendar→project mappings
└── fetch-github-activity.sh # GitHub activity fetcher scriptThese files persist between sessions. If they don't exist, create them from the plugin's references/ and scripts/ directories.
Workflow
Phase 1: Read Notes
Read the daily note for the requested date. Default location: 📅 Daily Notes/YYYY-MM-DD.md
Look for:
- Time entries with project/work descriptions
- Links to GitHub PRs or repos (e.g.,
https://github.com/owner/repo/pull/123) - Mentions of PR numbers (e.g., "PR #123", "reviewed PR 456")
Phase 1.5: GitHub Activity Correlation (REQUIRED)
ALWAYS run this phase to fetch GitHub activity and enhance time entry descriptions.
Step 1: Ensure Script Is Up-to-Date
The script has a version number on line 2 (e.g., # Version: 2). Check and update if needed:
- Read the plugin script:
~/.claude/skills/intervals-time-entry/scripts/fetch-github-activity.sh - Extract the version number from line 2
- If
.claude/intervals-cache/fetch-github-activity.shexists, extract its version number - If the cached script doesn't exist OR the plugin version is higher, copy the plugin script to
.claude/intervals-cache/fetch-github-activity.sh
This ensures users always have the latest script with bug fixes and new features.
Step 2: Fetch Activity
Run this command (replace YYYY-MM-DD with the target date):
bash .claude/intervals-cache/fetch-github-activity.sh YYYY-MM-DDThis returns JSON with:
- PRs authored (created or updated)
- PRs reviewed
- Events with timestamps (commits, reviews, comments)
IMPORTANT: The script output already includes PR titles and body snippets in prs_authored, prs_active, and prs_reviewed. Do NOT make separate gh pr view calls — use the data already returned by the script.
Step 3: Correlate with Notes
Using the JSON output from Step 2:
- Match PRs to time entries: If notes mention a PR or repo, link it to that entry
- Infer repo→project mappings: When a PR clearly matches a time entry's project, add to
.claude/intervals-cache/github-mappings.md - Extract PR links from notes: Look for GitHub URLs and extract repo/PR info
Step 4: Enhance Descriptions
ALWAYS improve descriptions when GitHub data provides more context. The goal is to make time entries self-documenting and meaningful for future reference.
Before/After Examples:
| Notes say | GitHub shows | Final description |
|---|---|---|
| "font awesome icon PR" | PR #574: "Add FontAwesome Pro icons to design system" | Add FontAwesome Pro icons to design system (PR #574) |
| "review and merge PRs" | Reviewed PR #580, #581, #583 | Code review: notification preferences (#580), cart validation (#581), search filters (#583) |
| "text-transform work" | PR #579: "Add text-transform utilities to typography tokens" | Add text-transform utilities to typography design tokens (PR #579) |
| "bug fixes" | PR #602: "Fix race condition in checkout flow" | Fix race condition in checkout flow (PR #602) |
| "API work" | Commits: "Add pagination to /users endpoint", "Handle empty results" | Add pagination to /users endpoint with empty result handling |
Rules:
- Use the PR title as the primary description when available (it's usually well-written)
- Use the PR description/body for additional context when the title alone is too brief or generic
- Add PR number in parentheses at the end:
(PR #123) - For reviews, briefly describe each PR reviewed (2-5 words each)
- For commits without PRs, summarize the commit messages
- Keep to 1-2 sentences max, but make them specific and meaningful
- Never use generic descriptions like "development work" when GitHub has specifics
Step 5: Suggest Adjustments
Compare GitHub activity to notes and flag potential issues:
Missing time entries: If GitHub shows significant activity (multiple commits, PRs) for a repo but notes have no corresponding entry, suggest adding one.
Time discrepancies: Use commit timestamps to estimate minimum time spent:
- Calculate span from first to last commit on a repo
- Account for gaps >2h as breaks
- If notes show significantly less time than commits suggest, flag for review
Example output:
⚠️ GitHub shows commits on technomic-api from 9:15am to 3:30pm (~4-5h with breaks)
but notes only show 2h for Technomic dev work. Consider adjusting.
💡 Found PR #456 "Fix payment edge case" for Technomic - using for description.
📝 No time entry found for 3 PR reviews on ewg-frontend. Suggest adding:
- EWG: 0.5-1h Architecture/Technical Design (PR reviews #12, #13, #14)Phase 1.7: Outlook Calendar Correlation (REQUIRED)
ALWAYS run this phase to visually read the Outlook calendar and cross-reference with notes and GitHub activity.
Prerequisite: User must be logged into Outlook Web in the browser (same Chrome instance with --remote-debugging-port=9222).
Step 1: Find or Open Outlook Web Tab
IMPORTANT: Never navigate away from the user's current tab. Always find an existing Outlook tab or create a new one.
- Call
list_pagesto see all open browser tabs - Look for a tab with URL containing
outlook.office.comoroutlook.live.com - If found: call
select_pagewith that page's ID - If NOT found: call
new_pagewith the day view URL (see Step 2)
Step 2: Navigate, Verify, and Extract Calendar Data
Navigate to the target date's day view:
https://outlook.office.com/calendar/view/day/YYYY/M/DNote: month and day are not zero-padded (e.g., 2026/2/16 not 2026/02/16).
- If the tab is already on Outlook but wrong date: call
navigate_pagewith the day view URL - If a new tab was created in Step 1: it will already be on the correct URL
Verify the date — immediately after navigation, call take_screenshot and confirm the calendar is showing the correct date. The date header is visible at the top of the day view.
- If the screenshot shows the wrong date: wait 3 seconds, then call
navigate_pageagain with the same URL. Take another screenshot to verify. - If still wrong after retry: try the alternative URL format with zero-padded month/day (e.g.,
2026/02/04instead of2026/2/4). Take a screenshot to verify. - Only proceed once the correct date is confirmed in the screenshot.
Extract calendar data visually from the verified screenshot:
- Meeting subjects — the text on each calendar block
- Start and end times — from the time labels on the left axis and block positions
- Duration — calculated from start/end times
- Declined events — shown with strikethrough text or dimmed/crossed-out appearance
- All-day events — shown in the top banner area (above the hourly grid)
- Attendee names — visible if shown in the calendar block
- Location — if visible in the calendar block
If the calendar is zoomed out and events are hard to read, note which events need clarification and proceed with what's visible.
CRITICAL: Use take_screenshot for visual reading — do NOT use take_snapshot, DOM inspection, or click-based navigation for calendar data extraction. The entire point of this phase is visual analysis without brittle DOM dependencies. If the screenshot is unclear, take another screenshot — never fall back to DOM snapshots or clicking calendar elements.
Step 3: Correlate with Notes and GitHub
Using the visually extracted calendar data, cross-reference with notes and GitHub data:
- Match meetings to time entries: If notes mention a meeting that appears in the calendar, link them
- Infer project from attendees: Use
people-context.mdto determine which project a meeting belongs to - Infer project from subject: Match calendar subjects against
project-mappings.mdterms - Learn calendar mappings: When a calendar event clearly maps to a project, add to
.claude/intervals-cache/outlook-mappings.md
Step 4: Detect Missing Entries
Compare calendar events against notes and flag gaps:
Missing time entries: If the calendar shows a meeting but notes have no corresponding entry, suggest adding one.
📅 Calendar shows "Technomic-EXSQ Weekly Touchbase" (11:00-12:00, 1h)
— but no matching time entry in notes.
Suggest: Ignite Application Development & Support | Meeting: Client Meeting - US | 1hDeclined events: Skip events with strikethrough/dimmed appearance (user declined). All-day events: Ignore for time entries (they're reminders, not meetings).
Step 5: Validate Durations
Compare calendar durations against note durations and flag discrepancies:
⚠️ Notes say "standup 30min" but calendar shows "Technomic Scrum" was 15min (9:00-9:15).
Suggest adjusting to 0.25h.
⚠️ Notes say "client meeting 1h" but calendar shows "Technomic-EXSQ Weekly Touchbase"
was 1.5h (11:00-12:30). Suggest adjusting to 1.5h.
✅ Notes say "EWG sync 1h" and calendar confirms "Weekly EX2 <> EWG Sync" was 1h (2:00-3:00).Rules for duration validation:
- Notes duration is the source of truth — if notes say 1.5h and calendar says 1h, trust the notes (it means more time was spent than scheduled)
- If a time entry links to a meeting note (e.g.,
[[2026-02-19 Meeting Name]]), check the meeting note for a recording duration — the time entry cannot be less than that duration - If notes duration is more than calendar, the meeting may have included prep/follow-up — keep notes as-is
- All-day events are reminders, not meetings — ignore for duration
- Events the user declined (strikethrough) should be excluded entirely
Step 6: Enhance Descriptions
Improve meeting descriptions when calendar data provides more context:
| Notes say | Calendar shows | Final description |
|---|---|---|
| "meeting" | "Technomic-EXSQ Weekly Touchbase" | Weekly touchbase with Technomic |
| "EWG sync" | "Weekly EX2 <> EWG Sync" | EWG weekly sync |
| "standup" | "Technomic Scrum" | Technomic daily scrum |
| "1:1" | "1:1 with Chris" | 1:1 with Chris |
| "client call" | "Drees Design Review" | Drees design review |
Rules:
- Use calendar subject as primary description when it's more specific than notes
- Add attendee names visible in the calendar block (especially client names from
people-context.md) - For recurring meetings, note any distinguishing details from this specific instance
- Combine with GitHub context when applicable (e.g., meeting + PR demo)
Step 7: Time Gap Analysis
Use calendar events + GitHub commits to build a picture of the full workday:
9:00-9:15 Technomic Scrum (calendar) → standup
9:15-11:00 [gap: GitHub shows 5 commits on technomic-api] → dev work
11:00-12:00 Technomic-EXSQ Touchbase (calendar) → client meeting
12:00-1:00 [no activity] → likely lunch
1:00-3:00 [gap: GitHub shows 3 commits on ewg-frontend] → dev work
3:00-3:30 Weekly EX2 <> EWG Sync (calendar) → client meeting
3:30-4:00 1:1 with Chris (calendar) → 1:1
4:00-5:00 [gap: GitHub shows 2 PR reviews] → code reviewThis gap analysis helps:
- Validate that notes account for the full workday
- Identify development blocks between meetings
- Suggest time allocations for unaccounted gaps
Phase 2: Load Mappings
- Read project cache:
.claude/intervals-cache/project-mappings.md(in the project root) - Read GitHub mappings cache:
.claude/intervals-cache/github-mappings.md(learned repo→project associations) - Read Outlook mappings cache:
.claude/intervals-cache/outlook-mappings.md(learned calendar→project associations) - Read plugin references for defaults:
references/worktype-mappings.md,references/people-context.md
If the project cache doesn't exist, create it by copying from references/project-mappings.md.
If the GitHub mappings cache doesn't exist, create it from references/github-mappings.md.
Output format: Project | Module (if applicable) | Work Type | Hours | Description
Module handling: Some projects have a Module dropdown (e.g., Optimizely CMS Decoupling). Check project-mappings.md for listed modules. When building entries:
- If the work matches a specific module, include
module: "Module Name"in the entry - If no specific module applies, omit the module field — the fill script will automatically select "No Module"
- The script detects the Module dropdown at runtime; projects without it are unaffected
Phase 3: Validate Against Cache
Check the project cache for work types:
- If all projects have cached work types → skip browser inspection
- If any project is NOT cached → inspect browser to discover its work types
Phase 4: Browser Automation
Step 1: Find or Create Intervals Tab
IMPORTANT: Never navigate away from the user's current tab. Always find an existing Intervals tab or create a new one.
- Call
list_pagesto see all open browser tabs - Look for a tab with URL containing
intervalsonline.com - If found: call
select_pagewith that page's ID - If NOT found: call
new_pagewith URLhttps://bhi.intervalsonline.com/time/multiple/ - Only call
navigate_pageif the selected tab is on Intervals but wrong URL (e.g., different week)
Step 2: Run Scripts
Use MCP chrome-devtools with these scripts from scripts/:
- Basic inspection (
scripts/inspect-basics.js): Get dates and day index - Discover work types (
scripts/discover-worktypes.js): For uncached projects only - Fill entries (
scripts/fill-entries.js): Fill all validated entries
IMPORTANT: All scripts use arrow function format for MCP compatibility:
// ✅ Correct
async () => { ... }
// ❌ Wrong - causes syntax errors
(async function() { ... })();Phase 5: UPDATE THE CACHE (Critical!)
After discovering new work types, ALWAYS update the project cache file.
If you discovered work types for a new project (e.g., "Drees Maintenance and Support"):
- Read the current cache:
.claude/intervals-cache/project-mappings.md - Add the new project section:
### Drees Maintenance and Support (20240034)
- Development - US
- Meeting: Client Meeting - US
- QA/Testing - US- Write the updated file back
Example update workflow:
1. Read .claude/intervals-cache/project-mappings.md
2. Append new section under "## Cached Work Types by Project"
3. Write updated content to .claude/intervals-cache/project-mappings.mdThis ensures future runs skip inspection for this project, saving time and tokens.
Phase 5.5: Update GitHub Mappings Cache
When you discover a new repo→project association (from PR links in notes or inferred from context):
- Read the current cache:
.claude/intervals-cache/github-mappings.md - Add the mapping to the table:
| owner/repo-name | Intervals Project Name |- Write the updated file back
This helps future correlation work more accurately by remembering which repos belong to which projects.
Phase 5.6: Update Outlook Mappings Cache
When you discover a new calendar event→project association (from subject matching, attendee inference, or user confirmation):
- Read the current cache:
.claude/intervals-cache/outlook-mappings.md - Add the mapping to the appropriate table:
For subject→project mappings:
| Calendar Subject Pattern | Intervals Project | Work Type |
|--------------------------|-------------------|-----------|
| Technomic-EXSQ Weekly Touchbase | Ignite Application Development & Support | Meeting: Client Meeting - US |For recurring meeting mappings:
| Meeting Name | Intervals Project | Work Type |
|-------------|-------------------|-----------|
| Technomic Scrum | Ignite Application Development & Support | Meeting: Internal Stand Up - US |- Write the updated file back
This helps future runs instantly map recurring meetings to the correct project and work type.
Phase 6: Verify
Take screenshot to confirm entries are correct.
Phase 7: Write Time Entry Table to Daily Note
After verification, write the finalized entries back to the Obsidian daily note as a permanent record.
Step 1: Resolve Vault Path
Read $OBSIDIAN_VAULT_PATH from the environment. The daily note is at:
$OBSIDIAN_VAULT_PATH/📅 Daily Notes/YYYY-MM-DD.mdStep 2: Read the Daily Note
Read the daily note file to find the insertion point.
Step 3: Insert or Replace the Intervals Section
Look for an existing ### Intervals section:
- If found: Replace the entire section (from
### Intervalsto the next###or---or end of file) with the updated table - If not found: Insert the section using this priority:
- Before
### Coding Sessionsif it exists - After
### Todos(before the next###or---) - At the end of the note if neither exists
- Before
Step 4: Write the Table
Format as a markdown table with a horizontal rule before it:
------
### Intervals
| Project | Hours | Description |
|---------|------:|-------------|
| Ignite Application Development & Support | 2 | Weekly touchbase with Technomic |
| EWG Feature Enhancement Addendum | 3.5 | Add pagination endpoint (PR #574) |
| **Total** | **5.5** | |- Use the same ENTRIES data that was sent to
fill-entries.js - Right-align the Hours column
- Add a bold Total row summing all hours
- The
------separator goes before### Intervalsto visually separate it from the section above
Phase 8: Insert into SQLite Database
After writing the daily note table, insert the same entries into an SQLite database for cross-day querying.
Database Location
$OBSIDIAN_VAULT_PATH/.claude/time-entries.dbSchema
Created on first use via CREATE TABLE IF NOT EXISTS:
CREATE TABLE IF NOT EXISTS time_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL, -- YYYY-MM-DD
project TEXT NOT NULL,
work_type TEXT NOT NULL,
hours REAL NOT NULL,
description TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_time_entries_unique
ON time_entries(date, project, work_type, description);Insert Entries
Use INSERT OR REPLACE to make re-runs idempotent:
INSERT OR REPLACE INTO time_entries (date, project, work_type, hours, description)
VALUES ('2026-02-04', 'Ignite Application Development & Support', 'Meeting: Client Meeting - US', 2, 'Weekly touchbase with Technomic');Run via Bash:
sqlite3 "$OBSIDIAN_VAULT_PATH/.claude/time-entries.db" <<'SQL'
CREATE TABLE IF NOT EXISTS time_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
project TEXT NOT NULL,
work_type TEXT NOT NULL,
hours REAL NOT NULL,
description TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_time_entries_unique
ON time_entries(date, project, work_type, description);
INSERT OR REPLACE INTO time_entries (date, project, work_type, hours, description)
VALUES ('YYYY-MM-DD', 'Project Name', 'Work Type', 2.0, 'Description here');
SQLRepeat the INSERT OR REPLACE for each entry in the ENTRIES array.
Quick Reference
Day Index Mapping
| Day | Index |
|---|---|
| Sunday | 0 |
| Monday | 1 |
| Tuesday | 2 |
| Wednesday | 3 |
| Thursday | 4 |
| Friday | 5 |
| Saturday | 6 |
Common Fallbacks
| Project | Missing Work Type | Use Instead |
|---|---|---|
| Meeting | Internal Working Session | Team/Company Meeting |
| EWG Feature Enhancement Addendum | Analysis - US | Development - US |
Customization
Plugin References (read-only defaults)
The references/ files in this plugin contain default mappings. Fork this repo to customize for your organization.
Project Cache (read-write, auto-updated)
The cache at .claude/intervals-cache/project-mappings.md in your project:
- Gets created automatically from plugin defaults on first run
- Gets UPDATED automatically when new projects are discovered
- Persists between sessions
- Is project-specific (each project can have its own cache)
GitHub Mappings Cache (read-write, auto-learned)
The cache at .claude/intervals-cache/github-mappings.md:
- Gets created on first use from plugin template
- Gets UPDATED when Claude discovers repo→project associations from:
- PR links in your notes (e.g.,
https://github.com/acme/widget/pull/123) - Contextual inference (PR activity matching time entry project names)
- PR links in your notes (e.g.,
- Used to correlate future GitHub activity to correct Intervals projects
Outlook Mappings Cache (read-write, auto-learned)
The cache at .claude/intervals-cache/outlook-mappings.md:
- Gets created on first use from plugin template
- Gets UPDATED when Claude discovers calendar→project associations from:
- Meeting subjects matching project names
- Attendees matching known people in
people-context.md - User confirmations during the workflow
- Used to instantly map recurring meetings to correct projects and work types
- Stores both subject-based and recurring meeting patterns
First-Time Setup
On first use in a new project, Claude will:
- Check if
.claude/intervals-cache/project-mappings.mdexists - If not, create it from the plugin's
references/project-mappings.mdtemplate - Check if
.claude/intervals-cache/github-mappings.mdexists - If not, create it from the plugin's
references/github-mappings.mdtemplate - Check if
.claude/intervals-cache/outlook-mappings.mdexists - If not, create it from the plugin's
references/outlook-mappings.mdtemplate - Check if
.claude/intervals-cache/fetch-github-activity.shexists and compare version - If missing or outdated, copy from the plugin's
scripts/fetch-github-activity.sh - Use and update these local caches going forward
Efficiency
This skill is optimized for minimal browser interaction:
- Cached mappings eliminate redundant inspection
- Auto-updating cache means you only inspect each project ONCE ever
- Single script execution fills all entries
- GitHub correlation runs once via
ghCLI, no browser needed - Outlook calendar uses a single browser screenshot — no API tokens or scripts needed
- Learned repo mappings improve correlation accuracy over time
- Learned calendar mappings improve meeting→project accuracy over time
- Cross-source validation catches discrepancies between notes, calendar, and GitHub