# page-diff-watch

> "Has the text of this public page (or one part of it) changed since I last looked?" The first call stores a baseline. Each later call fetches the page again and reports changed or not, which lines were added or removed, and a short rule-based summary including price-like tokens that moved.

- Status: MCP package (hosted access coming soon)

## What question it answers

"Has the text of this public page (or one part of it) changed since I last looked?" The first call stores a baseline. Each later call fetches the page again and reports changed or not, which lines were added or removed, and a short rule-based summary including price-like tokens that moved.

## When to use it

- Watching a pricing page, a changelog, a status note or a policy page for any text change.
- Narrowing to one part with a CSS selector (for example `main h1`).
- Pages that need JavaScript to render: set `js_render` (needs Chromium on the machine; otherwise it falls back to plain fetch and says so).

## When not to

- Not for LinkedIn, Facebook, Instagram, TikTok, X or Twitter (blocked on purpose), or private/internal addresses.
- Not for pages whose robots.txt disallows the path: you get `blockedByRobots:true` and no data.
- The diff is a set of changed lines; it ignores line order. A reordered page shows no change.
- The summary is regex-based. Price and number "shifts" are pairings by position, not proof.

## Example (MCP tool `check_page_change`, or CLI)

```bash
npx tsx src/cli.ts https://example.com/ --selector h1
```
Second call on a changed page, trimmed (shape from `samples/httpbin-uuid-change.json`):
```json
{
  "tool": "page-diff-watch",
  "version": "0.2.0",
  "url": "https://httpbin.org/uuid",
  "httpStatus": 200,
  "fetchMode": "static",
  "selector": null,
  "selectorMatched": null,
  "robots": { "allowed": true, "checked": true, "reason": "no matching disallow" },
  "firstCheck": false,
  "changed": true,
  "previousHash": "9601cf1c...",
  "hash": "db621474...",
  "changeRatio": 0.33,
  "addedLineCount": 1,
  "removedLineCount": 1,
  "summary": { "paragraph": "Page https://httpbin.org/uuid changed with 1 added and 1 removed line(s) ...", "pricesChanged": [], "numbersChanged": [] },
  "unifiedDiffPreview": "- ...\n+ ...",
  "billing": { "sku": "page_check", "plannedPriceUsd": 0.01, "x402Enabled": false }
}
```
First call: `firstCheck:true`, `changed:null`, summary "First baseline saved ...". If the page returns a server error or rate-limit status after a good baseline, you get `changed:null`, `baselinePreserved:true` and an `error` string instead of a fake change.

## Data source and licence

The page you name. The tool does not read the site's terms of use; it only checks robots.txt. You are responsible for having the right to monitor that page. The tool keeps the page's extracted text on disk as the baseline.

## Freshness, state and limits

- Every call fetches the live page (no result cache).
- Baselines live on the machine that runs the tool, per tenant name; if you share one tenant, you share baselines.
- Static fetch: 25 s timeout, 2 MB cap.
- Known gaps: a 404/410 response currently replaces the baseline; robots parsing is simplified and allows the fetch when robots.txt cannot be read or returns a server error.
- Browser mode has a known DNS-rebinding gap; use static mode for untrusted URLs.
