Skip to main content
Glama
README.md
<div align="center">
  <img src="docs/assets/logo.svg" alt="Sight Cache" width="180" />
  <h1>Sight Cache</h1>
  <p><em>Give AI agents eyes on the scene and a memory of what happened.</em></p>
  <p><a href="https://catwithlover.github.io/sight-cache/"><strong>Website</strong></a></p>
  <p>English · <a href="README.zh-TW.md" lang="zh-Hant-TW">繁體中文</a></p>
</div>

**Sight Cache** — A serverless camera history pipeline built on Cloudflare that gives AI agents a compact, inspectable visual memory through MCP.

## How it works

```text
Administrator <--> Cloudflare Access <--> Admin Worker
                                                +--> D1 devices + token hashes
                                                `--> Collector Token (shown once)

RTSP camera --> FFmpeg Collector --> Ingest Worker --> D1 activity
                                                   `--> R2 original JPEGs

MCP client <--> Access Managed OAuth <--> Image Worker
                                          ^  |  |
Hourly cron --> Queue ---------------------'  |  +--> Cloudflare Images
R2 original JPEGs ----------------------------'  `--> R2 contact sheets + manifests
```

## Components

| Component | Runtime | Responsibility |
| --- | --- | --- |
| [`apps/collector/`](apps/collector/README.md) | Node.js or Docker | Capture and upload RTSP frames |
| [`apps/admin-worker/`](apps/admin-worker/README.md) | Cloudflare Workers | Manage devices, issue Collector Tokens, rotate credentials, and disable access |
| [`apps/ingest-worker/`](apps/ingest-worker/README.md) | Cloudflare Workers | Authenticate collectors, validate image uploads, and store original frames |
| [`apps/image-worker/`](apps/image-worker/README.md) | Cloudflare Workers | Build contact sheets and expose protected MCP inspection tools |

## Cloudflare services

This project uses the following Cloudflare services and Workers features:

| Service or feature | Used by | Purpose |
| --- | --- | --- |
| Cloudflare Workers | Admin, Ingest, and Image Workers | Run the administration interface, image ingestion API, MCP endpoint, schedules, and Queue handlers |
| Cloudflare Access | Admin and Image Workers | Protect administration and MCP; MCP uses Managed OAuth, and the Worker validates the Access JWT assertion |
| Cloudflare D1 | Admin, Ingest, and Image Workers | Store devices, Collector Token hashes, and activity timestamps, and provide recent active-device information |
| Cloudflare R2 | Ingest and Image Workers | Store original images, contact sheets, and manifests |
| Cloudflare Images | Image Worker | Resize and crop original frames, then compose JPEG contact sheets |
| Cloudflare Queues | Image Worker | Deliver hourly contact-sheet jobs |
| Cron Triggers | Image Worker | Enqueue contact-sheet jobs for the previous complete hour near the start of each hour |
| Workers Static Assets | Admin, Ingest, and Image Workers | Serve administration CSS and JavaScript, service-page assets, and the blank contact-sheet canvas |

## MCP inspection

The Image Worker exposes a stateless `/mcp` endpoint protected by Cloudflare Access Managed OAuth.

| Tool | Purpose |
| --- | --- |
| `list_devices` | List active devices and their latest upload time |
| `get_contact_sheet` | Return one JPEG contact sheet with exact metadata for each slot |
| `list_frames` | List exact capture timestamps in an interval of up to five minutes |
| `get_frame_comparison_sheet` | Compare 2–10 selected exact frames in one derived JPEG |
| `get_original_frame` | Return the unmodified JPEG at an exact capture timestamp |
| `create_original_frame_downloads` | When enabled, create temporary download URLs for up to 20 original frames |

Hourly windows contain 60 one-minute samples across six contact sheets. Minute
windows contain 12 five-second samples across two contact sheets and are built
on the first request after a five-minute finalization delay.

## Storage layout

Object keys use the normalized UTC capture time:

```text
frames/{deviceId}/{YYYY}/{MM}/{DD}/{HH}/{mm}/{UTC_TIMESTAMP}.jpg

contact-sheets/v1/{deviceId}/{YYYY}/{MM}/{DD}/{HH}/
|-- manifest.json
|-- generations/{generationId}/sheet-{NN}.jpg
`-- {mm}/
    |-- manifest.json
    `-- generations/{generationId}/sheet-{NN}.jpg
```

The hour-level manifest publishes scheduled hourly sheets. A minute directory
contains its on-demand sheets.

## Requirements

- Node.js 24.19.0 or later
- FFmpeg, or Docker with Compose, for the Collector
- A reachable RTSP camera stream
- Cloudflare Workers Paid plan

## Getting started

Install all workspace dependencies and run the available type checks:

```sh
npm ci
npm run typecheck
```

Before deployment, provision the shared D1 database and R2 bucket, the
contact-sheet Queues, and the required Access applications, then complete each
`wrangler.jsonc` for your environment.

Apply the D1 migration from the repository root:

```sh
# Local D1
npm run dev:migrate --workspace admin-worker

# Remote D1
npm run prod:migrate --workspace admin-worker
```

Run each service in its own terminal:

```sh
npm run dev --workspace admin-worker
npm run dev --workspace ingest-worker
npm run dev --workspace image-worker
npm start --workspace @sight-cache/collector
```

Create a device in the Admin Worker, save the Collector Token when it is shown,
then configure `apps/collector/.env` from `apps/collector/.env.example` with the Ingest
URL, token, and RTSP URL. See each component guide for its detailed
configuration and behavior.

After completing the account-specific configuration and applying the remote D1
migration, deploy the Workers:

```sh
npm run deploy --workspace admin-worker
npm run deploy --workspace ingest-worker
npm run deploy --workspace image-worker
```

## Security and retention

- Protect the Admin Worker and every route that can reach `/mcp` with the
  matching Cloudflare Access applications.
- Every identity admitted by the MCP Access policy can currently inspect every
  active device. Keep that policy restricted until per-device authorization is
  implemented.
- Before enabling image ingestion, consider configuring the shared R2 bucket
  with a lifecycle rule that expires objects after 14 days.

## Documentation

- [Collector](apps/collector/README.md)
- [Admin Worker](apps/admin-worker/README.md)
- [Ingest Worker](apps/ingest-worker/README.md)
- [Image Worker and MCP](apps/image-worker/README.md)