Skip to content

Team Tracker

Team Tracker records active minutes (1-minute ticks) while you work in VS Code or Cursor, classifies them per team workspace as internal or external based on the Git remote of the active editor, and sends aggregates to the SnakeFlow team API. It is optional and off until you sign in and enable it.

Minute counting is independent of Wake Lock → (OS sleep prevention): the tracker does not infer “human active” from whether the wake lock is held.

Requirements

  • SnakeFlow: Team Tracker — Login (GitHub OAuth via the editor).
  • devManager.team.enabled set to true (the login command can turn this on for you).
  • For OS-wide mouse and keyboard coverage outside the editor: ActivityWatch (aw-qt) on your machine (recommended; see below).

ActivityWatch and the status bar

When ActivityWatch is running, the extension can see input activity across apps (browser, terminal outside VS Code, etc.) so you do not drop to idle only because the editor has no events. If the window watcher (aw-watcher-window_* bucket) is present, the extension also reads the currently focused application name and window title once per minute (cached briefly) for app allowlist classification and for the status-bar tooltip.

  • Click the Team Tracker status-bar item to start aw-qt (if it is installed) or stop ActivityWatch (terminates the local ActivityWatch processes on your OS).
  • If aw-qt is not found, you get a prompt to open the download page.
  • SnakeFlow: Team Tracker — Show Activity Log (Command Palette) opens the detailed trace (ACTIVE / IDLE / GATED lines).

Hard gate (requireActivityWatch)

When devManager.team.requireActivityWatch is true (default), no minutes are counted unless the ActivityWatch REST API is reachable at activityWatchUrl. The status bar shows a red blocked icon and the tooltip explains that time is not being recorded until you start ActivityWatch.

Turn requireActivityWatch off only for roles that should not be forced (for example admins on their own machines).

What counts as “active”

Each 60-second tick first checks optional Sleep window (forces IDLE if local time falls inside idleSleepWindow).

Otherwise the collector needs ActivityWatch reachable (see Hard gate). It then evaluates three signals:

SignalSourceRole
AW inputaw-watcher-afk / input buckets via REST“ActivityWatch saw keyboard/mouse within the idle window.”
OS idleWindows GetLastInputInfo, macOS HIDIdleTime, Linux xprintidle when installed“True last user input age” — robust against synthetic hooks that fool AW alone.
IDE focusVS Code window.state.focused + last focus timestampCorroborates AW when the editor had OS focus recently.

A tick refreshes the activity clock (lastAwAt) only when trustworthy evaluates true — i.e. one of:

  1. AW and OS agree — both report recent input within the idle window (strongest).
  2. OS alone (Windows / macOS) — OS idle API says recent input (AW can disagree; we still count activity on these platforms).
  3. AW + recent IDE focus — AW says active and this VS Code window had OS focus within the idle window (blocks “synthetic input while you are away from the editor”).
  4. Linux fallback — when the OS idle helper is unavailable (no xprintidle), AW-only behaviour is preserved so Linux users are not regressed.

If none of the above apply for longer than your effective idle window, the minute is IDLE (no increment). The effective window is the minimum idle_window_minutes from all team workspaces you belong to (server-side admin setting); with no workspace memberships yet, a 5-minute built-in default applies until you join.

Each UTC day is capped by the backend — exact limits depend on your plan; workspace admins can confirm in the Cloud dashboard or the API reference.

Internal vs external

For each workspace you belong to, a tick is internal only when both are true:

  1. Repo match — the current GitHub-style remote of the active text editor (owner/repo) matches that workspace’s repo patterns (glob-style lists configured per workspace; see Cloud sync → Repo patterns).
  2. App match (if configured) — that workspace’s app allowlist is empty (no filter), or at least one regex pattern matches the ActivityWatch window app name or window title (case-insensitive). Patterns are stored on the server per workspace (admins edit them; every member receives them via /api/team/me).

Otherwise the tick is +1 external for that workspace:

  • Wrong repo → external with the real owner/repo label when known.
  • Wrong app (non-empty allowlist and no pattern matches the focused window) → external with a synthetic label _app:ApplicationName so the backend can store per-source external minutes alongside real repos.

Empty app allowlist means “any application counts” for the app filter; only repo patterns decide internal vs external.

Note: When ActivityWatch marks you active while you are focused in another app, repo classification still uses the last active editor in Cursor/VS Code. Keep a relevant file focused when you care about correct internal routing. If a workspace has a non-empty app allowlist but the window watcher is not installed or has no bucket, ActivityWatch may not report app/title → the tick is treated as not matching the allowlist and counts as external for that workspace.

App allowlist (admin)

Workspace admins maintain the list of regex strings on the server:

  • SnakeFlow CloudTeam tab → workspace toolbar → Edit app allowlist (camera icon), which opens the same flow as the command below.
  • SnakeFlow: Team Tracker — Edit App Allowlist (Command Palette) — comma-separated regexes; leave empty to clear the list (allow all apps again).

The server validates each pattern as a JavaScript RegExp (case-insensitive flag) and rejects invalid syntax. There is a hard limit on how many patterns and how long each string may be — check the Team / workspace endpoints in the API reference for current caps.

Tip: Patterns are matched against both the executable / app name and the window title (for example Cursor or \.tsx in the title), similar in spirit to ActivityWatch categorization but enforced by your team’s allowlist for SnakeFlow reporting.

Settings

SettingTypeDefaultDescription
devManager.team.enabledbooleanfalseMaster switch for Team Tracker.
devManager.team.idleWindowMinutesnumber5Declared idle window (1–15); changing it restarts the collector. Effective window with memberships = min server idle_window_minutes — see What counts as active.
devManager.team.useActivityWatchbooleantrueQuery the local ActivityWatch server for OS-wide input.
devManager.team.requireActivityWatchbooleantrueIf true, no time is recorded when ActivityWatch is not reachable.
devManager.team.activityWatchUrlstringhttp://localhost:5600Base URL of aw-server.
devManager.team.requireTrackingbooleanfalseWhen true, warn if tracking is off and no session started.
devManager.team.idleSleepWindowstring""Local-time HH:MM-HH:MM window during which tracking is force-IDLE — see Sleep window.

Changing these keys triggers a collector restart so new values apply without reloading the window.

Sleep window

devManager.team.idleSleepWindow (format HH:MM-HH:MM in local time, default empty) is a belt-and-braces guard against false-positive activity at night. While the current local time falls inside this interval, the collector forces IDLE regardless of ActivityWatch, OS idle time, or VS Code focus.

Use it when:

  • Synthetic input from remote-control or “anti-idle” tools (AnyDesk, mouse jigglers, certain drivers) misleads ActivityWatch into reporting not-afk while you sleep.
  • A long-running task or never-released wake lock keeps the OS awake overnight.

The window wraps over midnight when end < start — 23:00-07:00 covers a typical sleep schedule. Empty string disables the override.

This works alongside the dual-factor activity rules in What counts as active: even with the sleep window disabled, the collector prefers corroborated signals (AW + OS, OS alone on Windows/macOS, or AW + recent VS Code focus) so lone synthetic input from remote tools is unlikely to count a minute.

Commands

CommandDescription
SnakeFlow: Team Tracker — LoginSign in; can enable team tracking.
SnakeFlow: Team Tracker — LogoutStop tracking and sign out.
SnakeFlow: Team Tracker — StatusQuick summary in a notification.
SnakeFlow: Team Tracker — Show Activity LogOutput channel with per-minute reasons.
SnakeFlow: Team Tracker — Start/Stop ActivityWatchSame as status-bar toggle (palette access).
SnakeFlow: Team Tracker — Edit App AllowlistWorkspace admin: edit regex allowlist for ActivityWatch window app/title (server-side).
SnakeFlow: Team Tracker — Create WorkspaceAdmin: new workspace + invite.
SnakeFlow: Team Tracker — Join WorkspaceJoin with invite code.
SnakeFlow: Team Tracker — Manage WorkspacesList / switch context.
SnakeFlow: Team Tracker — Sync Commits (admin)Push aggregated commit counts for dashboards.
SnakeFlow: Team Tracker — Sync Closed Issues (admin)Push closed GitHub issue counts (assignee + UTC closed day) for dashboards.

Commit and closed-issue sync (admin)

Workspace admins with Team Tracker enabled and a GitHub session can sync aggregated GitHub data into SnakeFlow Cloud (D1). The backend never receives your GitHub token; the extension queries GitHub Search and POSTs counts.

MetricGitHub query (per member, per UTC day)Stored asInternal vs external
Commitsauthor:{login} author-date:{YYYY-MM-DD}daily_commitsRepo matches workspace repo patterns
Closed issuesassignee:{login} is:issue is:closed closed:{YYYY-MM-DD}daily_closed_issuesSame repo patterns
  • Assignee required for closed issues: unassigned issues are not counted.
  • Who closed does not matter — only assignee and closed date.
  • Sync runs hourly while tracking is on (admin workspaces) and can be triggered manually via the commands above (last 7 UTC days by default).
  • SnakeFlow Cloud → Activity shows internal/external columns for both metrics.

Hourly heatmap and member time zones

The SnakeFlow Cloud → Activity view shows a per-day 24-hour strip (0h–23h) for each member. The backend stores hourly buckets in UTC (the hour at flush time on the server). That alone would misalign “9 AM local work” for teammates in different regions.

On every successful POST /api/team/active-minutes flush, the extension sends the member’s IANA time zone (from Intl.DateTimeFormat().resolvedOptions().timeZone, for example Europe/Kyiv). The API stores it on the workspace_members row for that member (last value wins).

The dashboard then converts each stored (UTC day, UTC hour) bucket into that member’s local calendar hour before drawing the strip. The result is a shared 0–23 “local working hours” axis: if one person starts at 09:00 in Kyiv and another at 09:00 in London, both appear in the same column (hour 9), which matches how teams usually read “who was active when” in their own heads.

  • Legacy members who have never pushed with a client that sends timezone show null in the API; the UI treats that as UTC for the conversion until their next flush updates the field.
  • Changing the OS time zone on a machine updates the value on the next push; historical buckets are reinterpreted with the latest stored zone (acceptable for a team-activity overview).
  • Per-day totals (daily_minutes) stay on UTC calendar days from the server; only the hourly visualization uses the member zone shift.

There is no separate “workspace time zone” setting for this feature—the alignment comes from each member’s reported zone.

See also