feat: WakaTime integration for IDE activity tracking#326
feat: WakaTime integration for IDE activity tracking#326pedramamini merged 17 commits intoRunMaestro:mainfrom
Conversation
70a230b to
7c6369f
Compare
pedramamini
left a comment
There was a problem hiding this comment.
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 appwith--entity Maestroper WakaTime spec path.basename(projectDir)for project name matches VS Code behavior
Items to address:
- 🐛 Double handler registration —
registerWakatimeHandlersis called both inindex.ts:setupIpcHandlers()and added toregisterAllHandlers()inhandlers/index.ts - ⚡ Performance on disabled path —
dataevent listener fires on every stdout chunk, doing work even when WakaTime is disabled (the default) - 🔧
projectPathvscwdinconsistency in listener - 🧹 Cosmetic — stray double blank line in SettingsModal
See inline comments for details.
| agentConfigsStore: deps.agentConfigsStore, | ||
| settingsStore: deps.settingsStore, | ||
| }); | ||
| // Register WakaTime handlers (CLI check, API key validation) |
There was a problem hiding this comment.
🐛 Double handler registration — registerWakatimeHandlers 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
⚡ 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); | ||
| } |
There was a problem hiding this comment.
🔧 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> | ||
|
|
There was a problem hiding this comment.
🧹 Stray double blank line here.
| response.resume(); | ||
| fetchJson(response.headers.location, maxRedirects - 1).then(resolve, reject); | ||
| return; | ||
| } |
There was a problem hiding this comment.
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.
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>
f97d10f to
aec8b9c
Compare
Summary
What's included
Settings UI
~/.wakatime.cfgif not set in MaestroMain Process
WakaTimeManagerclass (src/main/wakatime-manager.ts) handles:~/.wakatime/dataevents) and AI reasoning (thinking-chunkevents) for interactive sessions, plusquery-completefor batch/auto-run; debounced to 2 min per session--entity-type appwith--entity Maestro(app name, not file path) per WakaTime spectsconfig.json→ TypeScript,Cargo.toml→ Rust,go.mod→ Go,pyproject.toml→ Python, etc.) — cached per sessiongit rev-parse --abbrev-ref HEADand passes via--alternate-branch— cached per session--plugin maestro/{version} maestro-wakatime/{version}per official specai codingcategory (dedicated WakaTime category for AI-assisted work)wakatime-listener.ts) wires heartbeats to:dataevents — fires on every AI stdout chunk (covers interactive sessions); skips terminal sessionsthinking-chunkevents — fires during AI extended thinking/reasoning, ensuring long reasoning periods are trackedquery-completeevents — fires for batch/auto-run processesexitevents — cleans up per-session debounce, branch, and language cachesWakaTimeManagerfor auto-installTests
wakatime-manager.test.ts— heartbeats, debouncing, CLI detection, auto-install, version checking, config fallbackwakatime.test.ts(IPC handlers) — handler registration, CLI check, API key validationwakatime-listener.test.ts— data events, thinking-chunk events, terminal session filtering, query-complete, exit cleanupSettingsModal.test.tsx— CLI status UI statesuseSettings.test.ts— settings persistenceNew files
src/main/wakatime-manager.tssrc/main/ipc/handlers/wakatime.tssrc/main/preload/wakatime.tswindow.maestro.wakatime)src/main/process-listeners/wakatime-listener.tsKey design decisions
--entity-type appwith--entity Maestro: WakaTime expects an application name (not a file path) for theappentity type. The project directory is passed via--projectinstead.data+thinking-chunkevents: Thequery-completeevent only fires for batch-mode processes withquerySourceset, which the renderer doesn't provide for interactive sessions. Listening ondataandthinking-chunkensures heartbeats fire for all AI activity including extended reasoning.tsconfig.json,Cargo.toml,go.mod, etc.) rather than scanning all file extensions — fast, deterministic, and covers the common case.Test plan
~/.wakatime/for binary)npm run lintandnpm run testpass