Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ Get the client ID from your Sentry OAuth application settings.
## Running Locally

```bash
bun run --env-file=.env.local src/bin.ts auth login
bun run --env-file=.env.local cli auth login
```

## Testing the Device Flow

1. Run the CLI login command:

```bash
bun run --env-file=.env.local src/bin.ts auth login
bun run --env-file=.env.local cli auth login
```

2. You'll see output like:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ bun install

```bash
# Run CLI in development mode
bun run dev --help
bun run cli --help

# With environment variables
bun run --env-file=.env.local src/bin.ts --help
bun run --env-file=.env.local cli --help
```

### Scripts
Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cd cli
bun install

# Run CLI in development mode
bun run --env-file=.env.local src/bin.ts --help
bun run --env-file=.env.local cli --help

# Run tests
bun test
Expand Down Expand Up @@ -53,14 +53,14 @@ cli/
│ ├── commands/ # CLI commands
│ │ ├── auth/ # login, logout, refresh, status, token, whoami
│ │ ├── cli/ # defaults, feedback, fix, setup, upgrade
│ │ ├── dashboard/ # list, view, create, add, edit, delete
│ │ ├── dashboard/ # list, view, create, widget add, widget edit, widget delete
│ │ ├── event/ # view, list
│ │ ├── issue/ # list, events, explain, plan, view, resolve, unresolve, archive, merge
│ │ ├── log/ # list, view
│ │ ├── org/ # list, view
│ │ ├── project/ # create, delete, list, view
│ │ ├── release/ # list, view, create, finalize, delete, deploy, deploys, set-commits, propose-version
│ │ ├── replay/ # list, view
│ │ ├── replay/ # event list, list, summarize, view
│ │ ├── repo/ # list
│ │ ├── sourcemap/ # inject, upload
│ │ ├── span/ # list, view
Expand Down
35 changes: 35 additions & 0 deletions docs/src/fragments/commands/replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ sentry replay list my-org/ --query "environment:production"
# Change the time window and sort
sentry replay list my-org/frontend --period 24h --sort errors

# Find recent sessions that actually visited a route path
sentry replay list my-org/frontend --path /signup --json

# Find recent sessions with indexed friction signals
sentry replay list my-org/frontend --path /signup --friction --json

# Paginate through results
sentry replay list my-org/frontend -c next
sentry replay list my-org/frontend -c prev
Expand All @@ -36,3 +42,32 @@ sentry replay view my-org/frontend/346789a703f6454384f1de473b8b9fcc
# Open a replay in the browser
sentry replay view my-org/346789a703f6454384f1de473b8b9fcc --web
```

### Summarize behavior

```bash
# Summarize route flow, event counts, timings, and friction signals
sentry replay summarize my-org/346789a703f6454384f1de473b8b9fcc --json

# Focus the summary on a particular route path
sentry replay summarize my-org/346789a703f6454384f1de473b8b9fcc \
--path /signup --json
```

### Inspect replay events

```bash
# List normalized replay events for agent-readable inspection
sentry replay events my-org/346789a703f6454384f1de473b8b9fcc --json

# Focus on user actions and failures on a page
sentry replay events my-org/346789a703f6454384f1de473b8b9fcc \
--path /signup --kind click,network,console,error --json

# Pull an evidence window around a timestamp
sentry replay events my-org/346789a703f6454384f1de473b8b9fcc \
--around 01:23 --json

# Emit newline-delimited JSON for large timelines
sentry replay events my-org/346789a703f6454384f1de473b8b9fcc --json --jsonl

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Our --json mode should default to JSONL in this case?

```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@sentry/core@10.50.0": "patches/@sentry%2Fcore@10.50.0.patch"
},
"scripts": {
"cli": "bun run src/bin.ts",
"dev": "bun run generate:schema && bun run generate:docs && bun run generate:sdk && bun run src/bin.ts",
"build": "bun run generate:schema && bun run generate:docs && bun run generate:sdk && bun run script/build.ts --single",
"build:all": "bun run generate:schema && bun run generate:docs && bun run generate:sdk && bun run script/build.ts",
Expand Down
2 changes: 2 additions & 0 deletions plugins/sentry-cli/skills/sentry-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@ Manage Sentry dashboards

Search and inspect Session Replays

- `sentry replay event list <replay-id-or-url...>` — List normalized events from a Session Replay
- `sentry replay list <org/project>` — List recent Session Replays
- `sentry replay summarize <replay-id-or-url...>` — Summarize Session Replay behavior
- `sentry replay view <replay-id-or-url...>` — View a Session Replay

→ Full flags and examples: `references/replay.md`
Expand Down
96 changes: 95 additions & 1 deletion plugins/sentry-cli/skills/sentry-cli/references/replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,64 @@ requires:

Search and inspect Session Replays

### `sentry replay event list <replay-id-or-url...>`

List normalized events from a Session Replay

**Flags:**
- `-k, --kind <value>... - Event kind filter (navigation, click, tap, input, focus, blur, scroll, viewport, mutation, dom-snapshot, breadcrumb, network, console, error, span, web-vital, memory, video, mobile, unknown)`
- `-u, --url <value> - Filter events by current or target URL substring`
- `--path <value> - Filter events by parsed URL pathname`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if we can make this an optional positional argument instead as it looks quite common and looks like a natural sub-section?

- `-q, --contains <value> - Filter events by text in labels, messages, URLs, selectors, or data`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

-q is reserved for the short version of --search so we should either change it or change --contains to --search?

- `--selector <value> - Filter events by selector substring`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not clear how --selector and --contains work together or how they defer?

- `--from <value> - Start offset (seconds, 90s, 01:23, or 1:02:03)`
- `--to <value> - End offset (seconds, 90s, 01:23, or 1:02:03)`
- `--around <value> - Center an evidence window around this offset`
- `--before <value> - Window before --around (default: 10s)`
- `--after <value> - Window after --around (default: 30s)`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we use the existing -t/--period notation for these:

-t, --period <period> | Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" (default: "7d")

- `-n, --limit <value> - Number of events (1-1000) - (default: "200")`
- `--raw - Include raw source frame payloads in JSON output`
- `--jsonl - Emit one JSON object per event (requires --json)`
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`

**JSON Fields** (use `--json --fields` to select specific fields):

| Field | Type | Description |
|-------|------|-------------|
| `replayId` | string | Replay ID |
| `segmentIndex` | number | Zero-based recording segment index |
| `frameIndex` | number | Zero-based frame index within segment |
| `offsetMs` | number \| null | Milliseconds from replay start to the event |
| `timestamp` | string \| null | Event timestamp as ISO 8601 when available |
| `kind` | string | Normalized event kind |
| `category` | string | Broad event category |
| `label` | string \| null | Short event label |
| `message` | string \| null | Message or summary |
| `url` | string \| null | Current or target URL |
| `urlPath` | string \| null | Parsed URL pathname when available |
| `urlQuery` | string \| null | Parsed URL query string when available |
| `selector` | string \| null | CSS selector or target selector when available |
| `nodeId` | unknown \| null | rrweb node ID when available |
| `rawType` | string \| null | Source frame type |
| `rawSource` | string \| null | Source frame subtype |
| `data` | unknown | Kind-specific normalized fields |
| `raw` | unknown | Raw source frame, only present when requested |

### `sentry replay list <org/project>`

List recent Session Replays

**Flags:**
- `-n, --limit <value> - Number of replays (1-1000) - (default: "25")`
- `-q, --query <value> - Search query (Sentry replay search syntax)`
- `-u, --url <value> - Filter by visited URL text using replay search`
- `--path <value> - Filter by actual visited URL pathname`
- `--entry-path <value> - Filter by first visited URL pathname`
- `--exit-path <value> - Filter by last visited URL pathname`
- `--friction - Only show replays with indexed friction signals (errors, warnings, rage clicks, or dead clicks)`
- `--problem-only - Only show replays with indexed errors or warnings`
- `-e, --environment <value>... - Filter by environment (repeatable, comma-separated)`
- `-s, --sort <value> - Sort by: date, oldest, duration, errors, activity, or a raw replay sort field - (default: "date")`
- `-s, --sort <value> - Sort by: date, oldest, duration, errors, warnings, rage, dead, activity, or a raw replay sort field - (default: "date")`
- `-t, --period <value> - Time range: "7d", "2026-04-01..2026-05-01", ">=2026-04-01" - (default: "7d")`
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`
- `-c, --cursor <value> - Navigate pages: "next", "prev", "first" (or raw cursor string)`
Expand Down Expand Up @@ -72,6 +121,12 @@ sentry replay list my-org/ --query "environment:production"
# Change the time window and sort
sentry replay list my-org/frontend --period 24h --sort errors

# Find recent sessions that actually visited a route path
sentry replay list my-org/frontend --path /signup --json

# Find recent sessions with indexed friction signals
sentry replay list my-org/frontend --path /signup --friction --json

# Paginate through results
sentry replay list my-org/frontend -c next
sentry replay list my-org/frontend -c prev
Expand All @@ -80,6 +135,45 @@ sentry replay list my-org/frontend -c prev
sentry replay list my-org/frontend --json
```

### `sentry replay summarize <replay-id-or-url...>`

Summarize Session Replay behavior

**Flags:**
- `--path <value> - Focus summary on events from this URL pathname`
- `--limit-signals <value> - Maximum friction signals to include (0-50) - (default: "10")`
- `--limit-events <value> - Maximum notable events to include (0-50) - (default: "12")`
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`

**JSON Fields** (use `--json --fields` to select specific fields):

| Field | Type | Description |
|-------|------|-------------|
| `replayId` | string | Replay ID |
| `org` | string | Organization slug |
| `project` | string \| null | Project slug |
| `startedAt` | string \| null | Replay start time |
| `durationSeconds` | number \| null | Replay duration in seconds |
| `entryUrl` | string \| null | First replay URL |
| `exitUrl` | string \| null | Last replay URL |
| `focusPath` | string \| null | Optional route path used to focus the summary |
| `counts` | object | Normalized event counts |
| `timings` | object | Key timing observations |
| `routes` | array | Route timeline |
| `signals` | array | Detected non-error and error friction signals |
| `notableEvents` | array | Representative events useful for agent narrative |

**Examples:**

```bash
# Summarize route flow, event counts, timings, and friction signals
sentry replay summarize my-org/346789a703f6454384f1de473b8b9fcc --json

# Focus the summary on a particular route path
sentry replay summarize my-org/346789a703f6454384f1de473b8b9fcc \
--path /signup --json
```

### `sentry replay view <replay-id-or-url...>`

View a Session Replay
Expand Down
17 changes: 12 additions & 5 deletions script/generate-docs-sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,20 @@ function isStandaloneCommand(route: RouteInfo): boolean {

/**
* Get subcommand names for a route group (e.g., "list, view, create").
* Extracts the last path segment from each command's path.
* Preserves nested subcommands as "parent child" so route groups do not
* collapse multiple commands to the same final segment.
*/
function getSubcommandNames(route: RouteInfo): string[] {
return route.commands.map((cmd) => {
const parts = cmd.path.split(" ");
return parts.at(-1) ?? route.name;
});
const prefix = `sentry ${route.name} `;
return [
...new Set(
route.commands.map((cmd) =>
cmd.path.startsWith(prefix)
? cmd.path.slice(prefix.length)
: (cmd.path.split(" ").at(-1) ?? route.name)
)
),
];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use Array.from(new Set(....)) as that's way more efficient

}

/**
Expand Down
24 changes: 24 additions & 0 deletions src/commands/replay/event/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* sentry replay event
*
* Inspect normalized events from Session Replay recordings.
*/

import { buildRouteMap } from "../../../lib/route-map.js";
import { listCommand } from "./list.js";

export const eventRoute = buildRouteMap({
routes: {
list: listCommand,
},
defaultCommand: "list",
docs: {
brief: "Inspect normalized replay events",
fullDescription:
"Inspect normalized events extracted from Session Replay recordings.\n\n" +
"Commands:\n" +
" list List normalized replay events\n\n" +
"Alias: `sentry replay events` → `sentry replay event list`",
hideRoute: {},
},
});
Loading
Loading