Skip to content

feat: WakaTime integration for IDE activity tracking#326

Merged
pedramamini merged 17 commits intoRunMaestro:mainfrom
kianhub:feature/wakatime-integration
Feb 16, 2026
Merged

feat: WakaTime integration for IDE activity tracking#326
pedramamini merged 17 commits intoRunMaestro:mainfrom
kianhub:feature/wakatime-integration

Conversation

@kianhub
Copy link
Copy Markdown
Contributor

@kianhub kianhub commented Feb 7, 2026

Summary

  • Adds WakaTime integration to track Maestro coding sessions as IDE activity in WakaTime dashboards
  • Users only need to toggle WakaTime on and enter their API key in Settings — CLI installation, version updates, and heartbeat management are fully automatic
  • Follows the official WakaTime plugin creation guide

What's included

Settings UI

  • WakaTime toggle and API key input added to the Usage & Stats section in General settings
  • Password-masked API key field with validation on blur (green checkmark / red X)
  • CLI availability check with auto-install status feedback
  • Falls back to reading API key from ~/.wakatime.cfg if not set in Maestro

Main Process

  • WakaTimeManager class (src/main/wakatime-manager.ts) handles:
    • Auto-install: Downloads platform-specific CLI binary from GitHub releases to ~/.wakatime/
    • Auto-update: Daily version check against GitHub API, re-downloads if outdated
    • Heartbeats: Sent on AI output (data events) and AI reasoning (thinking-chunk events) for interactive sessions, plus query-complete for batch/auto-run; debounced to 2 min per session
    • Entity type: Uses --entity-type app with --entity Maestro (app name, not file path) per WakaTime spec
    • Language detection: Detects project language from manifest files in the session's cwd (tsconfig.json → TypeScript, Cargo.toml → Rust, go.mod → Go, pyproject.toml → Python, etc.) — cached per session
    • Branch detection: Detects current git branch via git rev-parse --abbrev-ref HEAD and passes via --alternate-branch — cached per session
    • Plugin identification: --plugin maestro/{version} maestro-wakatime/{version} per official spec
    • Category: Uses ai coding category (dedicated WakaTime category for AI-assisted work)
  • Process listener (wakatime-listener.ts) wires heartbeats to:
    • data events — fires on every AI stdout chunk (covers interactive sessions); skips terminal sessions
    • thinking-chunk events — fires during AI extended thinking/reasoning, ensuring long reasoning periods are tracked
    • query-complete events — fires for batch/auto-run processes
    • exit events — cleans up per-session debounce, branch, and language caches
  • IPC handlers for CLI check and API key validation, using WakaTimeManager for auto-install
  • Auto-install triggered on startup (if enabled) and when user toggles WakaTime on

Tests

  • 48 WakaTime-specific tests across:
    • wakatime-manager.test.ts — heartbeats, debouncing, CLI detection, auto-install, version checking, config fallback
    • wakatime.test.ts (IPC handlers) — handler registration, CLI check, API key validation
    • wakatime-listener.test.ts — data events, thinking-chunk events, terminal session filtering, query-complete, exit cleanup
    • SettingsModal.test.tsx — CLI status UI states
    • useSettings.test.ts — settings persistence

New files

File Purpose
src/main/wakatime-manager.ts CLI management, heartbeats, language/branch detection, auto-install/update
src/main/ipc/handlers/wakatime.ts IPC handlers for renderer
src/main/preload/wakatime.ts Preload bridge (window.maestro.wakatime)
src/main/process-listeners/wakatime-listener.ts AI activity → heartbeat wiring

Key design decisions

  • --entity-type app with --entity Maestro: WakaTime expects an application name (not a file path) for the app entity type. The project directory is passed via --project instead.
  • data + thinking-chunk events: The query-complete event only fires for batch-mode processes with querySource set, which the renderer doesn't provide for interactive sessions. Listening on data and thinking-chunk ensures heartbeats fire for all AI activity including extended reasoning.
  • Project language detection: Checks for well-known manifest files (tsconfig.json, Cargo.toml, go.mod, etc.) rather than scanning all file extensions — fast, deterministic, and covers the common case.
  • 2-minute debounce: Matches WakaTime's own deduplication window. WakaTime uses a 15-minute timeout to join heartbeats into durations, so continuous activity within 2-minute intervals produces accurate time tracking.

Test plan

  • Enable WakaTime in Settings → General → Usage & Stats
  • Verify CLI auto-installs (check ~/.wakatime/ for binary)
  • Enter API key → blur field → verify validation indicator appears
  • Run a query in an interactive AI session → verify heartbeat appears in WakaTime dashboard
  • Verify WakaTime dashboard shows correct project language (not "Other")
  • Verify WakaTime dashboard shows correct git branch
  • Run an auto-run task → verify heartbeat appears in WakaTime dashboard
  • Verify npm run lint and npm run test pass

@kianhub kianhub marked this pull request as draft February 7, 2026 21:44
@kianhub kianhub marked this pull request as ready for review February 7, 2026 21:45
@kianhub kianhub force-pushed the feature/wakatime-integration branch from 70a230b to 7c6369f Compare February 9, 2026 22:42
Copy link
Copy Markdown
Collaborator

@pedramamini pedramamini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: WakaTime Integration

Solid contribution — well-structured, follows existing patterns, good test coverage (48 tests). Clean separation across manager, listener, IPC handlers, preload, and settings. A few items to address below.

Key positives:

  • Concurrent install guard, per-session debounce/caching, fire-and-forget heartbeats
  • Clean file separation following established project patterns
  • Correct --entity-type app with --entity Maestro per WakaTime spec
  • path.basename(projectDir) for project name matches VS Code behavior

Items to address:

  1. 🐛 Double handler registrationregisterWakatimeHandlers is called both in index.ts:setupIpcHandlers() and added to registerAllHandlers() in handlers/index.ts
  2. Performance on disabled pathdata event listener fires on every stdout chunk, doing work even when WakaTime is disabled (the default)
  3. 🔧 projectPath vs cwd inconsistency in listener
  4. 🧹 Cosmetic — stray double blank line in SettingsModal

See inline comments for details.

Comment thread src/main/ipc/handlers/index.ts Outdated
agentConfigsStore: deps.agentConfigsStore,
settingsStore: deps.settingsStore,
});
// Register WakaTime handlers (CLI check, API key validation)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 Double handler registrationregisterWakatimeHandlers is called here inside registerAllHandlers() AND separately in index.ts:setupIpcHandlers(). The app uses setupIpcHandlers() directly (not registerAllHandlers()), so this line is dead code today. But if registerAllHandlers is ever called, it would double-register the IPC handlers and crash.

The wakatimeManager field was also added to the HandlerDependencies interface (line 156), but nothing currently passes it — type-level landmine.

Suggested fix: Remove the registerAllHandlers addition and the HandlerDependencies change entirely, since index.ts calls registerWakatimeHandlers directly.

const projectName = projectDir ? path.basename(projectDir) : sessionId;
void wakaTimeManager.sendHeartbeat(sessionId, projectName, managedProcess.cwd);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance on the disabled path — The data event fires on every single stdout chunk from AI agents. This function runs processManager.get(), path.basename(), and then sendHeartbeat() (which hits settingsStore.get('wakatimeEnabled')) on every chunk — even when WakaTime is disabled (the default).

Since most users won't have WakaTime enabled, consider adding an early bail-out in the data listener that checks the enabled flag before doing any work. You could cache the enabled state and listen on store.onDidChange('wakatimeEnabled') to update it.

// In setupWakaTimeListener:
let enabled = settingsStore.get('wakatimeEnabled', false);
settingsStore.onDidChange('wakatimeEnabled', (v) => { enabled = !!v; });

processManager.on('data', (sessionId: string) => {
  if (!enabled) return; // fast path — no store reads, no lookups
  heartbeatForSession(processManager, wakaTimeManager, sessionId);
});

const projectDir = managedProcess.projectPath || managedProcess.cwd;
const projectName = projectDir ? path.basename(projectDir) : sessionId;
void wakaTimeManager.sendHeartbeat(sessionId, projectName, managedProcess.cwd);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 projectPath vs cwd inconsistency — This passes managedProcess.cwd as the third arg to sendHeartbeat, which is used for language detection and branch detection. But projectPath (when available) would be more accurate since cwd might be a subdirectory. You already use projectPath || cwd for the project name — the cwd param should match:

const projectDir = managedProcess.projectPath || managedProcess.cwd;
const projectName = projectDir ? path.basename(projectDir) : sessionId;
void wakaTimeManager.sendHeartbeat(sessionId, projectName, projectDir);

)}
</div>
</div>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Stray double blank line here.

response.resume();
fetchJson(response.headers.location, maxRedirects - 1).then(resolve, reject);
return;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: fs.readdirSync(cwd) on the main process for the *.csproj glob pattern. Cached per session so it only hits once, but it's a synchronous I/O call on the event loop. Fine for now since the cache mitigates it — just flagging.

kianhub and others added 17 commits February 15, 2026 23:21
Add WakaTime API key and enable/disable toggle to the Settings modal
General tab with Beta badge. Settings are persisted via electron-store
following existing patterns. Includes 5 new tests for the settings hook.
Add WakaTimeManager class that detects wakatime-cli on PATH and sends
heartbeats on query-complete events, debounced at 2-minute intervals
per session. Integrated via a dedicated wakatime-listener in the
process-listeners module, with cleanup on process exit.

Also adds wakatimeEnabled and wakatimeApiKey to MaestroSettings type
and defaults to support the settings UI from the previous commit.
Add wakatime:checkCli and wakatime:validateApiKey IPC handlers with
preload API exposure. Settings modal now shows CLI availability warning
when wakatime-cli is not found and validates API keys on input blur
with green checkmark / red X feedback. Includes 10 unit tests.
- Add missing registerWakatimeHandlers() call in setupIpcHandlers()
  to fix "No handler registered" IPC errors
- Add ensureCliInstalled() method to WakaTimeManager that downloads
  and installs wakatime-cli from GitHub releases to ~/.wakatime/
- Update detectCli() to check ~/.wakatime/ local install path
- Trigger auto-install on startup (if enabled) and when user enables
  WakaTime via store.onDidChange listener
- Replace detectCli() with ensureCliInstalled() in sendHeartbeat()
  for automatic recovery if CLI is removed
- Update Settings UI warning to show "installing automatically..."
  and add retry logic with 3-second delay for CLI status check
- Add comprehensive tests for auto-installer and updated detection
The CLI status useEffect now only retries after 3 seconds when the
initial check returns available: false or errors, avoiding unnecessary
API calls when the CLI is already installed. Also adds wakatime mock
to test setup and 7 new WakaTime CLI status tests.
Update the --plugin flag from 'maestro-wakatime' to the official format
'maestro/{version} maestro-wakatime/{version}' per the WakaTime plugin
creation guide. This matches the pattern used by VS Code's WakaTime plugin.
Per the official WakaTime plugin guide, the 'ai coding' category is the
correct classification for AI-assisted coding activity. This ensures
Maestro heartbeats show up properly categorized in the WakaTime dashboard.
Add daily update check that compares installed CLI version against the
latest GitHub release. When a newer version is available, the CLI is
automatically re-downloaded. The check runs as fire-and-forget from
ensureCliInstalled() and errors are handled gracefully.

- Add fetchJson() helper with User-Agent header for GitHub API
- Add checkForUpdate() private method with version normalization
- Add UPDATE_CHECK_INTERVAL_MS (24h) throttle to avoid excessive checks
- Add 5 new tests covering update lifecycle and error handling
Per the official WakaTime plugin guide, the API key should be readable
from ~/.wakatime.cfg when not provided in Maestro settings. Added
readApiKeyFromConfig() method that parses the INI-format config file
and extracts the api_key value. Settings key takes precedence over cfg.
Update registerWakatimeHandlers() to accept a WakaTimeManager instance.
Both checkCli and validateApiKey handlers now delegate to the manager's
ensureCliInstalled() and getCliPath() methods instead of manually
iterating through binary names. This means the Settings UI CLI check
also triggers auto-install when needed.

Added getCliPath() public method to WakaTimeManager. Updated
HandlerDependencies interface and registerAllHandlers() in
handlers/index.ts accordingly.
The WakaTime toggle is inside the Usage & Stats section which already
has a BETA badge on its header, making the inline badge redundant.
The query-complete event only fires for batch-mode processes with
querySource set, which the renderer never provides for interactive
sessions. Added a data event listener to trigger heartbeats on any
AI stdout output (skipping terminal sessions). Also fixed --entity
to use "Maestro" (app name) instead of a directory path, matching
the WakaTime spec for --entity-type app.
Detects the current git branch via git rev-parse in the session's
working directory and passes it to wakatime-cli via --alternate-branch.
Branch is cached per session to avoid running git on every heartbeat.
Pass agent type as --language flag (e.g., "Claude Code", "Codex") so
WakaTime shows the agent name instead of "Other". Also listen on
thinking-chunk events to capture time spent during AI reasoning,
improving accuracy of reported activity duration.
Replace agent type as --language with actual project language detection.
Checks for well-known manifest files in the session's cwd (tsconfig.json
→ TypeScript, Cargo.toml → Rust, go.mod → Go, etc.) and passes the
result via --language flag. Cached per session to avoid repeated fs
reads. This replaces the "Other" label on WakaTime dashboards with
the actual language being worked on.
Matches the behavior of VS Code, JetBrains, and other WakaTime plugins
which send the project directory name rather than the full filesystem path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove double handler registration: drop registerWakatimeHandlers from
  registerAllHandlers() and wakatimeManager from HandlerDependencies
- Add early bail-out in data/thinking-chunk/query-complete listeners when
  WakaTime is disabled, using cached enabled state with onDidChange
- Fix projectPath vs cwd inconsistency: pass projectDir (projectPath || cwd)
  to sendHeartbeat for language/branch detection
- Remove stray double blank line in SettingsModal
- Add 4 new tests for disabled-path bail-out and onDidChange reactivity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kianhub kianhub force-pushed the feature/wakatime-integration branch from f97d10f to aec8b9c Compare February 16, 2026 05:31
@pedramamini pedramamini merged commit 5c0133a into RunMaestro:main Feb 16, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants