# Error Reports CLI Tool

`scripts/error-reports.mjs` is a Node.js CLI tool for querying Penpot error reports via the RPC API. Provides access to error logs with filtering, pagination, and multiple output formats.

## When to use

- Querying error reports from the database for debugging or analysis
- Filtering errors by source, kind, tenant, or backend version
- Exporting error data in JSON, NDJSON, or table format
- Computing error statistics (top signatures, version, source, audit-log kind, hourly distribution, bursts, heatmap)
- Investigating specific error reports by ID

## Prerequisites

- Node.js with `commander` and `dotenv` packages installed (in root `package.json`)
- Running Penpot backend with error-reports RPC endpoints
- Access token with `error-reports:read` permission

## Configuration

Create a `.env` file in the project root:

```bash
PENPOT_API_URI=http://localhost:3450
PENPOT_ACCESS_TOKEN=<your-token>
```

Grant the required permission to your access token:

```sql
UPDATE access_token
SET perms = ARRAY['error-reports:read']::text[],
    updated_at = now()
WHERE id = '<token-uuid>';
```

## Usage

```bash
./scripts/error-reports.mjs <command> [options]
```

### Commands

#### `list` - List error reports with pagination and filters

```bash
./scripts/error-reports.mjs list [options]
```

**Options:**

| Flag | Description | Default |
|------|-------------|---------|
| `-l, --limit <n>` | Max items per page (max: 200) | `50` |
| `--from <date>` | ISO timestamp — oldest boundary (items after this) | — |
| `--to <date>` | ISO timestamp — newest boundary (items before this) | — |
| `--since <date>` | ISO timestamp — explicit cursor for manual pagination | — |
| `--since-id <uuid>` | Fetch errors after this ID (cursor pagination) | — |
| `-s, --source <name>` | Filter by source (see source names below) | — |
| `-p, --profile-id <uuid>` | Filter by profile ID | — |
| `-k, --kind <kind>` | Filter by kind (string) | — |
| `-t, --tenant <tenant>` | Filter by tenant (string) | — |
| `--version <version>` | Filter by version | — |
| `--hint <text>` | Filter by hint (ILIKE match) | — |
| `-a, --all` | Fetch all pages automatically (streams output) | `false` |
| `-f, --format <type>` | Output format: `json`, `table`, or `ndjson` | `table` |
| `--normalize-hints` | Normalize hints by stripping dynamic values | `false` |
| `-o, --output <file>` | Write output to file instead of stdout | — |
| `--env <path>` | Custom .env file path | `.env` |
| `-h, --help` | Show help message | — |

**Streaming behavior:** With `--all`, output must be `ndjson` or `table`; `--all --format json` is rejected because `--all` streams output. `--all --format table` prints rows immediately. `--format ndjson` always streams one JSON object per line.

#### `get` - Get a single error report by ID

```bash
./scripts/error-reports.mjs get [options]
```

**Options:**

| Flag | Description | Required |
|------|-------------|----------|
| `--id <uuid>` | Error report ID | Yes (or --error-id) |
| `--error-id <id>` | Error report error-id | Yes (or --id) |
| `-f, --format <type>` | Output format: `json` or `table` | No (default: `table`) |
| `--env <path>` | Custom .env file path | No (default: `.env`) |
| `-h, --help` | Show help message | No |

#### `stats` - Compute error report statistics

```bash
./scripts/error-reports.mjs stats [options]
```

Reads from `--input <file>`, stdin (piped), or fetches from API. Computes aggregations by signature, version, source, audit-log kind, hour, optional 5-minute bursts, and optional day-of-week × hour heatmap.

**Options:**

| Flag | Description | Default |
|------|-------------|---------|
| `--from <date>` | Start of interval (ISO timestamp) | — |
| `--to <date>` | End of interval (ISO timestamp) | — |
| `--limit <n>` | Items per page when fetching from API | `200` |
| `--input <file>` | Read from local JSON/NDJSON file instead of API | — |
| `--burst` | Detect 5-minute windows above 3× the average rate | `false` |
| `--heatmap` | Show day-of-week × hour-of-day heatmap | `false` |
| `-f, --format <type>` | Output format: `json` or `table` | `table` |
| `--env <path>` | Custom .env file path | `.env` |

## Source Names

The `--source` filter accepts these values:

- `logging`
- `audit-log`
- `rlimit`

## Hint Normalization

With `--normalize-hints` (or always in `stats`), hints are normalized by stripping dynamic values:

1. File IDs in file-id context → `<file-id>`
2. UUIDs (8-4-4-4-12 hex) → `<uuid>`
3. Numeric IDs in parentheses `(12345)` → `(<id>)`
4. Elapsed times (`7.5s`, `2m3.027s`) → `<elapsed>`
5. URIs (`https://...`) → `<uri>`
6. Unicode quotes and whitespace normalized

## Examples

### List recent errors
```bash
./scripts/error-reports.mjs list --limit 10
```

### Time-range query (today)
```bash
./scripts/error-reports.mjs list --from 2026-07-23T00:00:00Z --to 2026-07-23T23:59:59Z --all
```

### Stream all errors as NDJSON
```bash
./scripts/error-reports.mjs list --all --format ndjson > errors.ndjson
```

### Save to file with --output
```bash
./scripts/error-reports.mjs list --all --format ndjson -o errors.ndjson
./scripts/error-reports.mjs list --format json -o errors.json
```

### Filter by source
```bash
./scripts/error-reports.mjs list --source audit-log --limit 20
```

### Filter by kind
```bash
./scripts/error-reports.mjs list --kind exception-page
```

### Filter by tenant
```bash
./scripts/error-reports.mjs list --tenant production
```

### Filter by version
```bash
./scripts/error-reports.mjs list --version 2.1.0
```

### Search by hint (partial match)
```bash
./scripts/error-reports.mjs list --hint "NullPointerException"
```

### Fetch all errors with pagination
```bash
./scripts/error-reports.mjs list --all
```

### Get specific error by ID
```bash
./scripts/error-reports.mjs get --id 550e8400-e29b-41d4-a716-446655440000
```

### Output as JSON
```bash
./scripts/error-reports.mjs list --limit 5 --format json
```

### Combine filters
```bash
./scripts/error-reports.mjs list --source audit-log --kind exception-page --tenant production --limit 50
```

### Stats with burst and heatmap analysis
```bash
./scripts/error-reports.mjs stats --from 2026-07-23T00:00:00Z --to 2026-07-23T23:59:59Z --burst --heatmap
```

### Stats from file
```bash
./scripts/error-reports.mjs stats --input errors.json
```

### Stats from pipe
```bash
./scripts/error-reports.mjs list --all --format json | ./scripts/error-reports.mjs stats
```

## Output Formats

### Table (default)
Human-readable table format for terminal display. With `--all`, rows stream as they arrive.

### JSON
Single page: `{items: [...], nextSince, nextId}`. `--all` cannot be combined with `--format json`; use `--format ndjson` for streaming.

### NDJSON
One JSON object per line, always streaming. Pipe-friendly: `| jq -c '.hint'`, `| wc -l`.

## Pagination

The server returns items in **ascending** order (oldest first). Cursor pagination uses `--since` / `--since-id` to fetch the next page of newer items.

### Manual pagination
Use `--since` and `--since-id` with values from `nextSince` and `nextId` in the response:

```bash
./scripts/error-reports.mjs list --limit 50
# Use nextSince and nextId from response
./scripts/error-reports.mjs list --limit 50 --since "2026-01-20T10:29:00Z" --since-id "next-uuid"
```

### Automatic pagination
Use `--all` to fetch all pages automatically (streams output):

```bash
./scripts/error-reports.mjs list --all
```

### Time-range queries
Use `--from` and `--to` to bound the query. These map to the server's `--since` and `--until` parameters:

```bash
./scripts/error-reports.mjs list --from 2026-07-20T00:00:00Z --to 2026-07-23T23:59:59Z --all
```

## Key principles

- **Authentication required** - Uses access token with `error-reports:read` permission
- **API endpoint configurable** - Set via `PENPOT_API_URI` in `.env` file
- **Table is default format** - Use `--format json` for structured JSON, `--format ndjson` for streaming
- **Streaming with --all** - Items print as they arrive, no buffering. Use `--format ndjson` or `--format table`; `--all --format json` is rejected.
- **Filters are combinable** - All filter options can be used together
- **Both flag formats supported** - `--option=value` and `--option value` both work
- **Ascending order** - Server returns oldest items first (changed from DESC)

## Error handling

The tool provides helpful error messages for common issues:

- **Missing configuration**: Shows setup instructions for `.env` file
- **Authentication errors (401)**: Indicates invalid or expired token
- **Authorization errors (403)**: Indicates missing `error-reports:read` permission
- **RPC errors**: Displays error code and message from the API

## Integration with other scripts

- **jq**: Pipe NDJSON output to `jq` for further processing
  ```bash
  ./scripts/error-reports.mjs list --all --format ndjson | jq -c '{id, hint}'
  ```
- **stats from pipe**: Fetch data once, compute stats
  ```bash
  ./scripts/error-reports.mjs list --all --format ndjson | ./scripts/error-reports.mjs stats
  ```
- **stats from NDJSON pipe**: Works with NDJSON format too
  ```bash
  ./scripts/error-reports.mjs list --all --format ndjson | ./scripts/error-reports.mjs stats
  ```
- **grep/search**: Filter output by specific patterns
- **--output**: Save to file without shell redirection
  ```bash
  ./scripts/error-reports.mjs list --all --format ndjson -o errors.ndjson
  ```
