Vibecode ArtificialWatch
track this build4 phases, 12 steps, beginner friendly0%The core loop is a genuine one-sitting build: poll the model-list endpoint of every provider you hold a key for, diff the ids against a local table, push the new ones to your phone. What does not one-shot is the three things a launch alert is actually judged on. Coverage · your script sees only the labs you have accounts with, while the watchlist here runs to 48 models including Chinese labs, restricted previews and things that have not shipped at all, which no API returns. Telephony · a call that rings until you answer means Twilio, a purchased number and US A2P 10DLC registration, which is days of paperwork before a line of code. And uptime, which is the whole product · a poller on a laptop that slept through the drop is worth nothing, and the second-sweep debounce that keeps preview aliases from crying wolf is the part you only tune after it has already cried wolf twice.
You are building a lean indie version of ArtificialWatch. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # ArtificialWatch · indie build A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node.js 22, one process under a supervisor on an always-on machine | a poller on a laptop that slept through the launch is worth nothing | | Schedule | A cron tick at a fixed interval, with one sweep per provider per tick | predictable load and a predictable answer to when you would have known | | Storage | better-sqlite3 holding every id ever seen and every sweep attempted | the sweep table is what proves the silence was real | | Providers | One adapter per provider, each paging to the end of its list | half a list is worse than no list, because it looks like it worked | | Alerting | A push topic with a delivery check, plus an append-only launch log | an alert that failed to send is the failure you will not notice | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the poller and the local page in one process. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An always-on machine with a supervisor** · a few pounds a month, or free on hardware you own - Why: The product is the alert arriving at seven in the morning, which a laptop that slept cannot deliver. - Get it: A small virtual server or a single-board computer at home, with a process supervisor configured to start on boot. - Verify: The watcher is running after a reboot with no manual step - [ ] **API keys for the providers you want covered** · listing models is typically free, but check each provider's terms - Why: Coverage is exactly the set of providers you hold a key for, which is the tool's main limitation and should be visible from the start. - Get it: Create a read-scoped key per provider where the provider supports scoping. - Verify: Each adapter lists models and records a successful sweep - [ ] **A push topic or notification service** · free tiers are common - Why: The alert has to reach a phone, and the delivery result has to be observable rather than assumed. - Get it: Create a topic on a push service and subscribe your phone to it. - Verify: A test alert arrives and its delivery is recorded - [ ] **An external check on the heartbeat** · free tiers are common - Why: Something outside the process must notice when it stops, because the failure mode of this product is silence. - Get it: Any uptime checker that can call a URL or expect a periodic ping. - Verify: Stopping the process produces an external alert within the stated window - [ ] **Your alias and snapshot patterns** · free - Why: These decide which ids never fire, and writing them before the first false alarm is what keeps you from muting the topic after it. - Get it: List the dated snapshot, preview and moving alias shapes each provider uses, with a reason per pattern. - Verify: Each pattern is stored with its reason and visible on the suppressed list ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. - coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews - the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration - the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return - debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms - someone else owning the uptime · your poller sleeps when your machine does If one of those is essential to you, that is the reason to keep paying for ArtificialWatch, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ArtificialWatch The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a model-launch watcher that pages you once, correctly, the first time a new model id appears to replace ArtificialWatch. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node.js 22, one process under a supervisor on an always-on machine · a poller on a laptop that slept through the launch is worth nothing - Schedule: A cron tick at a fixed interval, with one sweep per provider per tick · predictable load and a predictable answer to when you would have known - Storage: better-sqlite3 holding every id ever seen and every sweep attempted · the sweep table is what proves the silence was real - Providers: One adapter per provider, each paging to the end of its list · half a list is worse than no list, because it looks like it worked - Alerting: A push topic with a delivery check, plus an append-only launch log · an alert that failed to send is the failure you will not notice ### Data model (create this before Phase 1) - providers(id, name, base_url, key_env, enabled, page_limit, last_good_sweep_at, consecutive_errors) - sweeps(id, provider_id, started_at, finished_at, pages_fetched, ids_seen, http_status, error) · one row per attempt, successful or not - model_ids(provider_id, model_id, first_seen_at, first_seen_sweep_id, confirmed_at, announced_at, suppressed_reason) - alerts(id, provider_id, model_id, created_at, delivered_at, attempts, last_error, dedupe_key) - filters(id, pattern, reason, added_at) · alias and dated snapshot patterns that never fire - heartbeats(at, providers_ok, providers_failing) · written every tick, read by whatever watches the watcher - A sweep row exists before the request is made, so a process that dies mid-sweep leaves evidence rather than a gap that looks like a quiet minute. ### Phase 1 · Every id a provider exposes, not the first page of them Build: Write one adapter per provider you hold a key for. Follow each provider's own pagination to exhaustion: Anthropic's model list, for instance, documents a limit parameter that defaults to twenty and ranges up to a thousand, with has_more and a last_id cursor to follow. Record pages fetched and ids seen on the sweep row, and treat a sweep that stopped with more pages available as a failure rather than a result. Trap: Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. Done when: Each adapter pages until the provider reports no more results, tested against a fake provider with three pages. A sweep that ends with pages remaining is recorded as failed and raises no launches, tested. The sweep row records pages fetched and ids seen for every attempt including failures. The first run seeds every id silently and raises nothing, tested on an empty database. Do not build yet: No alerting, filtering, dashboard or supervision yet. ### Phase 2 · A launch is an id you have never seen, twice Build: Compare each sweep's ids against the stored set. A new id is recorded immediately but confirmed only when a second consecutive successful sweep still contains it. Keep a pattern list for dated snapshots, preview suffixes and moving aliases, each with a written reason, and suppress matching ids with that reason recorded rather than dropping them silently. Trap: Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. Done when: An id present in one sweep and absent in the next is never alerted and is recorded as unconfirmed, tested. A suppressed id stores which pattern matched it and why, and appears in a suppressed list rather than vanishing. Confirmation requires two consecutive successful sweeps, so a failed sweep between them does not count, tested. Removing a pattern makes previously suppressed ids eligible without re-alerting historical ones. Do not build yet: No alert delivery, dashboard or supervision yet. ### Phase 3 · An alert that was actually delivered Build: Send a push carrying the model id, the provider and whatever context the list itself gave you. Use a dedupe key of provider and model id so a retry cannot produce a second message. Retry a failed delivery with backoff, record attempts and the last error, and append every confirmed launch to a plain text log regardless of whether the push succeeded. Trap: Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. Done when: A delivery failure is retried with backoff and the attempts and last error are stored, tested against a failing fake. The same launch cannot produce two messages, tested by forcing a retry after a successful send. Every confirmed launch is appended to the text log even when delivery fails, verified by disabling the push target. The alert body contains only fields the provider's own response supplied, with no invented context. Do not build yet: No dashboard, heartbeat or supervision yet. ### Phase 4 · Proving the silence is real Build: One local page showing recent launches, the last successful sweep per provider with its age, and a red state for any provider erroring beyond a stated window. Write a heartbeat every tick and expose it so an external checker can notice the process stopped. Honour rate limit responses and their retry hints with backoff rather than hammering. Trap: A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. Done when: The page shows the age of the last successful sweep for every provider and turns red past the stated threshold, tested by disabling a key. A heartbeat is written every tick and the README explains how to point an external checker at it. A rate limited response backs off according to the provider's hint and the sweep is recorded as rate limited rather than failed, tested. A provider erroring continuously raises one alert about itself rather than one per tick. Do not build yet: No supervision, key rotation or coverage statement yet. ### Phase 5 · Uptime, keys, and saying what you cannot see Build: Run under a supervisor that restarts on crash and on boot, and confirm by rebooting the machine. Handle key rotation without losing the seen-id history. Print the covered providers and, just as prominently, the ones you have no key for, since a launch alerter that implies coverage it does not have is worse than one that admits the gap. Trap: Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. Done when: The process restarts automatically after a crash and after a reboot, confirmed by rebooting the machine. Rotating a key leaves the seen-id table intact and raises no launches, tested. The page and the alert footer both name the covered providers and state that anything else is not watched. Restoring the database from a backup does not re-alert models already recorded as confirmed, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Phone calls and text messages, which mean a telephony account, a purchased number and messaging registration paperwork before a single line of code. - A watchlist of models that have not shipped, which no endpoint can return and which would be a hand-maintained list dressed up as a feed. - Scraping provider blogs and pricing pages, which breaks whenever a page changes and may sit outside a site's terms; a page-change monitor is the honest tool for that job. - Benchmark scores or quality rankings on launch day, since nothing credible exists within hours of a release. ### README must contain - That each provider adapter pages to the end of its list, and that a truncated sweep is treated as a failure - That a new id must appear in two consecutive successful sweeps before it alerts, and why - How suppression patterns work, where suppressed ids are visible, and how to remove a pattern safely - How to point an external checker at the heartbeat, because a dead watcher looks exactly like a quiet week - Which providers are covered, which are not, and that the uncovered list is the honest limit of the tool ===== AGENTS.md ===== # Agent instructions · ArtificialWatch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node.js 22, one process under a supervisor on an always-on machine, A cron tick at a fixed interval, with one sweep per provider per tick, better-sqlite3 holding every id ever seen and every sweep attempted, One adapter per provider, each paging to the end of its list, A push topic with a delivery check, plus an append-only launch log. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ===== BUILD_PLAN.md ===== # Build plan · ArtificialWatch A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Every id a provider exposes, not the first page of them Each adapter pages to the end of the list and records how many pages it read. ### Steps 1. Follow each provider's cursor to the end The default page size is the number that will embarrass you. Files: `src/providers/anthropic.js`, `src/providers/openai.js`, `src/providers/openrouter.js` 2. Fail a truncated sweep rather than using it A partial list is not a smaller list, it is a wrong one. Files: `src/sweep.js`, `test/pagination.test.js` 3. Seed silently on first run Otherwise the first tick pages you about every model that exists. Files: `src/seed.js` ### Done when - [ ] Adapters page to exhaustion against a multi-page fake - [ ] A truncated sweep is recorded as failed - [ ] Sweep rows carry pages fetched and ids seen - [ ] The first run raises nothing ### Watch out - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. ## Phase 2 · A launch is an id you have never seen, twice New ids are confirmed across two sweeps and known alias patterns never fire. ### Steps 1. Confirm across two successful sweeps A failed sweep in between must not count as agreement. Files: `src/diff.js`, `test/debounce.test.js` 2. Store the reason a pattern suppressed an id You will need to know why the launch you missed was filtered. Files: `src/filters.js` 3. Keep suppressed ids visible Silently dropped ids are how a watcher goes quietly wrong. Files: `src/web/suppressed.js` ### Done when - [ ] A one-sweep appearance never alerts - [ ] Suppressions record the pattern and reason - [ ] A failed sweep breaks the confirmation pair - [ ] Removing a pattern does not re-alert history ### Watch out - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. ## Phase 3 · An alert that was actually delivered Every confirmed launch reaches the phone once, and a failed delivery is retried and visible. ### Steps 1. Dedupe on provider and model id The retry path is where duplicates come from. Files: `src/alert.js` 2. Record every delivery attempt An alert you cannot prove was delivered was not delivered. Files: `src/db/alerts.js`, `test/delivery.test.js` 3. Append to the log regardless The file is the record that survives every outage. Files: `src/log.js` ### Done when - [ ] Failed deliveries retry with backoff and are recorded - [ ] A retry after success sends nothing - [ ] The text log gets every confirmed launch - [ ] Alert bodies carry no invented context ### Watch out - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. ## Phase 4 · Proving the silence is real A quiet morning is distinguishable from a dead watcher at a glance. ### Steps 1. Show the age of the last good sweep The single most useful number on the page. Files: `src/web/dashboard.js` 2. Write a heartbeat and document the external check Something outside this process has to watch it. Files: `src/heartbeat.js` 3. Back off on rate limits, and say you did Getting yourself rate limited during a launch is the wrong day for it. Files: `src/http.js`, `test/backoff.test.js` ### Done when - [ ] Last good sweep age is shown per provider and goes red - [ ] A heartbeat is written every tick - [ ] Rate limits back off and are recorded distinctly - [ ] A failing provider alerts once, not per tick ### Watch out - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ## Not in this build - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. ## After v1, if you want it - A second push destination, so a muted topic is not a single point of failure - Context enrichment from an open model database, kept clearly separate from what the provider returned - A weekly digest of suppressed ids, which is how you find a pattern that is filtering too much ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Seen ids, sweeps, alerts and heartbeats. DATABASE_PATH=./data/watcher.db # Required. One sweep per provider per tick. Check each provider's rate limits before lowering it. SWEEP_INTERVAL_SECONDS=60 # Optional · secret. One provider adapter. Absent means that provider is listed as not covered. ANTHROPIC_API_KEY=sk-ant-...redacted # Optional · secret. Another adapter, same rule about coverage. OPENAI_API_KEY=sk-...redacted # Optional · secret. Covers models from labs you hold no direct account with. OPENROUTER_API_KEY=sk-or-...redacted # Required · secret. Treat the topic name as a secret; anyone with it can post to your phone. PUSH_TOPIC_URL=https://ntfy.sh/your-private-topic # Required. How long a provider may error before the watcher alerts about itself. PROVIDER_ERROR_ALERT_MINUTES=10 # Required. Append-only record, written even when the push fails. LAUNCH_LOG_PATH=./data/launches.md
You are building a lean indie version of ArtificialWatch. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # ArtificialWatch · indie build A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node.js 22, one process under a supervisor on an always-on machine | a poller on a laptop that slept through the launch is worth nothing | | Schedule | A cron tick at a fixed interval, with one sweep per provider per tick | predictable load and a predictable answer to when you would have known | | Storage | better-sqlite3 holding every id ever seen and every sweep attempted | the sweep table is what proves the silence was real | | Providers | One adapter per provider, each paging to the end of its list | half a list is worse than no list, because it looks like it worked | | Alerting | A push topic with a delivery check, plus an append-only launch log | an alert that failed to send is the failure you will not notice | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the poller and the local page in one process. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An always-on machine with a supervisor** · a few pounds a month, or free on hardware you own - Why: The product is the alert arriving at seven in the morning, which a laptop that slept cannot deliver. - Get it: A small virtual server or a single-board computer at home, with a process supervisor configured to start on boot. - Verify: The watcher is running after a reboot with no manual step - [ ] **API keys for the providers you want covered** · listing models is typically free, but check each provider's terms - Why: Coverage is exactly the set of providers you hold a key for, which is the tool's main limitation and should be visible from the start. - Get it: Create a read-scoped key per provider where the provider supports scoping. - Verify: Each adapter lists models and records a successful sweep - [ ] **A push topic or notification service** · free tiers are common - Why: The alert has to reach a phone, and the delivery result has to be observable rather than assumed. - Get it: Create a topic on a push service and subscribe your phone to it. - Verify: A test alert arrives and its delivery is recorded - [ ] **An external check on the heartbeat** · free tiers are common - Why: Something outside the process must notice when it stops, because the failure mode of this product is silence. - Get it: Any uptime checker that can call a URL or expect a periodic ping. - Verify: Stopping the process produces an external alert within the stated window - [ ] **Your alias and snapshot patterns** · free - Why: These decide which ids never fire, and writing them before the first false alarm is what keeps you from muting the topic after it. - Get it: List the dated snapshot, preview and moving alias shapes each provider uses, with a reason per pattern. - Verify: Each pattern is stored with its reason and visible on the suppressed list ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. - coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews - the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration - the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return - debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms - someone else owning the uptime · your poller sleeps when your machine does If one of those is essential to you, that is the reason to keep paying for ArtificialWatch, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ArtificialWatch The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a model-launch watcher that pages you once, correctly, the first time a new model id appears to replace ArtificialWatch. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node.js 22, one process under a supervisor on an always-on machine · a poller on a laptop that slept through the launch is worth nothing - Schedule: A cron tick at a fixed interval, with one sweep per provider per tick · predictable load and a predictable answer to when you would have known - Storage: better-sqlite3 holding every id ever seen and every sweep attempted · the sweep table is what proves the silence was real - Providers: One adapter per provider, each paging to the end of its list · half a list is worse than no list, because it looks like it worked - Alerting: A push topic with a delivery check, plus an append-only launch log · an alert that failed to send is the failure you will not notice ### Data model (create this before Phase 1) - providers(id, name, base_url, key_env, enabled, page_limit, last_good_sweep_at, consecutive_errors) - sweeps(id, provider_id, started_at, finished_at, pages_fetched, ids_seen, http_status, error) · one row per attempt, successful or not - model_ids(provider_id, model_id, first_seen_at, first_seen_sweep_id, confirmed_at, announced_at, suppressed_reason) - alerts(id, provider_id, model_id, created_at, delivered_at, attempts, last_error, dedupe_key) - filters(id, pattern, reason, added_at) · alias and dated snapshot patterns that never fire - heartbeats(at, providers_ok, providers_failing) · written every tick, read by whatever watches the watcher - A sweep row exists before the request is made, so a process that dies mid-sweep leaves evidence rather than a gap that looks like a quiet minute. ### Phase 1 · Every id a provider exposes, not the first page of them Build: Write one adapter per provider you hold a key for. Follow each provider's own pagination to exhaustion: Anthropic's model list, for instance, documents a limit parameter that defaults to twenty and ranges up to a thousand, with has_more and a last_id cursor to follow. Record pages fetched and ids seen on the sweep row, and treat a sweep that stopped with more pages available as a failure rather than a result. Trap: Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. Done when: Each adapter pages until the provider reports no more results, tested against a fake provider with three pages. A sweep that ends with pages remaining is recorded as failed and raises no launches, tested. The sweep row records pages fetched and ids seen for every attempt including failures. The first run seeds every id silently and raises nothing, tested on an empty database. Do not build yet: No alerting, filtering, dashboard or supervision yet. ### Phase 2 · A launch is an id you have never seen, twice Build: Compare each sweep's ids against the stored set. A new id is recorded immediately but confirmed only when a second consecutive successful sweep still contains it. Keep a pattern list for dated snapshots, preview suffixes and moving aliases, each with a written reason, and suppress matching ids with that reason recorded rather than dropping them silently. Trap: Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. Done when: An id present in one sweep and absent in the next is never alerted and is recorded as unconfirmed, tested. A suppressed id stores which pattern matched it and why, and appears in a suppressed list rather than vanishing. Confirmation requires two consecutive successful sweeps, so a failed sweep between them does not count, tested. Removing a pattern makes previously suppressed ids eligible without re-alerting historical ones. Do not build yet: No alert delivery, dashboard or supervision yet. ### Phase 3 · An alert that was actually delivered Build: Send a push carrying the model id, the provider and whatever context the list itself gave you. Use a dedupe key of provider and model id so a retry cannot produce a second message. Retry a failed delivery with backoff, record attempts and the last error, and append every confirmed launch to a plain text log regardless of whether the push succeeded. Trap: Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. Done when: A delivery failure is retried with backoff and the attempts and last error are stored, tested against a failing fake. The same launch cannot produce two messages, tested by forcing a retry after a successful send. Every confirmed launch is appended to the text log even when delivery fails, verified by disabling the push target. The alert body contains only fields the provider's own response supplied, with no invented context. Do not build yet: No dashboard, heartbeat or supervision yet. ### Phase 4 · Proving the silence is real Build: One local page showing recent launches, the last successful sweep per provider with its age, and a red state for any provider erroring beyond a stated window. Write a heartbeat every tick and expose it so an external checker can notice the process stopped. Honour rate limit responses and their retry hints with backoff rather than hammering. Trap: A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. Done when: The page shows the age of the last successful sweep for every provider and turns red past the stated threshold, tested by disabling a key. A heartbeat is written every tick and the README explains how to point an external checker at it. A rate limited response backs off according to the provider's hint and the sweep is recorded as rate limited rather than failed, tested. A provider erroring continuously raises one alert about itself rather than one per tick. Do not build yet: No supervision, key rotation or coverage statement yet. ### Phase 5 · Uptime, keys, and saying what you cannot see Build: Run under a supervisor that restarts on crash and on boot, and confirm by rebooting the machine. Handle key rotation without losing the seen-id history. Print the covered providers and, just as prominently, the ones you have no key for, since a launch alerter that implies coverage it does not have is worse than one that admits the gap. Trap: Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. Done when: The process restarts automatically after a crash and after a reboot, confirmed by rebooting the machine. Rotating a key leaves the seen-id table intact and raises no launches, tested. The page and the alert footer both name the covered providers and state that anything else is not watched. Restoring the database from a backup does not re-alert models already recorded as confirmed, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Phone calls and text messages, which mean a telephony account, a purchased number and messaging registration paperwork before a single line of code. - A watchlist of models that have not shipped, which no endpoint can return and which would be a hand-maintained list dressed up as a feed. - Scraping provider blogs and pricing pages, which breaks whenever a page changes and may sit outside a site's terms; a page-change monitor is the honest tool for that job. - Benchmark scores or quality rankings on launch day, since nothing credible exists within hours of a release. ### README must contain - That each provider adapter pages to the end of its list, and that a truncated sweep is treated as a failure - That a new id must appear in two consecutive successful sweeps before it alerts, and why - How suppression patterns work, where suppressed ids are visible, and how to remove a pattern safely - How to point an external checker at the heartbeat, because a dead watcher looks exactly like a quiet week - Which providers are covered, which are not, and that the uncovered list is the honest limit of the tool ===== AGENTS.md ===== # Agent instructions · ArtificialWatch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node.js 22, one process under a supervisor on an always-on machine, A cron tick at a fixed interval, with one sweep per provider per tick, better-sqlite3 holding every id ever seen and every sweep attempted, One adapter per provider, each paging to the end of its list, A push topic with a delivery check, plus an append-only launch log. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ===== BUILD_PLAN.md ===== # Build plan · ArtificialWatch A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Every id a provider exposes, not the first page of them Each adapter pages to the end of the list and records how many pages it read. ### Steps 1. Follow each provider's cursor to the end The default page size is the number that will embarrass you. Files: `src/providers/anthropic.js`, `src/providers/openai.js`, `src/providers/openrouter.js` 2. Fail a truncated sweep rather than using it A partial list is not a smaller list, it is a wrong one. Files: `src/sweep.js`, `test/pagination.test.js` 3. Seed silently on first run Otherwise the first tick pages you about every model that exists. Files: `src/seed.js` ### Done when - [ ] Adapters page to exhaustion against a multi-page fake - [ ] A truncated sweep is recorded as failed - [ ] Sweep rows carry pages fetched and ids seen - [ ] The first run raises nothing ### Watch out - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. ## Phase 2 · A launch is an id you have never seen, twice New ids are confirmed across two sweeps and known alias patterns never fire. ### Steps 1. Confirm across two successful sweeps A failed sweep in between must not count as agreement. Files: `src/diff.js`, `test/debounce.test.js` 2. Store the reason a pattern suppressed an id You will need to know why the launch you missed was filtered. Files: `src/filters.js` 3. Keep suppressed ids visible Silently dropped ids are how a watcher goes quietly wrong. Files: `src/web/suppressed.js` ### Done when - [ ] A one-sweep appearance never alerts - [ ] Suppressions record the pattern and reason - [ ] A failed sweep breaks the confirmation pair - [ ] Removing a pattern does not re-alert history ### Watch out - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. ## Phase 3 · An alert that was actually delivered Every confirmed launch reaches the phone once, and a failed delivery is retried and visible. ### Steps 1. Dedupe on provider and model id The retry path is where duplicates come from. Files: `src/alert.js` 2. Record every delivery attempt An alert you cannot prove was delivered was not delivered. Files: `src/db/alerts.js`, `test/delivery.test.js` 3. Append to the log regardless The file is the record that survives every outage. Files: `src/log.js` ### Done when - [ ] Failed deliveries retry with backoff and are recorded - [ ] A retry after success sends nothing - [ ] The text log gets every confirmed launch - [ ] Alert bodies carry no invented context ### Watch out - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. ## Phase 4 · Proving the silence is real A quiet morning is distinguishable from a dead watcher at a glance. ### Steps 1. Show the age of the last good sweep The single most useful number on the page. Files: `src/web/dashboard.js` 2. Write a heartbeat and document the external check Something outside this process has to watch it. Files: `src/heartbeat.js` 3. Back off on rate limits, and say you did Getting yourself rate limited during a launch is the wrong day for it. Files: `src/http.js`, `test/backoff.test.js` ### Done when - [ ] Last good sweep age is shown per provider and goes red - [ ] A heartbeat is written every tick - [ ] Rate limits back off and are recorded distinctly - [ ] A failing provider alerts once, not per tick ### Watch out - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ## Not in this build - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. ## After v1, if you want it - A second push destination, so a muted topic is not a single point of failure - Context enrichment from an open model database, kept clearly separate from what the provider returned - A weekly digest of suppressed ids, which is how you find a pattern that is filtering too much ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Seen ids, sweeps, alerts and heartbeats. DATABASE_PATH=./data/watcher.db # Required. One sweep per provider per tick. Check each provider's rate limits before lowering it. SWEEP_INTERVAL_SECONDS=60 # Optional · secret. One provider adapter. Absent means that provider is listed as not covered. ANTHROPIC_API_KEY=sk-ant-...redacted # Optional · secret. Another adapter, same rule about coverage. OPENAI_API_KEY=sk-...redacted # Optional · secret. Covers models from labs you hold no direct account with. OPENROUTER_API_KEY=sk-or-...redacted # Required · secret. Treat the topic name as a secret; anyone with it can post to your phone. PUSH_TOPIC_URL=https://ntfy.sh/your-private-topic # Required. How long a provider may error before the watcher alerts about itself. PROVIDER_ERROR_ALERT_MINUTES=10 # Required. Append-only record, written even when the push fails. LAUNCH_LOG_PATH=./data/launches.md
You are building a production product version of ArtificialWatch. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # ArtificialWatch · product brief ## Problem The core loop is a genuine one-sitting build: poll the model-list endpoint of every provider you hold a key for, diff the ids against a local table, push the new ones to your phone. What does not one-shot is the three things a launch alert is actually judged on. Coverage · your script sees only the labs you have accounts with, while the watchlist here runs to 48 models including Chinese labs, restricted previews and things that have not shipped at all, which no API returns. Telephony · a call that rings until you answer means Twilio, a purchased number and US A2P 10DLC registration, which is days of paperwork before a line of code. And uptime, which is the whole product · a poller on a laptop that slept through the drop is worth nothing, and the second-sweep debounce that keeps preview aliases from crying wolf is the part you only tune after it has already cried wolf twice. ## Product outcome A watcher that sees every id a provider exposes rather than the first page, fires once for a real launch and not at all for a renamed alias, proves its own silence is genuine, and tells you plainly which labs it cannot see. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - API keys for the providers you want covered (OpenAI, Anthropic, Google, OpenRouter) - an always-on box · a small VPS or a Pi, not a laptop - an ntfy or Pushover topic for push ## Explicit non-goals for v1 - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. - coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews - the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration - the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return - debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms - someone else owning the uptime · your poller sleeps when your machine does ## Success criteria - Adapters are confirmed to page to exhaustion - A single-sweep appearance is confirmed never to alert - A failed delivery is confirmed retried and visible - A dead process is confirmed detectable from outside - A reboot is confirmed to bring the watcher back ===== BRIEF.md ===== # Build brief · ArtificialWatch The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a model-launch watcher that pages you once, correctly, the first time a new model id appears to replace ArtificialWatch. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node.js 22, one process under a supervisor on an always-on machine · a poller on a laptop that slept through the launch is worth nothing - Schedule: A cron tick at a fixed interval, with one sweep per provider per tick · predictable load and a predictable answer to when you would have known - Storage: better-sqlite3 holding every id ever seen and every sweep attempted · the sweep table is what proves the silence was real - Providers: One adapter per provider, each paging to the end of its list · half a list is worse than no list, because it looks like it worked - Alerting: A push topic with a delivery check, plus an append-only launch log · an alert that failed to send is the failure you will not notice ### Data model (create this before Phase 1) - providers(id, name, base_url, key_env, enabled, page_limit, last_good_sweep_at, consecutive_errors) - sweeps(id, provider_id, started_at, finished_at, pages_fetched, ids_seen, http_status, error) · one row per attempt, successful or not - model_ids(provider_id, model_id, first_seen_at, first_seen_sweep_id, confirmed_at, announced_at, suppressed_reason) - alerts(id, provider_id, model_id, created_at, delivered_at, attempts, last_error, dedupe_key) - filters(id, pattern, reason, added_at) · alias and dated snapshot patterns that never fire - heartbeats(at, providers_ok, providers_failing) · written every tick, read by whatever watches the watcher - A sweep row exists before the request is made, so a process that dies mid-sweep leaves evidence rather than a gap that looks like a quiet minute. ### Phase 1 · Every id a provider exposes, not the first page of them Build: Write one adapter per provider you hold a key for. Follow each provider's own pagination to exhaustion: Anthropic's model list, for instance, documents a limit parameter that defaults to twenty and ranges up to a thousand, with has_more and a last_id cursor to follow. Record pages fetched and ids seen on the sweep row, and treat a sweep that stopped with more pages available as a failure rather than a result. Trap: Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. Done when: Each adapter pages until the provider reports no more results, tested against a fake provider with three pages. A sweep that ends with pages remaining is recorded as failed and raises no launches, tested. The sweep row records pages fetched and ids seen for every attempt including failures. The first run seeds every id silently and raises nothing, tested on an empty database. Do not build yet: No alerting, filtering, dashboard or supervision yet. ### Phase 2 · A launch is an id you have never seen, twice Build: Compare each sweep's ids against the stored set. A new id is recorded immediately but confirmed only when a second consecutive successful sweep still contains it. Keep a pattern list for dated snapshots, preview suffixes and moving aliases, each with a written reason, and suppress matching ids with that reason recorded rather than dropping them silently. Trap: Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. Done when: An id present in one sweep and absent in the next is never alerted and is recorded as unconfirmed, tested. A suppressed id stores which pattern matched it and why, and appears in a suppressed list rather than vanishing. Confirmation requires two consecutive successful sweeps, so a failed sweep between them does not count, tested. Removing a pattern makes previously suppressed ids eligible without re-alerting historical ones. Do not build yet: No alert delivery, dashboard or supervision yet. ### Phase 3 · An alert that was actually delivered Build: Send a push carrying the model id, the provider and whatever context the list itself gave you. Use a dedupe key of provider and model id so a retry cannot produce a second message. Retry a failed delivery with backoff, record attempts and the last error, and append every confirmed launch to a plain text log regardless of whether the push succeeded. Trap: Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. Done when: A delivery failure is retried with backoff and the attempts and last error are stored, tested against a failing fake. The same launch cannot produce two messages, tested by forcing a retry after a successful send. Every confirmed launch is appended to the text log even when delivery fails, verified by disabling the push target. The alert body contains only fields the provider's own response supplied, with no invented context. Do not build yet: No dashboard, heartbeat or supervision yet. ### Phase 4 · Proving the silence is real Build: One local page showing recent launches, the last successful sweep per provider with its age, and a red state for any provider erroring beyond a stated window. Write a heartbeat every tick and expose it so an external checker can notice the process stopped. Honour rate limit responses and their retry hints with backoff rather than hammering. Trap: A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. Done when: The page shows the age of the last successful sweep for every provider and turns red past the stated threshold, tested by disabling a key. A heartbeat is written every tick and the README explains how to point an external checker at it. A rate limited response backs off according to the provider's hint and the sweep is recorded as rate limited rather than failed, tested. A provider erroring continuously raises one alert about itself rather than one per tick. Do not build yet: No supervision, key rotation or coverage statement yet. ### Phase 5 · Uptime, keys, and saying what you cannot see Build: Run under a supervisor that restarts on crash and on boot, and confirm by rebooting the machine. Handle key rotation without losing the seen-id history. Print the covered providers and, just as prominently, the ones you have no key for, since a launch alerter that implies coverage it does not have is worse than one that admits the gap. Trap: Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. Done when: The process restarts automatically after a crash and after a reboot, confirmed by rebooting the machine. Rotating a key leaves the seen-id table intact and raises no launches, tested. The page and the alert footer both name the covered providers and state that anything else is not watched. Restoring the database from a backup does not re-alert models already recorded as confirmed, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Phone calls and text messages, which mean a telephony account, a purchased number and messaging registration paperwork before a single line of code. - A watchlist of models that have not shipped, which no endpoint can return and which would be a hand-maintained list dressed up as a feed. - Scraping provider blogs and pricing pages, which breaks whenever a page changes and may sit outside a site's terms; a page-change monitor is the honest tool for that job. - Benchmark scores or quality rankings on launch day, since nothing credible exists within hours of a release. ### README must contain - That each provider adapter pages to the end of its list, and that a truncated sweep is treated as a failure - That a new id must appear in two consecutive successful sweeps before it alerts, and why - How suppression patterns work, where suppressed ids are visible, and how to remove a pattern safely - How to point an external checker at the heartbeat, because a dead watcher looks exactly like a quiet week - Which providers are covered, which are not, and that the uncovered list is the honest limit of the tool ===== ARCHITECTURE.md ===== # Architecture · ArtificialWatch ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node.js 22, one process under a supervisor on an always-on machine | a poller on a laptop that slept through the launch is worth nothing | | Schedule | A cron tick at a fixed interval, with one sweep per provider per tick | predictable load and a predictable answer to when you would have known | | Storage | better-sqlite3 holding every id ever seen and every sweep attempted | the sweep table is what proves the silence was real | | Providers | One adapter per provider, each paging to the end of its list | half a list is worse than no list, because it looks like it worked | | Alerting | A push topic with a delivery check, plus an append-only launch log | an alert that failed to send is the failure you will not notice | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Adapters | One provider each, paging to exhaustion. | Any provider added as an adapter, with its own pagination. | | Diff | Seen ids, confirmation across sweeps and suppression. | Any comparison, provided a single sighting never alerts. | | Alerting | Dedupe, retries and the append-only log. | Any transport, provided delivery is recorded rather than assumed. | | Liveness | Heartbeat, sweep ages and error states. | Any external checker, provided something outside the process watches it. | | Supervision | Restart on crash and on boot. | Any supervisor, tested by an actual reboot. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Seen ids, sweeps, alerts and heartbeats. - `SWEEP_INTERVAL_SECONDS` · required · One sweep per provider per tick. Check each provider's rate limits before lowering it. - `ANTHROPIC_API_KEY` · optional, secret · One provider adapter. Absent means that provider is listed as not covered. - `OPENAI_API_KEY` · optional, secret · Another adapter, same rule about coverage. - `OPENROUTER_API_KEY` · optional, secret · Covers models from labs you hold no direct account with. - `PUSH_TOPIC_URL` · required, secret · Treat the topic name as a secret; anyone with it can post to your phone. - `PROVIDER_ERROR_ALERT_MINUTES` · required · How long a provider may error before the watcher alerts about itself. - `LAUNCH_LOG_PATH` · required · Append-only record, written even when the push fails. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · ArtificialWatch product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node.js 22, one process under a supervisor on an always-on machine, A cron tick at a fixed interval, with one sweep per provider per tick, better-sqlite3 holding every id ever seen and every sweep attempted, One adapter per provider, each paging to the end of its list, A push topic with a delivery check, plus an append-only launch log. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. - Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. ===== MILESTONES.md ===== # Delivery milestones · ArtificialWatch Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Every id a provider exposes, not the first page of them Each adapter pages to the end of the list and records how many pages it read. ### Steps 1. Follow each provider's cursor to the end The default page size is the number that will embarrass you. Files: `src/providers/anthropic.js`, `src/providers/openai.js`, `src/providers/openrouter.js` 2. Fail a truncated sweep rather than using it A partial list is not a smaller list, it is a wrong one. Files: `src/sweep.js`, `test/pagination.test.js` 3. Seed silently on first run Otherwise the first tick pages you about every model that exists. Files: `src/seed.js` ### Done when - [ ] Adapters page to exhaustion against a multi-page fake - [ ] A truncated sweep is recorded as failed - [ ] Sweep rows carry pages fetched and ids seen - [ ] The first run raises nothing ### Watch out - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. ## M2 · A launch is an id you have never seen, twice New ids are confirmed across two sweeps and known alias patterns never fire. ### Steps 1. Confirm across two successful sweeps A failed sweep in between must not count as agreement. Files: `src/diff.js`, `test/debounce.test.js` 2. Store the reason a pattern suppressed an id You will need to know why the launch you missed was filtered. Files: `src/filters.js` 3. Keep suppressed ids visible Silently dropped ids are how a watcher goes quietly wrong. Files: `src/web/suppressed.js` ### Done when - [ ] A one-sweep appearance never alerts - [ ] Suppressions record the pattern and reason - [ ] A failed sweep breaks the confirmation pair - [ ] Removing a pattern does not re-alert history ### Watch out - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. ## M3 · An alert that was actually delivered Every confirmed launch reaches the phone once, and a failed delivery is retried and visible. ### Steps 1. Dedupe on provider and model id The retry path is where duplicates come from. Files: `src/alert.js` 2. Record every delivery attempt An alert you cannot prove was delivered was not delivered. Files: `src/db/alerts.js`, `test/delivery.test.js` 3. Append to the log regardless The file is the record that survives every outage. Files: `src/log.js` ### Done when - [ ] Failed deliveries retry with backoff and are recorded - [ ] A retry after success sends nothing - [ ] The text log gets every confirmed launch - [ ] Alert bodies carry no invented context ### Watch out - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. ## M4 · Proving the silence is real A quiet morning is distinguishable from a dead watcher at a glance. ### Steps 1. Show the age of the last good sweep The single most useful number on the page. Files: `src/web/dashboard.js` 2. Write a heartbeat and document the external check Something outside this process has to watch it. Files: `src/heartbeat.js` 3. Back off on rate limits, and say you did Getting yourself rate limited during a launch is the wrong day for it. Files: `src/http.js`, `test/backoff.test.js` ### Done when - [ ] Last good sweep age is shown per provider and goes red - [ ] A heartbeat is written every tick - [ ] Rate limits back off and are recorded distinctly - [ ] A failing provider alerts once, not per tick ### Watch out - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ## M5 · Uptime, keys, and saying what you cannot see (production only) The watcher survives reboots and states plainly which labs it covers. ### Steps 1. Restart on crash and on boot, then reboot to check Supervisor configuration that has never been tested is decoration. Files: `ops/supervisor.md` 2. Rotate keys without losing history The seen-id table is the only thing that stops a replay of every launch. Files: `src/providers/keys.js` 3. State the coverage boundary in every alert So the gap travels with the message rather than living in a README. Files: `src/alert.js` ### Done when - [ ] A reboot brings the watcher back automatically - [ ] Key rotation preserves the seen-id history - [ ] Coverage and its gaps appear on the page and in alerts - [ ] A restore does not re-alert confirmed models ### Watch out - Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. ===== OPERATIONS.md ===== # Operations · ArtificialWatch ## Backup The database, mostly for the seen-id table. Losing it means every existing model looks new, so the restore procedure matters more than the backup frequency. ## Restore Restore, then confirm the first sweep after the restore raises nothing, because a restore that re-alerts forty models is indistinguishable from a broken watcher. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The age of the last successful sweep per provider, undelivered alerts, and an external check on the heartbeat. The last one is not optional, since every other signal is produced by the process that might be dead. ## Incident checklist The incident is always the same shape: a silence that turns out not to have been quiet. The sweep table tells you whether requests were being made, and the alerts table tells you whether a message was created and failed to deliver. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Adapters are confirmed to page to exhaustion - [ ] A single-sweep appearance is confirmed never to alert - [ ] A failed delivery is confirmed retried and visible - [ ] A dead process is confirmed detectable from outside - [ ] A reboot is confirmed to bring the watcher back ## Launch constraint Do not market omitted ArtificialWatch capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Seen ids, sweeps, alerts and heartbeats. DATABASE_PATH=./data/watcher.db # Required. One sweep per provider per tick. Check each provider's rate limits before lowering it. SWEEP_INTERVAL_SECONDS=60 # Optional · secret. One provider adapter. Absent means that provider is listed as not covered. ANTHROPIC_API_KEY=sk-ant-...redacted # Optional · secret. Another adapter, same rule about coverage. OPENAI_API_KEY=sk-...redacted # Optional · secret. Covers models from labs you hold no direct account with. OPENROUTER_API_KEY=sk-or-...redacted # Required · secret. Treat the topic name as a secret; anyone with it can post to your phone. PUSH_TOPIC_URL=https://ntfy.sh/your-private-topic # Required. How long a provider may error before the watcher alerts about itself. PROVIDER_ERROR_ALERT_MINUTES=10 # Required. Append-only record, written even when the push fails. LAUNCH_LOG_PATH=./data/launches.md
# ArtificialWatch · indie build A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node.js 22, one process under a supervisor on an always-on machine | a poller on a laptop that slept through the launch is worth nothing | | Schedule | A cron tick at a fixed interval, with one sweep per provider per tick | predictable load and a predictable answer to when you would have known | | Storage | better-sqlite3 holding every id ever seen and every sweep attempted | the sweep table is what proves the silence was real | | Providers | One adapter per provider, each paging to the end of its list | half a list is worse than no list, because it looks like it worked | | Alerting | A push topic with a delivery check, plus an append-only launch log | an alert that failed to send is the failure you will not notice | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the poller and the local page in one process. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An always-on machine with a supervisor** · a few pounds a month, or free on hardware you own - Why: The product is the alert arriving at seven in the morning, which a laptop that slept cannot deliver. - Get it: A small virtual server or a single-board computer at home, with a process supervisor configured to start on boot. - Verify: The watcher is running after a reboot with no manual step - [ ] **API keys for the providers you want covered** · listing models is typically free, but check each provider's terms - Why: Coverage is exactly the set of providers you hold a key for, which is the tool's main limitation and should be visible from the start. - Get it: Create a read-scoped key per provider where the provider supports scoping. - Verify: Each adapter lists models and records a successful sweep - [ ] **A push topic or notification service** · free tiers are common - Why: The alert has to reach a phone, and the delivery result has to be observable rather than assumed. - Get it: Create a topic on a push service and subscribe your phone to it. - Verify: A test alert arrives and its delivery is recorded - [ ] **An external check on the heartbeat** · free tiers are common - Why: Something outside the process must notice when it stops, because the failure mode of this product is silence. - Get it: Any uptime checker that can call a URL or expect a periodic ping. - Verify: Stopping the process produces an external alert within the stated window - [ ] **Your alias and snapshot patterns** · free - Why: These decide which ids never fire, and writing them before the first false alarm is what keeps you from muting the topic after it. - Get it: List the dated snapshot, preview and moving alias shapes each provider uses, with a reason per pattern. - Verify: Each pattern is stored with its reason and visible on the suppressed list ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. - coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews - the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration - the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return - debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms - someone else owning the uptime · your poller sleeps when your machine does If one of those is essential to you, that is the reason to keep paying for ArtificialWatch, and the README should say so rather than pretend.
# Build brief · ArtificialWatch The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a model-launch watcher that pages you once, correctly, the first time a new model id appears to replace ArtificialWatch. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node.js 22, one process under a supervisor on an always-on machine · a poller on a laptop that slept through the launch is worth nothing - Schedule: A cron tick at a fixed interval, with one sweep per provider per tick · predictable load and a predictable answer to when you would have known - Storage: better-sqlite3 holding every id ever seen and every sweep attempted · the sweep table is what proves the silence was real - Providers: One adapter per provider, each paging to the end of its list · half a list is worse than no list, because it looks like it worked - Alerting: A push topic with a delivery check, plus an append-only launch log · an alert that failed to send is the failure you will not notice ### Data model (create this before Phase 1) - providers(id, name, base_url, key_env, enabled, page_limit, last_good_sweep_at, consecutive_errors) - sweeps(id, provider_id, started_at, finished_at, pages_fetched, ids_seen, http_status, error) · one row per attempt, successful or not - model_ids(provider_id, model_id, first_seen_at, first_seen_sweep_id, confirmed_at, announced_at, suppressed_reason) - alerts(id, provider_id, model_id, created_at, delivered_at, attempts, last_error, dedupe_key) - filters(id, pattern, reason, added_at) · alias and dated snapshot patterns that never fire - heartbeats(at, providers_ok, providers_failing) · written every tick, read by whatever watches the watcher - A sweep row exists before the request is made, so a process that dies mid-sweep leaves evidence rather than a gap that looks like a quiet minute. ### Phase 1 · Every id a provider exposes, not the first page of them Build: Write one adapter per provider you hold a key for. Follow each provider's own pagination to exhaustion: Anthropic's model list, for instance, documents a limit parameter that defaults to twenty and ranges up to a thousand, with has_more and a last_id cursor to follow. Record pages fetched and ids seen on the sweep row, and treat a sweep that stopped with more pages available as a failure rather than a result. Trap: Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. Done when: Each adapter pages until the provider reports no more results, tested against a fake provider with three pages. A sweep that ends with pages remaining is recorded as failed and raises no launches, tested. The sweep row records pages fetched and ids seen for every attempt including failures. The first run seeds every id silently and raises nothing, tested on an empty database. Do not build yet: No alerting, filtering, dashboard or supervision yet. ### Phase 2 · A launch is an id you have never seen, twice Build: Compare each sweep's ids against the stored set. A new id is recorded immediately but confirmed only when a second consecutive successful sweep still contains it. Keep a pattern list for dated snapshots, preview suffixes and moving aliases, each with a written reason, and suppress matching ids with that reason recorded rather than dropping them silently. Trap: Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. Done when: An id present in one sweep and absent in the next is never alerted and is recorded as unconfirmed, tested. A suppressed id stores which pattern matched it and why, and appears in a suppressed list rather than vanishing. Confirmation requires two consecutive successful sweeps, so a failed sweep between them does not count, tested. Removing a pattern makes previously suppressed ids eligible without re-alerting historical ones. Do not build yet: No alert delivery, dashboard or supervision yet. ### Phase 3 · An alert that was actually delivered Build: Send a push carrying the model id, the provider and whatever context the list itself gave you. Use a dedupe key of provider and model id so a retry cannot produce a second message. Retry a failed delivery with backoff, record attempts and the last error, and append every confirmed launch to a plain text log regardless of whether the push succeeded. Trap: Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. Done when: A delivery failure is retried with backoff and the attempts and last error are stored, tested against a failing fake. The same launch cannot produce two messages, tested by forcing a retry after a successful send. Every confirmed launch is appended to the text log even when delivery fails, verified by disabling the push target. The alert body contains only fields the provider's own response supplied, with no invented context. Do not build yet: No dashboard, heartbeat or supervision yet. ### Phase 4 · Proving the silence is real Build: One local page showing recent launches, the last successful sweep per provider with its age, and a red state for any provider erroring beyond a stated window. Write a heartbeat every tick and expose it so an external checker can notice the process stopped. Honour rate limit responses and their retry hints with backoff rather than hammering. Trap: A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. Done when: The page shows the age of the last successful sweep for every provider and turns red past the stated threshold, tested by disabling a key. A heartbeat is written every tick and the README explains how to point an external checker at it. A rate limited response backs off according to the provider's hint and the sweep is recorded as rate limited rather than failed, tested. A provider erroring continuously raises one alert about itself rather than one per tick. Do not build yet: No supervision, key rotation or coverage statement yet. ### Phase 5 · Uptime, keys, and saying what you cannot see Build: Run under a supervisor that restarts on crash and on boot, and confirm by rebooting the machine. Handle key rotation without losing the seen-id history. Print the covered providers and, just as prominently, the ones you have no key for, since a launch alerter that implies coverage it does not have is worse than one that admits the gap. Trap: Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. Done when: The process restarts automatically after a crash and after a reboot, confirmed by rebooting the machine. Rotating a key leaves the seen-id table intact and raises no launches, tested. The page and the alert footer both name the covered providers and state that anything else is not watched. Restoring the database from a backup does not re-alert models already recorded as confirmed, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Phone calls and text messages, which mean a telephony account, a purchased number and messaging registration paperwork before a single line of code. - A watchlist of models that have not shipped, which no endpoint can return and which would be a hand-maintained list dressed up as a feed. - Scraping provider blogs and pricing pages, which breaks whenever a page changes and may sit outside a site's terms; a page-change monitor is the honest tool for that job. - Benchmark scores or quality rankings on launch day, since nothing credible exists within hours of a release. ### README must contain - That each provider adapter pages to the end of its list, and that a truncated sweep is treated as a failure - That a new id must appear in two consecutive successful sweeps before it alerts, and why - How suppression patterns work, where suppressed ids are visible, and how to remove a pattern safely - How to point an external checker at the heartbeat, because a dead watcher looks exactly like a quiet week - Which providers are covered, which are not, and that the uncovered list is the honest limit of the tool
# Agent instructions · ArtificialWatch indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node.js 22, one process under a supervisor on an always-on machine, A cron tick at a fixed interval, with one sweep per provider per tick, better-sqlite3 holding every id ever seen and every sweep attempted, One adapter per provider, each paging to the end of its list, A push topic with a delivery check, plus an append-only launch log. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra.
# Build plan · ArtificialWatch A launch alerter whose hard problems are enumerating every model id a provider actually exposes rather than the first page of them, firing once for a real launch and never for a renamed alias, and proving to yourself that the watcher is still alive on the morning nothing has happened. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Every id a provider exposes, not the first page of them Each adapter pages to the end of the list and records how many pages it read. ### Steps 1. Follow each provider's cursor to the end The default page size is the number that will embarrass you. Files: `src/providers/anthropic.js`, `src/providers/openai.js`, `src/providers/openrouter.js` 2. Fail a truncated sweep rather than using it A partial list is not a smaller list, it is a wrong one. Files: `src/sweep.js`, `test/pagination.test.js` 3. Seed silently on first run Otherwise the first tick pages you about every model that exists. Files: `src/seed.js` ### Done when - [ ] Adapters page to exhaustion against a multi-page fake - [ ] A truncated sweep is recorded as failed - [ ] Sweep rows carry pages fetched and ids seen - [ ] The first run raises nothing ### Watch out - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. ## Phase 2 · A launch is an id you have never seen, twice New ids are confirmed across two sweeps and known alias patterns never fire. ### Steps 1. Confirm across two successful sweeps A failed sweep in between must not count as agreement. Files: `src/diff.js`, `test/debounce.test.js` 2. Store the reason a pattern suppressed an id You will need to know why the launch you missed was filtered. Files: `src/filters.js` 3. Keep suppressed ids visible Silently dropped ids are how a watcher goes quietly wrong. Files: `src/web/suppressed.js` ### Done when - [ ] A one-sweep appearance never alerts - [ ] Suppressions record the pattern and reason - [ ] A failed sweep breaks the confirmation pair - [ ] Removing a pattern does not re-alert history ### Watch out - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. ## Phase 3 · An alert that was actually delivered Every confirmed launch reaches the phone once, and a failed delivery is retried and visible. ### Steps 1. Dedupe on provider and model id The retry path is where duplicates come from. Files: `src/alert.js` 2. Record every delivery attempt An alert you cannot prove was delivered was not delivered. Files: `src/db/alerts.js`, `test/delivery.test.js` 3. Append to the log regardless The file is the record that survives every outage. Files: `src/log.js` ### Done when - [ ] Failed deliveries retry with backoff and are recorded - [ ] A retry after success sends nothing - [ ] The text log gets every confirmed launch - [ ] Alert bodies carry no invented context ### Watch out - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. ## Phase 4 · Proving the silence is real A quiet morning is distinguishable from a dead watcher at a glance. ### Steps 1. Show the age of the last good sweep The single most useful number on the page. Files: `src/web/dashboard.js` 2. Write a heartbeat and document the external check Something outside this process has to watch it. Files: `src/heartbeat.js` 3. Back off on rate limits, and say you did Getting yourself rate limited during a launch is the wrong day for it. Files: `src/http.js`, `test/backoff.test.js` ### Done when - [ ] Last good sweep age is shown per provider and goes red - [ ] A heartbeat is written every tick - [ ] Rate limits back off and are recorded distinctly - [ ] A failing provider alerts once, not per tick ### Watch out - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ## Not in this build - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. ## After v1, if you want it - A second push destination, so a muted topic is not a single point of failure - Context enrichment from an open model database, kept clearly separate from what the provider returned - A weekly digest of suppressed ids, which is how you find a pattern that is filtering too much
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Seen ids, sweeps, alerts and heartbeats. DATABASE_PATH=./data/watcher.db # Required. One sweep per provider per tick. Check each provider's rate limits before lowering it. SWEEP_INTERVAL_SECONDS=60 # Optional · secret. One provider adapter. Absent means that provider is listed as not covered. ANTHROPIC_API_KEY=sk-ant-...redacted # Optional · secret. Another adapter, same rule about coverage. OPENAI_API_KEY=sk-...redacted # Optional · secret. Covers models from labs you hold no direct account with. OPENROUTER_API_KEY=sk-or-...redacted # Required · secret. Treat the topic name as a secret; anyone with it can post to your phone. PUSH_TOPIC_URL=https://ntfy.sh/your-private-topic # Required. How long a provider may error before the watcher alerts about itself. PROVIDER_ERROR_ALERT_MINUTES=10 # Required. Append-only record, written even when the push fails. LAUNCH_LOG_PATH=./data/launches.md
# ArtificialWatch · product brief ## Problem The core loop is a genuine one-sitting build: poll the model-list endpoint of every provider you hold a key for, diff the ids against a local table, push the new ones to your phone. What does not one-shot is the three things a launch alert is actually judged on. Coverage · your script sees only the labs you have accounts with, while the watchlist here runs to 48 models including Chinese labs, restricted previews and things that have not shipped at all, which no API returns. Telephony · a call that rings until you answer means Twilio, a purchased number and US A2P 10DLC registration, which is days of paperwork before a line of code. And uptime, which is the whole product · a poller on a laptop that slept through the drop is worth nothing, and the second-sweep debounce that keeps preview aliases from crying wolf is the part you only tune after it has already cried wolf twice. ## Product outcome A watcher that sees every id a provider exposes rather than the first page, fires once for a real launch and not at all for a renamed alias, proves its own silence is genuine, and tells you plainly which labs it cannot see. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - API keys for the providers you want covered (OpenAI, Anthropic, Google, OpenRouter) - an always-on box · a small VPS or a Pi, not a laptop - an ntfy or Pushover topic for push ## Explicit non-goals for v1 - Phone calls and text messages. - A pre-launch watchlist. - Blog and pricing page scraping. - Launch day benchmark scores. - coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews - the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration - the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return - debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms - someone else owning the uptime · your poller sleeps when your machine does ## Success criteria - Adapters are confirmed to page to exhaustion - A single-sweep appearance is confirmed never to alert - A failed delivery is confirmed retried and visible - A dead process is confirmed detectable from outside - A reboot is confirmed to bring the watcher back
# Build brief · ArtificialWatch The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a model-launch watcher that pages you once, correctly, the first time a new model id appears to replace ArtificialWatch. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node.js 22, one process under a supervisor on an always-on machine · a poller on a laptop that slept through the launch is worth nothing - Schedule: A cron tick at a fixed interval, with one sweep per provider per tick · predictable load and a predictable answer to when you would have known - Storage: better-sqlite3 holding every id ever seen and every sweep attempted · the sweep table is what proves the silence was real - Providers: One adapter per provider, each paging to the end of its list · half a list is worse than no list, because it looks like it worked - Alerting: A push topic with a delivery check, plus an append-only launch log · an alert that failed to send is the failure you will not notice ### Data model (create this before Phase 1) - providers(id, name, base_url, key_env, enabled, page_limit, last_good_sweep_at, consecutive_errors) - sweeps(id, provider_id, started_at, finished_at, pages_fetched, ids_seen, http_status, error) · one row per attempt, successful or not - model_ids(provider_id, model_id, first_seen_at, first_seen_sweep_id, confirmed_at, announced_at, suppressed_reason) - alerts(id, provider_id, model_id, created_at, delivered_at, attempts, last_error, dedupe_key) - filters(id, pattern, reason, added_at) · alias and dated snapshot patterns that never fire - heartbeats(at, providers_ok, providers_failing) · written every tick, read by whatever watches the watcher - A sweep row exists before the request is made, so a process that dies mid-sweep leaves evidence rather than a gap that looks like a quiet minute. ### Phase 1 · Every id a provider exposes, not the first page of them Build: Write one adapter per provider you hold a key for. Follow each provider's own pagination to exhaustion: Anthropic's model list, for instance, documents a limit parameter that defaults to twenty and ranges up to a thousand, with has_more and a last_id cursor to follow. Record pages fetched and ids seen on the sweep row, and treat a sweep that stopped with more pages available as a failure rather than a result. Trap: Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. Done when: Each adapter pages until the provider reports no more results, tested against a fake provider with three pages. A sweep that ends with pages remaining is recorded as failed and raises no launches, tested. The sweep row records pages fetched and ids seen for every attempt including failures. The first run seeds every id silently and raises nothing, tested on an empty database. Do not build yet: No alerting, filtering, dashboard or supervision yet. ### Phase 2 · A launch is an id you have never seen, twice Build: Compare each sweep's ids against the stored set. A new id is recorded immediately but confirmed only when a second consecutive successful sweep still contains it. Keep a pattern list for dated snapshots, preview suffixes and moving aliases, each with a written reason, and suppress matching ids with that reason recorded rather than dropping them silently. Trap: Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. Done when: An id present in one sweep and absent in the next is never alerted and is recorded as unconfirmed, tested. A suppressed id stores which pattern matched it and why, and appears in a suppressed list rather than vanishing. Confirmation requires two consecutive successful sweeps, so a failed sweep between them does not count, tested. Removing a pattern makes previously suppressed ids eligible without re-alerting historical ones. Do not build yet: No alert delivery, dashboard or supervision yet. ### Phase 3 · An alert that was actually delivered Build: Send a push carrying the model id, the provider and whatever context the list itself gave you. Use a dedupe key of provider and model id so a retry cannot produce a second message. Retry a failed delivery with backoff, record attempts and the last error, and append every confirmed launch to a plain text log regardless of whether the push succeeded. Trap: Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. Done when: A delivery failure is retried with backoff and the attempts and last error are stored, tested against a failing fake. The same launch cannot produce two messages, tested by forcing a retry after a successful send. Every confirmed launch is appended to the text log even when delivery fails, verified by disabling the push target. The alert body contains only fields the provider's own response supplied, with no invented context. Do not build yet: No dashboard, heartbeat or supervision yet. ### Phase 4 · Proving the silence is real Build: One local page showing recent launches, the last successful sweep per provider with its age, and a red state for any provider erroring beyond a stated window. Write a heartbeat every tick and expose it so an external checker can notice the process stopped. Honour rate limit responses and their retry hints with backoff rather than hammering. Trap: A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. Done when: The page shows the age of the last successful sweep for every provider and turns red past the stated threshold, tested by disabling a key. A heartbeat is written every tick and the README explains how to point an external checker at it. A rate limited response backs off according to the provider's hint and the sweep is recorded as rate limited rather than failed, tested. A provider erroring continuously raises one alert about itself rather than one per tick. Do not build yet: No supervision, key rotation or coverage statement yet. ### Phase 5 · Uptime, keys, and saying what you cannot see Build: Run under a supervisor that restarts on crash and on boot, and confirm by rebooting the machine. Handle key rotation without losing the seen-id history. Print the covered providers and, just as prominently, the ones you have no key for, since a launch alerter that implies coverage it does not have is worse than one that admits the gap. Trap: Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message. Done when: The process restarts automatically after a crash and after a reboot, confirmed by rebooting the machine. Rotating a key leaves the seen-id table intact and raises no launches, tested. The page and the alert footer both name the covered providers and state that anything else is not watched. Restoring the database from a backup does not re-alert models already recorded as confirmed, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Phone calls and text messages, which mean a telephony account, a purchased number and messaging registration paperwork before a single line of code. - A watchlist of models that have not shipped, which no endpoint can return and which would be a hand-maintained list dressed up as a feed. - Scraping provider blogs and pricing pages, which breaks whenever a page changes and may sit outside a site's terms; a page-change monitor is the honest tool for that job. - Benchmark scores or quality rankings on launch day, since nothing credible exists within hours of a release. ### README must contain - That each provider adapter pages to the end of its list, and that a truncated sweep is treated as a failure - That a new id must appear in two consecutive successful sweeps before it alerts, and why - How suppression patterns work, where suppressed ids are visible, and how to remove a pattern safely - How to point an external checker at the heartbeat, because a dead watcher looks exactly like a quiet week - Which providers are covered, which are not, and that the uncovered list is the honest limit of the tool
# Architecture · ArtificialWatch ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node.js 22, one process under a supervisor on an always-on machine | a poller on a laptop that slept through the launch is worth nothing | | Schedule | A cron tick at a fixed interval, with one sweep per provider per tick | predictable load and a predictable answer to when you would have known | | Storage | better-sqlite3 holding every id ever seen and every sweep attempted | the sweep table is what proves the silence was real | | Providers | One adapter per provider, each paging to the end of its list | half a list is worse than no list, because it looks like it worked | | Alerting | A push topic with a delivery check, plus an append-only launch log | an alert that failed to send is the failure you will not notice | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Adapters | One provider each, paging to exhaustion. | Any provider added as an adapter, with its own pagination. | | Diff | Seen ids, confirmation across sweeps and suppression. | Any comparison, provided a single sighting never alerts. | | Alerting | Dedupe, retries and the append-only log. | Any transport, provided delivery is recorded rather than assumed. | | Liveness | Heartbeat, sweep ages and error states. | Any external checker, provided something outside the process watches it. | | Supervision | Restart on crash and on boot. | Any supervisor, tested by an actual reboot. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Seen ids, sweeps, alerts and heartbeats. - `SWEEP_INTERVAL_SECONDS` · required · One sweep per provider per tick. Check each provider's rate limits before lowering it. - `ANTHROPIC_API_KEY` · optional, secret · One provider adapter. Absent means that provider is listed as not covered. - `OPENAI_API_KEY` · optional, secret · Another adapter, same rule about coverage. - `OPENROUTER_API_KEY` · optional, secret · Covers models from labs you hold no direct account with. - `PUSH_TOPIC_URL` · required, secret · Treat the topic name as a secret; anyone with it can post to your phone. - `PROVIDER_ERROR_ALERT_MINUTES` · required · How long a provider may error before the watcher alerts about itself. - `LAUNCH_LOG_PATH` · required · Append-only record, written even when the push fails. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · ArtificialWatch product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node.js 22, one process under a supervisor on an always-on machine, A cron tick at a fixed interval, with one sweep per provider per tick, better-sqlite3 holding every id ever seen and every sweep attempted, One adapter per provider, each paging to the end of its list, A push topic with a delivery check, plus an append-only launch log. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. - Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message.
# Delivery milestones · ArtificialWatch Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Every id a provider exposes, not the first page of them Each adapter pages to the end of the list and records how many pages it read. ### Steps 1. Follow each provider's cursor to the end The default page size is the number that will embarrass you. Files: `src/providers/anthropic.js`, `src/providers/openai.js`, `src/providers/openrouter.js` 2. Fail a truncated sweep rather than using it A partial list is not a smaller list, it is a wrong one. Files: `src/sweep.js`, `test/pagination.test.js` 3. Seed silently on first run Otherwise the first tick pages you about every model that exists. Files: `src/seed.js` ### Done when - [ ] Adapters page to exhaustion against a multi-page fake - [ ] A truncated sweep is recorded as failed - [ ] Sweep rows carry pages fetched and ids seen - [ ] The first run raises nothing ### Watch out - Reading the first page and assuming that is the list gives you twenty ids from a provider that has sixty, and the failure is invisible because twenty ids is a plausible answer. Worse, the day you fix the pagination forty old models arrive at once and every one of them fires as a launch. Page to the end from the first commit, and record the page count so a regression shows up in the sweep table. ## M2 · A launch is an id you have never seen, twice New ids are confirmed across two sweeps and known alias patterns never fire. ### Steps 1. Confirm across two successful sweeps A failed sweep in between must not count as agreement. Files: `src/diff.js`, `test/debounce.test.js` 2. Store the reason a pattern suppressed an id You will need to know why the launch you missed was filtered. Files: `src/filters.js` 3. Keep suppressed ids visible Silently dropped ids are how a watcher goes quietly wrong. Files: `src/web/suppressed.js` ### Done when - [ ] A one-sweep appearance never alerts - [ ] Suppressions record the pattern and reason - [ ] A failed sweep breaks the confirmation pair - [ ] Removing a pattern does not re-alert history ### Watch out - Firing on the first appearance means every brief listing glitch, every regional rollout and every alias that points at yesterday's model wakes you at four in the morning, and after the second false alarm you mute the topic, which is the same as deleting the product. Confirm across two sweeps and keep suppressions visible so a wrong pattern can be found and removed. ## M3 · An alert that was actually delivered Every confirmed launch reaches the phone once, and a failed delivery is retried and visible. ### Steps 1. Dedupe on provider and model id The retry path is where duplicates come from. Files: `src/alert.js` 2. Record every delivery attempt An alert you cannot prove was delivered was not delivered. Files: `src/db/alerts.js`, `test/delivery.test.js` 3. Append to the log regardless The file is the record that survives every outage. Files: `src/log.js` ### Done when - [ ] Failed deliveries retry with backoff and are recorded - [ ] A retry after success sends nothing - [ ] The text log gets every confirmed launch - [ ] Alert bodies carry no invented context ### Watch out - Treating the push as fire and forget means a muted topic, an expired token or a five hundred from the notification service produces exactly the same silence as no launch, and you learn about the model from somewhere else a day later. Record the delivery attempt, retry it, and show undelivered alerts on the page. ## M4 · Proving the silence is real A quiet morning is distinguishable from a dead watcher at a glance. ### Steps 1. Show the age of the last good sweep The single most useful number on the page. Files: `src/web/dashboard.js` 2. Write a heartbeat and document the external check Something outside this process has to watch it. Files: `src/heartbeat.js` 3. Back off on rate limits, and say you did Getting yourself rate limited during a launch is the wrong day for it. Files: `src/http.js`, `test/backoff.test.js` ### Done when - [ ] Last good sweep age is shown per provider and goes red - [ ] A heartbeat is written every tick - [ ] Rate limits back off and are recorded distinctly - [ ] A failing provider alerts once, not per tick ### Watch out - A watcher reports nothing when everything is quiet and also when its key expired three weeks ago, and those look identical from the outside, which is how people discover at the wrong moment that their alerter died in March. Show the age of the last good sweep per provider, and make an external check on the heartbeat part of the setup rather than an optional extra. ## M5 · Uptime, keys, and saying what you cannot see (production only) The watcher survives reboots and states plainly which labs it covers. ### Steps 1. Restart on crash and on boot, then reboot to check Supervisor configuration that has never been tested is decoration. Files: `ops/supervisor.md` 2. Rotate keys without losing history The seen-id table is the only thing that stops a replay of every launch. Files: `src/providers/keys.js` 3. State the coverage boundary in every alert So the gap travels with the message rather than living in a README. Files: `src/alert.js` ### Done when - [ ] A reboot brings the watcher back automatically - [ ] Key rotation preserves the seen-id history - [ ] Coverage and its gaps appear on the page and in alerts - [ ] A restore does not re-alert confirmed models ### Watch out - Claiming to watch for new models while only holding keys for two providers means the first thing the tool misses is the launch you cared most about, from a lab you never had an account with. Print the covered and uncovered list on the page and in the alert footer, so the boundary travels with every message.
# Operations · ArtificialWatch ## Backup The database, mostly for the seen-id table. Losing it means every existing model looks new, so the restore procedure matters more than the backup frequency. ## Restore Restore, then confirm the first sweep after the restore raises nothing, because a restore that re-alerts forty models is indistinguishable from a broken watcher. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The age of the last successful sweep per provider, undelivered alerts, and an external check on the heartbeat. The last one is not optional, since every other signal is produced by the process that might be dead. ## Incident checklist The incident is always the same shape: a silence that turns out not to have been quiet. The sweep table tells you whether requests were being made, and the alerts table tells you whether a message was created and failed to deliver. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Adapters are confirmed to page to exhaustion - [ ] A single-sweep appearance is confirmed never to alert - [ ] A failed delivery is confirmed retried and visible - [ ] A dead process is confirmed detectable from outside - [ ] A reboot is confirmed to bring the watcher back ## Launch constraint Do not market omitted ArtificialWatch capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Seen ids, sweeps, alerts and heartbeats. DATABASE_PATH=./data/watcher.db # Required. One sweep per provider per tick. Check each provider's rate limits before lowering it. SWEEP_INTERVAL_SECONDS=60 # Optional · secret. One provider adapter. Absent means that provider is listed as not covered. ANTHROPIC_API_KEY=sk-ant-...redacted # Optional · secret. Another adapter, same rule about coverage. OPENAI_API_KEY=sk-...redacted # Optional · secret. Covers models from labs you hold no direct account with. OPENROUTER_API_KEY=sk-or-...redacted # Required · secret. Treat the topic name as a secret; anyone with it can post to your phone. PUSH_TOPIC_URL=https://ntfy.sh/your-private-topic # Required. How long a provider may error before the watcher alerts about itself. PROVIDER_ERROR_ALERT_MINUTES=10 # Required. Append-only record, written even when the push fails. LAUNCH_LOG_PATH=./data/launches.md
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because the failure mode is asymmetric. A launch alerter that works 95% of the time is not 95% as good, it is worthless on the one morning it matters, and that is exactly the morning your VPS got rate limited or your ntfy topic was muted. Paying moves the pager duty to someone else, buys the labs you have no account with, and buys a phone that rings instead of a notification that stacks under forty others. At $19.99 it is priced against the AI subscriptions it watches, which is the comparison buyers actually make.
xcoverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews
xthe phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration
xthe pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return
xdebounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms
xsomeone else owning the uptime · your poller sleeps when your machine does
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
ArtificialWatch pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | Email and browser alerts delayed by about 15 minutes; 51 monitored AI models shown on the live page at verification. |
| signal | $12/user | — | Instant alerts plus SMS, routing and quiet-hour controls. |
| the call | $19.99/user | — | Signal features plus phone-call alerts; charter rate remains locked only while the subscription stays active. |
free tieremail and browser alerts delayed about 15 minutes; 51 monitored models shown at verification
billingmonthly only for currently purchasable paid tiers; no active annual checkout verified
hidden costsa cancelled charter subscription loses its locked rate; a $34.99 Watchtower tier is waitlist-only rather than currently purchasable
verified 2026-08-13 · source ↗
Vibecode ArtificialWatch
Kinda. The core of ArtificialWatch is buildable in a weekend with the prompt on this page, but there are real gaps: coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews, the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration. Read the honest list above before committing.
How much does ArtificialWatch cost?
ArtificialWatch costs about $19.99/month (The Call, checked 2026-07-30), which is $239.88 per year.
What do I lose by replacing ArtificialWatch?
Honestly: coverage of labs you hold no key for · 48 tracked models including Chinese labs and restricted previews; the phone call that rings until you answer, and SMS · Twilio plus US A2P 10DLC registration; the pre-launch watchlist and live Polymarket odds on models that have not shipped, which no API can return; debounce and alias filtering tuned so dated snapshots and -preview ids do not fire false alarms; someone else owning the uptime · your poller sleeps when your machine does. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to ArtificialWatch?
Yes: modelalert.ai (The exact job: one terse email when a model appears, without pretending it is a platform.) The prompt is for when you want it exactly your way.