# storebound watch Store events for every app on this machine, for people and agents. Read-only on the stores. ``` storebound watch # loop, every 15 minutes storebound watch --once # one pass (cron, PM2 restarts) storebound watch --dir ./my-app # one folder instead of the machine's app list storebound watch --notify os,ntfy # choose the sinks (default: every configured one) storebound watch --dry --json # print the events that would go out; nothing is sent or saved storebound watch test # send one test event to every configured sink storebound events --since 24h --kind rejected --json ``` Apps come from the machine's app folder list (`storebound apps folders`, the same list as the desktop app: folders and store-only apps) or from `--dir`. Each app is read with the credentials its commands use (the project's `.storebound/`, its profile, or the store-only app's account). Each pass reads release state per store (like `release status`), crash / ANR health (like `health`, at most once an hour) and new reviews (like `reviews list`), compares with `~/.storebound/watch-state.json` and emits events. The first pass of an app only records the baseline: no event storm. A store that cannot be read in a pass keeps its last state and produces no event. ## Events | kind | when | | --- | --- | | `rejected` | a version went from in review to rejected (App Store; link to App Review) | | `approved` | a version passed review and waits for release (App Store pending developer release) | | `live` | a version became live, or its rollout started | | `rollout_halted` | a Play staged rollout was halted, or an App Store phased release paused | | `crash_threshold` | Play crash or ANR rate (28 d) crossed the bad behavior threshold; a new App Store metric or diagnostic rated poor | | `review_low` | a new review of 2 stars or less | | `review_needs_reply` | a new review of 3-5 stars without a reply | Each event is one JSON object: `{ at, app, store, kind, version, from, to, detail, url }`, where `url` is the absolute console link for the app. Events are appended to `~/.storebound/events.jsonl` (rotated at 5 MB, one `.1` file kept). `STOREBOUND_HOME` moves `~/.storebound`. `storebound events` and the MCP tool `events_recent` read the file. ## Sinks All optional. Configure with environment variables or `~/.storebound/watch.json`; values are never printed. | sink | variables | | --- | --- | | ntfy | `STOREBOUND_NTFY_TOPIC` (required), `STOREBOUND_NTFY_URL` (default `https://ntfy.sh`), `STOREBOUND_NTFY_TOKEN` (optional). Title, Tags and Click headers; Priority high for `rejected` and `rollout_halted` | | Telegram | `STOREBOUND_TELEGRAM_TOKEN` and `STOREBOUND_TELEGRAM_CHAT` | | webhook | `STOREBOUND_WEBHOOK_URL` (JSON POST of the event) | | ingest | `watch.json` `ingest` (or `STOREBOUND_INGEST_URL`, `STOREBOUND_INGEST_TOKEN_FILE`, `STOREBOUND_INGEST_CATEGORY`): JSON POST with `Authorization: Bearer `; the token is read from `tokenFile` at send time (`~` expanded, trimmed, never logged; on POSIX a group- or world-readable file is refused). Body: `category`, `event`, `app` (slug), `platform` (`ios`/`android`), `version`, `detail` (max 300 chars), `url`, `dedupeKey`, at most 8 KiB. 202 = sent; 5xx and network errors retry once, 4xx do not (status and server message are logged). At most 60 sends a minute, the rest are dropped with a warning | | OS notification | `--notify os`, or `"notify": ["os"]` in watch.json (Windows toast, macOS osascript, Linux notify-send; skipped quietly when unavailable) | ```json { "notify": ["os"], "ntfy": { "topic": "..." }, "telegram": { "token": "...", "chat": "..." }, "webhook": { "url": "..." }, "ingest": { "url": "https://push.example.com/api/notifications/ingest", "tokenFile": "~/.ingest-token", "category": "storebound" } } ``` Ingest events: `review-rejected`, `review-approved`, `live`, `rollout-halted`, `crash-threshold`, `low-reviews`, `review-reply`. Several sinks can be active at once (ntfy stays a fine fallback). Network calls time out after 10 s and are retried once. A failing sink is logged (by name) and never stops the watch. ## On a server with PM2 ``` pm2 start "npx -y storebound watch --notify ntfy" --name storebound-watch pm2 save ``` Put the credentials profiles (`~/.storebound/credentials/`) and the app folder list (`storebound apps add `) on that server, and the sink variables in the PM2 environment (`STOREBOUND_NTFY_TOPIC=...`). The loop polls every `--every` (5m at least, 15m default, +-10 % jitter), backs off after failed passes (up to 4 h), writes one heartbeat line per pass and stops cleanly on SIGINT / SIGTERM. With cron use `--once`.