MCPInstall

live-cursors

Live cursors and multiplayer presence with Rivet Actors: per-connection cursor state, realtime updates over events or raw WebSockets, and throttling.

Installs
1.1K
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/rivet-dev/skills --skill live-cursors
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads live-cursors automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Live Cursors and Presence

IMPORTANT: Before doing anything, you MUST read BASE_SKILL.md in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.

Working Examples

If you need a reference implementation, read the raw working example code in these templates:

Patterns for building live cursors, multiplayer presence, and realtime cursor sharing with RivetKit. One room actor fans cursor positions out to every connected client, keyed per room with actor keys.

Starter Code

Start with one of the two working variants on GitHub. Both implement the same collaborative cursor canvas with persistent text labels; they differ only in transport.

VariantStarter CodeTransportPresence Storage
cursorsGitHubTyped actions and events over the RivetKit connectionconnState per connection
cursors-raw-websocketGitHubRaw onWebSocket handler with a custom JSON message protocolSocket map in createVars

Use cursors by default: typed actions, typed events, and automatic connection tracking cover most apps with less code. Use cursors-raw-websocket when you need full control of the wire format, for example a custom JSON or binary protocol, or clients that do not use the RivetKit client library.

Connection State vs Persistent State

Presence is ephemeral by definition. A cursor position is only meaningful while its connection is alive, so it belongs in per-connection storage, not in persistent actor state. Persistent state is reserved for data that must survive disconnects and actor restarts.

DataWhere It LivesWhy
Cursor positionconnState (cursors) or the createVars socket map (cursors-raw-websocket)Scoped to one connection and discarded with it. Stale presence cannot accumulate in storage.
Text labels (textLabels)Persistent actor state in both variantsCanvas content must survive disconnects and actor restarts.

In the cursors variant, updateCursor writes c.conn.state.cursor and getRoomState rebuilds the presence snapshot by iterating c.conns.values(), so the cursor map is always derived from live connections rather than stored. See Connections for connState and State for persistence semantics.

Presence Lifecycle

  • Join: The cursors-raw-websocket variant pushes an init message with the current { cursors, textLabels } snapshot as soon as a socket connects. The cursors variant has no explicit join broadcast; the client calls the getRoomState action once after connecting to seed its local maps, and peers first see a new user on that user's first cursorMoved broadcast.
  • Move: Every updateCursor call writes the connection's presence entry, then broadcasts cursorMoved to all connections, including the sender.
  • Leave: The cursors variant handles leave in onDisconnect, broadcasting cursorRemoved with the connection's last cursor. The raw variant does the same from the socket close listener, then deletes the session from the vars.websockets map. Clients delete that user from their local cursor map, so stale cursors disappear the moment a tab closes.

See Lifecycle for onDisconnect and createVars.

Update Throttling

Neither example throttles. Both frontends send a cursor update on every raw mousemove event with no debounce or interval cap. That is fine for a demo, but a fast mouse on a high-refresh display can emit hundreds of events per second per user. The patterns below are recommended production hardening on top of the starter code, not something the examples implement.

LayerPatternGuidance
Client (smoothness)Throttle to 20-30HzSample the latest pointer position every 33-50ms and send only that. Drop intermediate moves, but always flush the final position so cursors settle at the true location. Interpolate between received positions on the rendering side.
Server (enforcement)Per-connection rate limitTrack the last accepted update timestamp per connection and drop or coalesce updates arriving faster than your cap. Client throttles are cooperative; the actor is the enforcement boundary.

Actors

  • Key: cursorRoom[roomId] (the frontend defaults roomId to "general")

  • Responsibility: Holds per-connection cursor presence in connState, persists shared text labels in actor state, and broadcasts cursor and text updates to all connections.

  • Actions

    • updateCursor
    • updateText
    • removeText
    • getRoomState
  • Events

    • cursorMoved
    • cursorRemoved
    • textUpdated
    • textRemoved
  • Queues

    • None
  • State

    • JSON
    • textLabels (persistent)
    • connState.cursor per connection (ephemeral)
  • Key: cursorRoom[roomId] (resolved via client.cursorRoom.getOrCreate(roomId))

  • Responsibility: Exposes a raw WebSocket endpoint, tracks live sockets and their cursors in a createVars map keyed by a sessionId query parameter, persists text labels, and manually fans JSON frames out to every socket.

  • Actions

    • getOrCreate (stub returning { status: "ok" }; the frontend resolves the actor ID with the client handle's getOrCreate(roomId).resolve(), which creates the actor without dispatching this action)
    • getRoomState
  • Queues

    • None
  • State

    • JSON
    • textLabels (persistent)
    • vars.websockets map of sessionId to socket and cursor (in-memory, lost on restart)

The raw variant defines no RivetKit events. Its message names are type fields on raw JSON frames:

DirectionMessage typePayload
Client to serverupdateCursor{ userId, x, y }
Client to serverupdateText{ id, userId, text, x, y }
Client to serverremoveText{ id }
Server to clientinit{ cursors, textLabels } snapshot on connect
Server to clientcursorMoved, textUpdated, textRemoved, cursorRemovedThe corresponding cursor, label, or ID payload

Lifecycle

cursors (Actions + Events)

sequenceDiagram
	participant A as Client A
	participant R as cursorRoom
	participant B as Other Clients

	A->>R: connect via useActor (cursorRoom[roomId])
	A->>R: getRoomState()
	R-->>A: {cursors, textLabels}
	loop every mouse move
		A->>R: updateCursor(userId, x, y)
		Note over R: write c.conn.state.cursor
		R-->>B: cursorMoved (broadcast)
	end
	A->>R: updateText(id, userId, text, x, y)
	Note over R: upsert persistent state.textLabels
	R-->>B: textUpdated (broadcast)
	Note over A: tab closes
	Note over R: onDisconnect reads conn.state.cursor
	R-->>B: cursorRemoved (broadcast)

cursors-raw-websocket

sequenceDiagram
	participant A as Client A
	participant R as cursorRoom
	participant B as Other Clients

	A->>R: getOrCreate(roomId).resolve()
	R-->>A: actorId
	A->>R: open WebSocket /gateway/{actorId}/websocket?sessionId=...
	Note over R: close 1008 if sessionId is missing
	Note over R: store socket in vars.websockets
	R-->>A: init {cursors, textLabels}
	loop every mouse move
		A->>R: {type: "updateCursor"} frame
		Note over R: update session cursor in vars
		R-->>B: cursorMoved frame
	end
	Note over A: socket closes
	R-->>B: cursorRemoved frame
	Note over R: delete session from vars.websockets

Security Checklist

Both examples ship without authentication so the presence pattern stays readable. Everything below is recommended hardening for production, not behavior the examples implement.

  • Identity: Bind presence identity to the connection (c.conn.id in the actions variant, a server-generated session ID in the raw variant). Never trust a client-supplied userId; in the examples it is a random client-generated string, so any client can impersonate or remove any cursor.
  • Authorization: Authorize label mutations by owner. In the examples, updateText accepts arbitrary id and userId arguments and removeText accepts an arbitrary id, so any client can edit or delete any label.
  • Input validation: Clamp x and y to canvas bounds, cap text label length, and cap the total textLabels count so persistent state cannot grow unbounded.
  • Rate limiting: Enforce a per-connection cap on updateCursor (for example 30Hz) and on label writes, as described in Update Throttling.
  • Protocol strictness (raw variant): Validate message shape before use and close the socket on malformed JSON instead of logging and continuing. Reject duplicate sessionId values rather than silently overwriting another session's socket entry.

Reference Map

Actors

  • Access Control
  • Actions
  • Actor Keys
  • Actor Runtime Socket
  • Actor Statuses
  • Authentication
  • Cloudflare Workers Quickstart
  • Communicating Between Actors
  • Connections
  • Custom Inspector Tabs
  • Debugging
  • Design Patterns
  • Destroying Actors
  • Effect.ts Quickstart (Beta)
  • Errors
  • Fetch and WebSocket Handler
  • Helper Types
  • Icons & Names
  • In-Memory State
  • Input Parameters
  • Lifecycle
  • Limits
  • Low-Level HTTP Request Handler
  • Low-Level KV Storage
  • Low-Level WebSocket Handler
  • Metadata
  • Next.js Quickstart
  • Node.js & Bun Quickstart
  • Queues & Run Loops
  • React Quickstart
  • Realtime
  • Rust Quickstart (Beta)
  • Scaling & Concurrency
  • Schedule & Cron
  • Sharing and Joining State
  • SQLite
  • SQLite + Drizzle
  • Supabase Functions Quickstart
  • Testing
  • Troubleshooting
  • Types
  • Vanilla HTTP API
  • Versions & Upgrades
  • Workflows

Cli

  • CLI

Clients

  • Node.js & Bun
  • React
  • Rust (Beta)
  • Swift
  • SwiftUI

Cookbook

  • AI Agent
  • Chat Room
  • Collaborative Text Editor
  • Cron Jobs and Scheduled Tasks
  • Database per Tenant
  • Deploying Rivet in a VPC or Air-Gapped Network
  • Live Cursors and Presence
  • Multiplayer Game

Deploy

  • Container Runner
  • Deploy To Amazon Web Services Lambda
  • Deploying to AWS ECS
  • Deploying to Cloudflare Workers
  • Deploying to Freestyle
  • Deploying to Google Cloud Run
  • Deploying to Hetzner
  • Deploying to Kubernetes
  • Deploying to Railway
  • Deploying to Rivet Compute
  • Deploying to Supabase Functions
  • Deploying to Vercel
  • Deploying to VMs & Bare Metal

General

  • Actor Configuration
  • Cross-Origin Resource Sharing
  • Documentation for LLMs & AI
  • Edge Networking
  • Endpoints
  • Environment Variables
  • HTTP Server
  • Logging
  • Pool Configuration
  • Production Checklist
  • Registry Configuration
  • Runtime Modes
  • WASM vs Native SDK

Self Hosting

  • Configuration
  • Docker Compose
  • Docker Container
  • File System
  • FoundationDB (Enterprise)
  • Installing Rivet Engine
  • Kubernetes
  • Multi-Region
  • PostgreSQL
  • Production Checklist
  • Railway Deployment
  • Render Deployment
  • TLS & Certificates

FAQ

What does the live-cursors skill do?

Live cursors and multiplayer presence with Rivet Actors: per-connection cursor state, realtime updates over events or raw WebSockets, and throttling.

How do I install live-cursors?

Run npx skills add https://github.com/rivet-dev/skills --skill live-cursors in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/live-cursors/ (personal) or .claude/skills/live-cursors/ (project).

Which AI agents can use live-cursors?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is live-cursors safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains live-cursors?

It is published by rivet-dev in rivet-dev/skills. It has 1,144 recorded installs.

More from rivet-dev/skills

View all →

sandbox-agent

rivet-dev/skills

Deploy, configure, and integrate Sandbox Agent - a universal API for orchestrating AI coding agents (Claude Code, Codex, OpenCode, Amp) in sandboxed environments. Use when setting up sandbox-agent server locally or in cloud sandboxes (E2B, Daytona, Docker), creating and managing agent sessions via SDK or API, streaming agent events and handling human-in-the-loop interactions, building chat UIs for coding agents, or understanding the universal schema for agent responses.

OfficialBackend & APIs10.6K installs

rivetkit

rivet-dev/skills

RivetKit backend and Rivet Actor runtime guidance. Use for building, modifying, debugging, or testing Rivet Actors, registries, serverless/runner modes, deployment, or actor-based workflows.

OfficialBackend & APIs7.7K installs

RivetKit JavaScript client guidance. Use for browser, Node.js, or Bun clients that connect to Rivet Actors with rivetkit/client, create clients, call actions, or manage connections.

OfficialBackend & APIs7.6K installs

rivetkit-client-react

rivet-dev/skills

RivetKit React client guidance. Use for React apps that connect to Rivet Actors with @rivetkit/react, create hooks with createRivetKit, or manage realtime state with useActor.

OfficialFrontend & Design7.5K installs

rivetkit-client-swiftui

rivet-dev/skills

RivetKit SwiftUI client guidance. Use for SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI, @Actor, rivetKit view modifiers, and SwiftUI bindings.

OfficialMobile7.3K installs

rivetkit-client-swift

rivet-dev/skills

RivetKit Swift client guidance. Use for Swift clients that connect to Rivet Actors with RivetKitClient, create actor handles, call actions, or manage connections.

OfficialMobile7.3K installs

Popular other skills

View category →

hyperframes-cli

heygen-com/hyperframes

The HyperFrames CLI by job - scaffold, check, look at frames, edit the timeline, preview, render locally or in the cloud, publish, project history - plus reading its JSON and recovering from build or render failures.

1.3M installs

triage

mattpocock/skills

Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.

1M installs

ask-matt

mattpocock/skills

Ask which skill or flow fits your situation. A router over the skills in this repo.

778.4K installs

to-questionnaire

mattpocock/skills

Turn a decision you can't fully answer into a questionnaire for someone else to fill in.

508.9K installs

wait-what

mattpocock/skills

Stop. That last message did not land: re-pitch it.

459.7K installs

loop-me

mattpocock/skills

Grill me about specs for the workflows I want to build, within this workspace.

447.9K installs