Vibecode Hyperping
track this build5 phases, 10 steps, beginner friendly0%A fetch on an interval, a state machine with flap suppression, a webhook and a status page is a one-sitting build. The 30-second interval and the multi-region probes are the part you cannot do from one box, and that is exactly what the price buys.
You are building a lean indie version of Hyperping.
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 =====
# Hyperping · indie build
An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so.
Estimated effort: **one sitting**. 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 22 with node:http, node:sqlite and fetch | the check is a fetch |
| Database | SQLite in WAL mode | monitors, checks and incidents in one file |
| Alerts | One chat webhook | no paging provider bill |
| Hosting | A VPS on a different provider than the sites | otherwise it dies with them |
## Before you start
Have every one of these ready. The plan assumes them from step one.
- [ ] **Node.js 22 or newer** · free
- Why: Everything in this build runs on it: the server, the scripts, the tests.
- Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards.
- Verify: node --version prints v22 or higher
- [ ] **A terminal and a code editor** · free
- Why: Every step below is a command you type or a file you edit.
- Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal.
- Verify: You can open a folder and run a command in its terminal
- [ ] **Git** · free
- Why: History for your code, and the way most hosts deploy.
- Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists.
- Verify: git --version prints a version
- [ ] **A chat webhook URL (Discord, Slack or Telegram)** · free
- Why: Alerts go to a chat channel you already watch. A webhook URL is the only credential this needs.
- Get it: Discord: Server settings > Integrations > Webhooks > New Webhook, copy the URL. Slack: create an app at api.slack.com/apps, enable Incoming Webhooks, add to a channel, copy the URL. Telegram: create a bot with @BotFather and use the bot token plus your chat id.
- Verify: curl -X POST -H 'Content-Type: application/json' -d '{"content":"test"}' <url> posts a message (Discord form; Slack uses a text field)
- [ ] **URLs to watch with expected status and a keyword each** · free
- Why: Defines what healthy means per monitor.
- Get it: List them with an interval (60 or 300 seconds).
- [ ] **A VPS on a different provider than your sites** · about $5 a month
- Why: A monitor next to what it watches is silent exactly when needed.
- Get it: Smallest Ubuntu 24.04 instance elsewhere.
- [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain
- Why: status.yourdomain.com for the public page.
- Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase.
- [ ] **Caddy on the server** (optional) · free
- Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work.
- Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config.
- Verify: caddy version prints a version on the server
## Quick start
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
Then copy `.env.example` to `.env` and fill in the values it documents.
## Honest limits
This build deliberately does not replace:
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
- multi-region probes that separate "the site is down" from "my box is down"
- 30-second intervals without hammering from one IP
- incident management and subscriber notifications
- a status page nobody has to host
If one of those is essential to you, that is the reason to keep paying for Hyperping, and the README should say so rather than pretend.
===== BRIEF.md =====
# Build brief · Hyperping
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 an uptime monitor with a public status page like Hyperping. 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)
- Node 22 with node:http, node:sqlite and global fetch. No web framework, one process.
- SQLite in WAL mode at a path from .env. Server-rendered HTML with inline SVG.
### Data model (create this before Phase 1)
- monitors: id, name, url, interval_seconds, timeout_ms, expected_status, keyword, enabled, status ('unknown' | 'up' | 'down'), consecutive_failures, last_checked_at, last_change_at
- checks: id, monitor_id, checked_at, ok, status_code, latency_ms, error
- incidents: id, monitor_id, started_at, ended_at, cause
Index checks(monitor_id, checked_at). Incidents are their own table on purpose: uptime percentage comes from checks, "how long and why" comes from incidents, and deriving the second from the first at read time gets slow and wrong.
### Phase 1 · The check
Build: one function from a monitor to a result. An explicit AbortController timeout, redirects followed, body read capped at 64 kB for the keyword, latency recorded, and DNS, TLS, timeout and wrong-status recorded as distinct errors.
Done when: a good URL records ok with a sane latency; a URL that never responds fails at the timeout, not later; a 404 against expected 200 fails with the code; a keyword monitor fails when the keyword is absent.
Do not build yet: scheduling, alerts, the page.
### Phase 2 · Scheduler
Build: due monitors run on their own intervals, concurrently with a cap of 10, so one hanging host cannot delay the rest. One monitor's exception never kills the loop. On boot every monitor is due.
Done when: 30 s, 60 s and 300 s monitors each keep their cadence for ten minutes, a hanging host does not delay the others, and a thrown exception inside a check leaves the loop running.
### Phase 3 · Transitions and incidents
Build: down only after N consecutive failures (default 2), up on the first success. Open an incidents row on the transition to down, close it on recovery with the cause from the first failing check.
Done when: one blip opens nothing, two failures open one incident, recovery closes it with a correct duration, and up/down/up/down flapping produces one incident, not four.
### Phase 4 · Alerting
Build: a webhook (Slack, Discord or Telegram URL in .env), once on down and once on recovery with the duration. Three retries with backoff, then record the error and continue.
Done when: taking a service down produces exactly one message, keeping it down produces none, recovery produces exactly one with a correct duration, and a broken webhook does not stall the loop.
### Phase 5 · Status page
Build: public /status · one row per monitor with a colored dot, uptime over 24 h, 7 d and 30 d, a latency sparkline as inline SVG, and the recent incident list. Refresh every 30 seconds. Print the denominator ("over 2,880 checks") so the percentage can be argued with. Add an RSS feed of incidents so subscribers need no account.
Done when: percentages match a hand-written query, the page renders with zero history, and it loads in under 200 ms with 90 days of checks.
### Phase 6 · Retention and deploy
Build: a nightly prune keeping 90 days of checks and all incidents, a /healthz endpoint, a systemd unit, the README.
Done when: pruning does not distort the 30-day figure and the service survives a reboot with state intact.
### Out of scope (and why)
- Multi-region probes. From one box you cannot tell the site is down from your network being down · state that on the status page itself, not only in the README.
- Subscriber email notifications, incident templates and on-call.
### README must contain
- The single-region caveat, and the instruction to host this on a different provider than the things it watches.
- The RSS feed URL as the subscription mechanism.
===== AGENTS.md =====
# Agent instructions · Hyperping indie build
- Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with node:http, node:sqlite and fetch, SQLite in WAL mode, One chat webhook, A VPS on a different provider than the sites. 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".
===== BUILD_PLAN.md =====
# Build plan · Hyperping
An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so.
Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note.
## Phase 1 · The check
A fetch with a timeout that fires and errors you can tell apart.
### Steps
1. Create the project and tables
monitors, checks (indexed on monitor_id, checked_at), incidents.
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
2. Write check(monitor) with AbortController, redirects, a 64 kB body cap and distinct dns/tls/timeout/status/keyword errors
### Done when
- [ ] A hanging URL fails at the timeout
- [ ] A 404 against expected 200 fails with the code
- [ ] A missing keyword fails a 200
## Phase 2 · Scheduler and transitions
Own intervals, capped concurrency, incidents that open and close.
### Steps
1. Loop every 5 seconds over due monitors, up to 10 at once, every exception caught
2. Down after FAILURES_BEFORE_DOWN, up on the first success, one incident per outage
### Done when
- [ ] Cadences hold over ten minutes
- [ ] A blip opens nothing; two failures open one incident
- [ ] Flapping produces one incident
## Phase 3 · Alerting
Once down, once up with duration.
### Steps
1. Sender for ALERT_FORMAT with three retries
2. Recovery message carrying the incident duration
### Done when
- [ ] Exactly one message down and one up
- [ ] A broken webhook does not stall checks
## Phase 4 · Status page
Numbers with denominators, a sparkline, and a feed.
### Steps
1. Build /status with uptime over 24 h, 7 d, 30 d and the check counts printed, plus inline-SVG latency
2. Add /status.rss with one item per incident and a 30-second meta refresh on the page
### Done when
- [ ] Percentages match a query
- [ ] Renders with no history
- [ ] Under 200 ms with 90 days seeded
- [ ] The feed validates
## Phase 5 · Retention and deploy
Prune checks, keep incidents, live behind HTTPS.
### Steps
1. Nightly prune of checks beyond 90 days; /healthz
2. systemd, Caddy, README with the single-region caveat on the page itself
Files: `README.md`
### Done when
- [ ] Pruning does not distort the 30-day figure
- [ ] The status page carries the caveat
- [ ] Survives a reboot
## Not in this build
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
## After v1, if you want it
- A second probe location that must agree
- Maintenance windows
===== .env.example =====
# Copy to .env and fill in. Never commit .env; this file documents it.
# Required. Any free port.
PORT=3000
# Required. SQLite file.
DATABASE_PATH=./data/status.db
# Required · secret. Chat webhook.
ALERT_WEBHOOK_URL=https://...
# Required. discord, slack or telegram.
ALERT_FORMAT=discord
# Optional. Consecutive failures before down.
FAILURES_BEFORE_DOWN=2
# Required. Public base for the page and feed.
SITE_URL=https://status.yourdomain.com
# Required. Any username for the basic-auth admin pages.
ADMIN_USER=admin
# Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password.
ADMIN_PASS=change-me-to-a-long-random-string
You are building a lean indie version of Hyperping.
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 =====
# Hyperping · indie build
An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so.
Estimated effort: **one sitting**. 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 22 with node:http, node:sqlite and fetch | the check is a fetch |
| Database | SQLite in WAL mode | monitors, checks and incidents in one file |
| Alerts | One chat webhook | no paging provider bill |
| Hosting | A VPS on a different provider than the sites | otherwise it dies with them |
## Before you start
Have every one of these ready. The plan assumes them from step one.
- [ ] **Node.js 22 or newer** · free
- Why: Everything in this build runs on it: the server, the scripts, the tests.
- Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards.
- Verify: node --version prints v22 or higher
- [ ] **A terminal and a code editor** · free
- Why: Every step below is a command you type or a file you edit.
- Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal.
- Verify: You can open a folder and run a command in its terminal
- [ ] **Git** · free
- Why: History for your code, and the way most hosts deploy.
- Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists.
- Verify: git --version prints a version
- [ ] **A chat webhook URL (Discord, Slack or Telegram)** · free
- Why: Alerts go to a chat channel you already watch. A webhook URL is the only credential this needs.
- Get it: Discord: Server settings > Integrations > Webhooks > New Webhook, copy the URL. Slack: create an app at api.slack.com/apps, enable Incoming Webhooks, add to a channel, copy the URL. Telegram: create a bot with @BotFather and use the bot token plus your chat id.
- Verify: curl -X POST -H 'Content-Type: application/json' -d '{"content":"test"}' <url> posts a message (Discord form; Slack uses a text field)
- [ ] **URLs to watch with expected status and a keyword each** · free
- Why: Defines what healthy means per monitor.
- Get it: List them with an interval (60 or 300 seconds).
- [ ] **A VPS on a different provider than your sites** · about $5 a month
- Why: A monitor next to what it watches is silent exactly when needed.
- Get it: Smallest Ubuntu 24.04 instance elsewhere.
- [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain
- Why: status.yourdomain.com for the public page.
- Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase.
- [ ] **Caddy on the server** (optional) · free
- Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work.
- Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config.
- Verify: caddy version prints a version on the server
## Quick start
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
Then copy `.env.example` to `.env` and fill in the values it documents.
## Honest limits
This build deliberately does not replace:
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
- multi-region probes that separate "the site is down" from "my box is down"
- 30-second intervals without hammering from one IP
- incident management and subscriber notifications
- a status page nobody has to host
If one of those is essential to you, that is the reason to keep paying for Hyperping, and the README should say so rather than pretend.
===== BRIEF.md =====
# Build brief · Hyperping
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 an uptime monitor with a public status page like Hyperping. 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)
- Node 22 with node:http, node:sqlite and global fetch. No web framework, one process.
- SQLite in WAL mode at a path from .env. Server-rendered HTML with inline SVG.
### Data model (create this before Phase 1)
- monitors: id, name, url, interval_seconds, timeout_ms, expected_status, keyword, enabled, status ('unknown' | 'up' | 'down'), consecutive_failures, last_checked_at, last_change_at
- checks: id, monitor_id, checked_at, ok, status_code, latency_ms, error
- incidents: id, monitor_id, started_at, ended_at, cause
Index checks(monitor_id, checked_at). Incidents are their own table on purpose: uptime percentage comes from checks, "how long and why" comes from incidents, and deriving the second from the first at read time gets slow and wrong.
### Phase 1 · The check
Build: one function from a monitor to a result. An explicit AbortController timeout, redirects followed, body read capped at 64 kB for the keyword, latency recorded, and DNS, TLS, timeout and wrong-status recorded as distinct errors.
Done when: a good URL records ok with a sane latency; a URL that never responds fails at the timeout, not later; a 404 against expected 200 fails with the code; a keyword monitor fails when the keyword is absent.
Do not build yet: scheduling, alerts, the page.
### Phase 2 · Scheduler
Build: due monitors run on their own intervals, concurrently with a cap of 10, so one hanging host cannot delay the rest. One monitor's exception never kills the loop. On boot every monitor is due.
Done when: 30 s, 60 s and 300 s monitors each keep their cadence for ten minutes, a hanging host does not delay the others, and a thrown exception inside a check leaves the loop running.
### Phase 3 · Transitions and incidents
Build: down only after N consecutive failures (default 2), up on the first success. Open an incidents row on the transition to down, close it on recovery with the cause from the first failing check.
Done when: one blip opens nothing, two failures open one incident, recovery closes it with a correct duration, and up/down/up/down flapping produces one incident, not four.
### Phase 4 · Alerting
Build: a webhook (Slack, Discord or Telegram URL in .env), once on down and once on recovery with the duration. Three retries with backoff, then record the error and continue.
Done when: taking a service down produces exactly one message, keeping it down produces none, recovery produces exactly one with a correct duration, and a broken webhook does not stall the loop.
### Phase 5 · Status page
Build: public /status · one row per monitor with a colored dot, uptime over 24 h, 7 d and 30 d, a latency sparkline as inline SVG, and the recent incident list. Refresh every 30 seconds. Print the denominator ("over 2,880 checks") so the percentage can be argued with. Add an RSS feed of incidents so subscribers need no account.
Done when: percentages match a hand-written query, the page renders with zero history, and it loads in under 200 ms with 90 days of checks.
### Phase 6 · Retention and deploy
Build: a nightly prune keeping 90 days of checks and all incidents, a /healthz endpoint, a systemd unit, the README.
Done when: pruning does not distort the 30-day figure and the service survives a reboot with state intact.
### Out of scope (and why)
- Multi-region probes. From one box you cannot tell the site is down from your network being down · state that on the status page itself, not only in the README.
- Subscriber email notifications, incident templates and on-call.
### README must contain
- The single-region caveat, and the instruction to host this on a different provider than the things it watches.
- The RSS feed URL as the subscription mechanism.
===== AGENTS.md =====
# Agent instructions · Hyperping indie build
- Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with node:http, node:sqlite and fetch, SQLite in WAL mode, One chat webhook, A VPS on a different provider than the sites. 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".
===== BUILD_PLAN.md =====
# Build plan · Hyperping
An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so.
Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note.
## Phase 1 · The check
A fetch with a timeout that fires and errors you can tell apart.
### Steps
1. Create the project and tables
monitors, checks (indexed on monitor_id, checked_at), incidents.
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
2. Write check(monitor) with AbortController, redirects, a 64 kB body cap and distinct dns/tls/timeout/status/keyword errors
### Done when
- [ ] A hanging URL fails at the timeout
- [ ] A 404 against expected 200 fails with the code
- [ ] A missing keyword fails a 200
## Phase 2 · Scheduler and transitions
Own intervals, capped concurrency, incidents that open and close.
### Steps
1. Loop every 5 seconds over due monitors, up to 10 at once, every exception caught
2. Down after FAILURES_BEFORE_DOWN, up on the first success, one incident per outage
### Done when
- [ ] Cadences hold over ten minutes
- [ ] A blip opens nothing; two failures open one incident
- [ ] Flapping produces one incident
## Phase 3 · Alerting
Once down, once up with duration.
### Steps
1. Sender for ALERT_FORMAT with three retries
2. Recovery message carrying the incident duration
### Done when
- [ ] Exactly one message down and one up
- [ ] A broken webhook does not stall checks
## Phase 4 · Status page
Numbers with denominators, a sparkline, and a feed.
### Steps
1. Build /status with uptime over 24 h, 7 d, 30 d and the check counts printed, plus inline-SVG latency
2. Add /status.rss with one item per incident and a 30-second meta refresh on the page
### Done when
- [ ] Percentages match a query
- [ ] Renders with no history
- [ ] Under 200 ms with 90 days seeded
- [ ] The feed validates
## Phase 5 · Retention and deploy
Prune checks, keep incidents, live behind HTTPS.
### Steps
1. Nightly prune of checks beyond 90 days; /healthz
2. systemd, Caddy, README with the single-region caveat on the page itself
Files: `README.md`
### Done when
- [ ] Pruning does not distort the 30-day figure
- [ ] The status page carries the caveat
- [ ] Survives a reboot
## Not in this build
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
## After v1, if you want it
- A second probe location that must agree
- Maintenance windows
===== .env.example =====
# Copy to .env and fill in. Never commit .env; this file documents it.
# Required. Any free port.
PORT=3000
# Required. SQLite file.
DATABASE_PATH=./data/status.db
# Required · secret. Chat webhook.
ALERT_WEBHOOK_URL=https://...
# Required. discord, slack or telegram.
ALERT_FORMAT=discord
# Optional. Consecutive failures before down.
FAILURES_BEFORE_DOWN=2
# Required. Public base for the page and feed.
SITE_URL=https://status.yourdomain.com
# Required. Any username for the basic-auth admin pages.
ADMIN_USER=admin
# Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password.
ADMIN_PASS=change-me-to-a-long-random-string
You are building a production product version of Hyperping.
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 =====
# Hyperping · product brief
## Problem
A fetch on an interval, a state machine with flap suppression, a webhook and a status page is a one-sitting build. The 30-second interval and the multi-region probes are the part you cannot do from one box, and that is exactly what the price buys.
## Product outcome
A status page you could show customers, honest about being single-region, watched from a third place.
## Target user
A builder who needs a maintainable product foundation, not a one-off demo.
## Required capabilities
- an always-on host on a different network from the sites it watches
- a chat webhook
## Explicit non-goals for v1
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
- multi-region probes that separate "the site is down" from "my box is down"
- 30-second intervals without hammering from one IP
- incident management and subscriber notifications
- a status page nobody has to host
## Success criteria
- Different provider than any monitored site
- Flap test produces one incident
- One restore drill performed
===== BRIEF.md =====
# Build brief · Hyperping
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 an uptime monitor with a public status page like Hyperping. 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)
- Node 22 with node:http, node:sqlite and global fetch. No web framework, one process.
- SQLite in WAL mode at a path from .env. Server-rendered HTML with inline SVG.
### Data model (create this before Phase 1)
- monitors: id, name, url, interval_seconds, timeout_ms, expected_status, keyword, enabled, status ('unknown' | 'up' | 'down'), consecutive_failures, last_checked_at, last_change_at
- checks: id, monitor_id, checked_at, ok, status_code, latency_ms, error
- incidents: id, monitor_id, started_at, ended_at, cause
Index checks(monitor_id, checked_at). Incidents are their own table on purpose: uptime percentage comes from checks, "how long and why" comes from incidents, and deriving the second from the first at read time gets slow and wrong.
### Phase 1 · The check
Build: one function from a monitor to a result. An explicit AbortController timeout, redirects followed, body read capped at 64 kB for the keyword, latency recorded, and DNS, TLS, timeout and wrong-status recorded as distinct errors.
Done when: a good URL records ok with a sane latency; a URL that never responds fails at the timeout, not later; a 404 against expected 200 fails with the code; a keyword monitor fails when the keyword is absent.
Do not build yet: scheduling, alerts, the page.
### Phase 2 · Scheduler
Build: due monitors run on their own intervals, concurrently with a cap of 10, so one hanging host cannot delay the rest. One monitor's exception never kills the loop. On boot every monitor is due.
Done when: 30 s, 60 s and 300 s monitors each keep their cadence for ten minutes, a hanging host does not delay the others, and a thrown exception inside a check leaves the loop running.
### Phase 3 · Transitions and incidents
Build: down only after N consecutive failures (default 2), up on the first success. Open an incidents row on the transition to down, close it on recovery with the cause from the first failing check.
Done when: one blip opens nothing, two failures open one incident, recovery closes it with a correct duration, and up/down/up/down flapping produces one incident, not four.
### Phase 4 · Alerting
Build: a webhook (Slack, Discord or Telegram URL in .env), once on down and once on recovery with the duration. Three retries with backoff, then record the error and continue.
Done when: taking a service down produces exactly one message, keeping it down produces none, recovery produces exactly one with a correct duration, and a broken webhook does not stall the loop.
### Phase 5 · Status page
Build: public /status · one row per monitor with a colored dot, uptime over 24 h, 7 d and 30 d, a latency sparkline as inline SVG, and the recent incident list. Refresh every 30 seconds. Print the denominator ("over 2,880 checks") so the percentage can be argued with. Add an RSS feed of incidents so subscribers need no account.
Done when: percentages match a hand-written query, the page renders with zero history, and it loads in under 200 ms with 90 days of checks.
### Phase 6 · Retention and deploy
Build: a nightly prune keeping 90 days of checks and all incidents, a /healthz endpoint, a systemd unit, the README.
Done when: pruning does not distort the 30-day figure and the service survives a reboot with state intact.
### Out of scope (and why)
- Multi-region probes. From one box you cannot tell the site is down from your network being down · state that on the status page itself, not only in the README.
- Subscriber email notifications, incident templates and on-call.
### README must contain
- The single-region caveat, and the instruction to host this on a different provider than the things it watches.
- The RSS feed URL as the subscription mechanism.
===== ARCHITECTURE.md =====
# Architecture · Hyperping
## Stack
| Part | Choice | Why |
| --- | --- | --- |
| Runtime | Node 22 with node:http, node:sqlite and fetch | the check is a fetch |
| Database | SQLite in WAL mode | monitors, checks and incidents in one file |
| Alerts | One chat webhook | no paging provider bill |
| Hosting | A VPS on a different provider than the sites | otherwise it dies with them |
## Modules
Each module has one owner concern and a documented way to replace it.
| Module | Owns | How to replace it |
| --- | --- | --- |
| Checker | check() and error kinds | Add TCP or DNS checks as functions |
| Scheduler | due loop and cap | Pure orchestration |
| State | transitions and incidents | Fake-clock testable |
| Status | page and RSS | Any UI over the tables |
## Configuration
Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists.
- `PORT` · required · Any free port.
- `DATABASE_PATH` · required · SQLite file.
- `ALERT_WEBHOOK_URL` · required, secret · Chat webhook.
- `ALERT_FORMAT` · required · discord, slack or telegram.
- `FAILURES_BEFORE_DOWN` · optional · Consecutive failures before down.
- `SITE_URL` · required · Public base for the page and feed.
- `ADMIN_USER` · required · Any username for the basic-auth admin pages.
- `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password.
## 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 · Hyperping product build
- Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with node:http, node:sqlite and fetch, SQLite in WAL mode, One chat webhook, A VPS on a different provider than the sites.
- 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.
===== MILESTONES.md =====
# Delivery milestones · Hyperping
Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer.
## M1 · The check
A fetch with a timeout that fires and errors you can tell apart.
### Steps
1. Create the project and tables
monitors, checks (indexed on monitor_id, checked_at), incidents.
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
2. Write check(monitor) with AbortController, redirects, a 64 kB body cap and distinct dns/tls/timeout/status/keyword errors
### Done when
- [ ] A hanging URL fails at the timeout
- [ ] A 404 against expected 200 fails with the code
- [ ] A missing keyword fails a 200
## M2 · Scheduler and transitions
Own intervals, capped concurrency, incidents that open and close.
### Steps
1. Loop every 5 seconds over due monitors, up to 10 at once, every exception caught
2. Down after FAILURES_BEFORE_DOWN, up on the first success, one incident per outage
### Done when
- [ ] Cadences hold over ten minutes
- [ ] A blip opens nothing; two failures open one incident
- [ ] Flapping produces one incident
## M3 · Alerting
Once down, once up with duration.
### Steps
1. Sender for ALERT_FORMAT with three retries
2. Recovery message carrying the incident duration
### Done when
- [ ] Exactly one message down and one up
- [ ] A broken webhook does not stall checks
## M4 · Status page
Numbers with denominators, a sparkline, and a feed.
### Steps
1. Build /status with uptime over 24 h, 7 d, 30 d and the check counts printed, plus inline-SVG latency
2. Add /status.rss with one item per incident and a 30-second meta refresh on the page
### Done when
- [ ] Percentages match a query
- [ ] Renders with no history
- [ ] Under 200 ms with 90 days seeded
- [ ] The feed validates
## M5 · Retention and deploy
Prune checks, keep incidents, live behind HTTPS.
### Steps
1. Nightly prune of checks beyond 90 days; /healthz
2. systemd, Caddy, README with the single-region caveat on the page itself
Files: `README.md`
### Done when
- [ ] Pruning does not distort the 30-day figure
- [ ] The status page carries the caveat
- [ ] Survives a reboot
## M6 · Operate it like a product (production only)
Only for the product-builder path: know when the monitor is down, never lose the database, and keep the server patched.
### Steps
1. Add a /healthz endpoint and an external uptime check against it
Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices.
2. Write structured request logs and rotate them
One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight.
3. Back the SQLite file up off the machine nightly and test a restore
SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it.
```sh
sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'"
rclone copy /tmp/app-$(date +%F).db remote:backups/
```
4. Lock the box down
Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure.
### Done when
- [ ] Stopping the service triggers an uptime alert within a few minutes
- [ ] A restore from last night's backup contains yesterday's data
- [ ] A port scan from another machine shows only 22, 80 and 443
===== OPERATIONS.md =====
# Operations · Hyperping
## Backup
SQLite .backup nightly; incidents are the history.
## Restore
Copy back; monitors resume.
Do a restore drill before the first real user, and write the date here when it passes.
## Monitoring
Watch the watcher from a third provider.
## Incident checklist
Down monitor means unwatched sites, not down sites.
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
- [ ] Different provider than any monitored site
- [ ] Flap test produces one incident
- [ ] One restore drill performed
## Launch constraint
Do not market omitted Hyperping 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. Any free port.
PORT=3000
# Required. SQLite file.
DATABASE_PATH=./data/status.db
# Required · secret. Chat webhook.
ALERT_WEBHOOK_URL=https://...
# Required. discord, slack or telegram.
ALERT_FORMAT=discord
# Optional. Consecutive failures before down.
FAILURES_BEFORE_DOWN=2
# Required. Public base for the page and feed.
SITE_URL=https://status.yourdomain.com
# Required. Any username for the basic-auth admin pages.
ADMIN_USER=admin
# Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password.
ADMIN_PASS=change-me-to-a-long-random-string
# Hyperping · indie build
An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so.
Estimated effort: **one sitting**. 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 22 with node:http, node:sqlite and fetch | the check is a fetch |
| Database | SQLite in WAL mode | monitors, checks and incidents in one file |
| Alerts | One chat webhook | no paging provider bill |
| Hosting | A VPS on a different provider than the sites | otherwise it dies with them |
## Before you start
Have every one of these ready. The plan assumes them from step one.
- [ ] **Node.js 22 or newer** · free
- Why: Everything in this build runs on it: the server, the scripts, the tests.
- Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards.
- Verify: node --version prints v22 or higher
- [ ] **A terminal and a code editor** · free
- Why: Every step below is a command you type or a file you edit.
- Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal.
- Verify: You can open a folder and run a command in its terminal
- [ ] **Git** · free
- Why: History for your code, and the way most hosts deploy.
- Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists.
- Verify: git --version prints a version
- [ ] **A chat webhook URL (Discord, Slack or Telegram)** · free
- Why: Alerts go to a chat channel you already watch. A webhook URL is the only credential this needs.
- Get it: Discord: Server settings > Integrations > Webhooks > New Webhook, copy the URL. Slack: create an app at api.slack.com/apps, enable Incoming Webhooks, add to a channel, copy the URL. Telegram: create a bot with @BotFather and use the bot token plus your chat id.
- Verify: curl -X POST -H 'Content-Type: application/json' -d '{"content":"test"}' <url> posts a message (Discord form; Slack uses a text field)
- [ ] **URLs to watch with expected status and a keyword each** · free
- Why: Defines what healthy means per monitor.
- Get it: List them with an interval (60 or 300 seconds).
- [ ] **A VPS on a different provider than your sites** · about $5 a month
- Why: A monitor next to what it watches is silent exactly when needed.
- Get it: Smallest Ubuntu 24.04 instance elsewhere.
- [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain
- Why: status.yourdomain.com for the public page.
- Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase.
- [ ] **Caddy on the server** (optional) · free
- Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work.
- Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config.
- Verify: caddy version prints a version on the server
## Quick start
```sh
mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module
mkdir -p data && cp .env.example .env
```
Then copy `.env.example` to `.env` and fill in the values it documents.
## Honest limits
This build deliberately does not replace:
- Multi-region probes; the page says so.
- Subscriber notifications, incident templates, on-call.
- multi-region probes that separate "the site is down" from "my box is down"
- 30-second intervals without hammering from one IP
- incident management and subscriber notifications
- a status page nobody has to host
If one of those is essential to you, that is the reason to keep paying for Hyperping, and the README should say so rather than pretend.# Build brief · Hyperping
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 an uptime monitor with a public status page like Hyperping. 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)
- Node 22 with node:http, node:sqlite and global fetch. No web framework, one process.
- SQLite in WAL mode at a path from .env. Server-rendered HTML with inline SVG.
### Data model (create this before Phase 1)
- monitors: id, name, url, interval_seconds, timeout_ms, expected_status, keyword, enabled, status ('unknown' | 'up' | 'down'), consecutive_failures, last_checked_at, last_change_at
- checks: id, monitor_id, checked_at, ok, status_code, latency_ms, error
- incidents: id, monitor_id, started_at, ended_at, cause
Index checks(monitor_id, checked_at). Incidents are their own table on purpose: uptime percentage comes from checks, "how long and why" comes from incidents, and deriving the second from the first at read time gets slow and wrong.
### Phase 1 · The check
Build: one function from a monitor to a result. An explicit AbortController timeout, redirects followed, body read capped at 64 kB for the keyword, latency recorded, and DNS, TLS, timeout and wrong-status recorded as distinct errors.
Done when: a good URL records ok with a sane latency; a URL that never responds fails at the timeout, not later; a 404 against expected 200 fails with the code; a keyword monitor fails when the keyword is absent.
Do not build yet: scheduling, alerts, the page.
### Phase 2 · Scheduler
Build: due monitors run on their own intervals, concurrently with a cap of 10, so one hanging host cannot delay the rest. One monitor's exception never kills the loop. On boot every monitor is due.
Done when: 30 s, 60 s and 300 s monitors each keep their cadence for ten minutes, a hanging host does not delay the others, and a thrown exception inside a check leaves the loop running.
### Phase 3 · Transitions and incidents
Build: down only after N consecutive failures (default 2), up on the first success. Open an incidents row on the transition to down, close it on recovery with the cause from the first failing check.
Done when: one blip opens nothing, two failures open one incident, recovery closes it with a correct duration, and up/down/up/down flapping produces one incident, not four.
### Phase 4 · Alerting
Build: a webhook (Slack, Discord or Telegram URL in .env), once on down and once on recovery with the duration. Three retries with backoff, then record the error and continue.
Done when: taking a service down produces exactly one message, keeping it down produces none, recovery produces exactly one with a correct duration, and a broken webhook does not stall the loop.
### Phase 5 · Status page
Build: public /status · one row per monitor with a colored dot, uptime over 24 h, 7 d and 30 d, a latency sparkline as inline SVG, and the recent incident list. Refresh every 30 seconds. Print the denominator ("over 2,880 checks") so the percentage can be argued with. Add an RSS feed of incidents so subscribers need no account.
Done when: percentages match a hand-written query, the page renders with zero history, and it loads in under 200 ms with 90 days of checks.
### Phase 6 · Retention and deploy
Build: a nightly prune keeping 90 days of checks and all incidents, a /healthz endpoint, a systemd unit, the README.
Done when: pruning does not distort the 30-day figure and the service survives a reboot with state intact.
### Out of scope (and why)
- Multi-region probes. From one box you cannot tell the site is down from your network being down · state that on the status page itself, not only in the README.
- Subscriber email notifications, incident templates and on-call.
### README must contain
- The single-region caveat, and the instruction to host this on a different provider than the things it watches.
- The RSS feed URL as the subscription mechanism.# Agent instructions · Hyperping indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with node:http, node:sqlite and fetch, SQLite in WAL mode, One chat webhook, A VPS on a different provider than the sites. 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".
# Build plan · Hyperping An uptime monitor with a public status page: checks on their own intervals with real timeouts, down only after consecutive failures, one chat message per transition, and a status page with uptime percentages, latency sparklines and an RSS feed of incidents. From one box, so the page says so. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The check A fetch with a timeout that fires and errors you can tell apart. ### Steps 1. Create the project and tables monitors, checks (indexed on monitor_id, checked_at), incidents. ```sh mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Write check(monitor) with AbortController, redirects, a 64 kB body cap and distinct dns/tls/timeout/status/keyword errors ### Done when - [ ] A hanging URL fails at the timeout - [ ] A 404 against expected 200 fails with the code - [ ] A missing keyword fails a 200 ## Phase 2 · Scheduler and transitions Own intervals, capped concurrency, incidents that open and close. ### Steps 1. Loop every 5 seconds over due monitors, up to 10 at once, every exception caught 2. Down after FAILURES_BEFORE_DOWN, up on the first success, one incident per outage ### Done when - [ ] Cadences hold over ten minutes - [ ] A blip opens nothing; two failures open one incident - [ ] Flapping produces one incident ## Phase 3 · Alerting Once down, once up with duration. ### Steps 1. Sender for ALERT_FORMAT with three retries 2. Recovery message carrying the incident duration ### Done when - [ ] Exactly one message down and one up - [ ] A broken webhook does not stall checks ## Phase 4 · Status page Numbers with denominators, a sparkline, and a feed. ### Steps 1. Build /status with uptime over 24 h, 7 d, 30 d and the check counts printed, plus inline-SVG latency 2. Add /status.rss with one item per incident and a 30-second meta refresh on the page ### Done when - [ ] Percentages match a query - [ ] Renders with no history - [ ] Under 200 ms with 90 days seeded - [ ] The feed validates ## Phase 5 · Retention and deploy Prune checks, keep incidents, live behind HTTPS. ### Steps 1. Nightly prune of checks beyond 90 days; /healthz 2. systemd, Caddy, README with the single-region caveat on the page itself Files: `README.md` ### Done when - [ ] Pruning does not distort the 30-day figure - [ ] The status page carries the caveat - [ ] Survives a reboot ## Not in this build - Multi-region probes; the page says so. - Subscriber notifications, incident templates, on-call. ## After v1, if you want it - A second probe location that must agree - Maintenance windows
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/status.db # Required · secret. Chat webhook. ALERT_WEBHOOK_URL=https://... # Required. discord, slack or telegram. ALERT_FORMAT=discord # Optional. Consecutive failures before down. FAILURES_BEFORE_DOWN=2 # Required. Public base for the page and feed. SITE_URL=https://status.yourdomain.com # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Hyperping · product brief ## Problem A fetch on an interval, a state machine with flap suppression, a webhook and a status page is a one-sitting build. The 30-second interval and the multi-region probes are the part you cannot do from one box, and that is exactly what the price buys. ## Product outcome A status page you could show customers, honest about being single-region, watched from a third place. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - an always-on host on a different network from the sites it watches - a chat webhook ## Explicit non-goals for v1 - Multi-region probes; the page says so. - Subscriber notifications, incident templates, on-call. - multi-region probes that separate "the site is down" from "my box is down" - 30-second intervals without hammering from one IP - incident management and subscriber notifications - a status page nobody has to host ## Success criteria - Different provider than any monitored site - Flap test produces one incident - One restore drill performed
# Build brief · Hyperping
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 an uptime monitor with a public status page like Hyperping. 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)
- Node 22 with node:http, node:sqlite and global fetch. No web framework, one process.
- SQLite in WAL mode at a path from .env. Server-rendered HTML with inline SVG.
### Data model (create this before Phase 1)
- monitors: id, name, url, interval_seconds, timeout_ms, expected_status, keyword, enabled, status ('unknown' | 'up' | 'down'), consecutive_failures, last_checked_at, last_change_at
- checks: id, monitor_id, checked_at, ok, status_code, latency_ms, error
- incidents: id, monitor_id, started_at, ended_at, cause
Index checks(monitor_id, checked_at). Incidents are their own table on purpose: uptime percentage comes from checks, "how long and why" comes from incidents, and deriving the second from the first at read time gets slow and wrong.
### Phase 1 · The check
Build: one function from a monitor to a result. An explicit AbortController timeout, redirects followed, body read capped at 64 kB for the keyword, latency recorded, and DNS, TLS, timeout and wrong-status recorded as distinct errors.
Done when: a good URL records ok with a sane latency; a URL that never responds fails at the timeout, not later; a 404 against expected 200 fails with the code; a keyword monitor fails when the keyword is absent.
Do not build yet: scheduling, alerts, the page.
### Phase 2 · Scheduler
Build: due monitors run on their own intervals, concurrently with a cap of 10, so one hanging host cannot delay the rest. One monitor's exception never kills the loop. On boot every monitor is due.
Done when: 30 s, 60 s and 300 s monitors each keep their cadence for ten minutes, a hanging host does not delay the others, and a thrown exception inside a check leaves the loop running.
### Phase 3 · Transitions and incidents
Build: down only after N consecutive failures (default 2), up on the first success. Open an incidents row on the transition to down, close it on recovery with the cause from the first failing check.
Done when: one blip opens nothing, two failures open one incident, recovery closes it with a correct duration, and up/down/up/down flapping produces one incident, not four.
### Phase 4 · Alerting
Build: a webhook (Slack, Discord or Telegram URL in .env), once on down and once on recovery with the duration. Three retries with backoff, then record the error and continue.
Done when: taking a service down produces exactly one message, keeping it down produces none, recovery produces exactly one with a correct duration, and a broken webhook does not stall the loop.
### Phase 5 · Status page
Build: public /status · one row per monitor with a colored dot, uptime over 24 h, 7 d and 30 d, a latency sparkline as inline SVG, and the recent incident list. Refresh every 30 seconds. Print the denominator ("over 2,880 checks") so the percentage can be argued with. Add an RSS feed of incidents so subscribers need no account.
Done when: percentages match a hand-written query, the page renders with zero history, and it loads in under 200 ms with 90 days of checks.
### Phase 6 · Retention and deploy
Build: a nightly prune keeping 90 days of checks and all incidents, a /healthz endpoint, a systemd unit, the README.
Done when: pruning does not distort the 30-day figure and the service survives a reboot with state intact.
### Out of scope (and why)
- Multi-region probes. From one box you cannot tell the site is down from your network being down · state that on the status page itself, not only in the README.
- Subscriber email notifications, incident templates and on-call.
### README must contain
- The single-region caveat, and the instruction to host this on a different provider than the things it watches.
- The RSS feed URL as the subscription mechanism.# Architecture · Hyperping ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with node:http, node:sqlite and fetch | the check is a fetch | | Database | SQLite in WAL mode | monitors, checks and incidents in one file | | Alerts | One chat webhook | no paging provider bill | | Hosting | A VPS on a different provider than the sites | otherwise it dies with them | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Checker | check() and error kinds | Add TCP or DNS checks as functions | | Scheduler | due loop and cap | Pure orchestration | | State | transitions and incidents | Fake-clock testable | | Status | page and RSS | Any UI over the tables | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite file. - `ALERT_WEBHOOK_URL` · required, secret · Chat webhook. - `ALERT_FORMAT` · required · discord, slack or telegram. - `FAILURES_BEFORE_DOWN` · optional · Consecutive failures before down. - `SITE_URL` · required · Public base for the page and feed. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## 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 · Hyperping product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with node:http, node:sqlite and fetch, SQLite in WAL mode, One chat webhook, A VPS on a different provider than the sites. - 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.
# Delivery milestones · Hyperping Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The check A fetch with a timeout that fires and errors you can tell apart. ### Steps 1. Create the project and tables monitors, checks (indexed on monitor_id, checked_at), incidents. ```sh mkdir statuspage && cd statuspage && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Write check(monitor) with AbortController, redirects, a 64 kB body cap and distinct dns/tls/timeout/status/keyword errors ### Done when - [ ] A hanging URL fails at the timeout - [ ] A 404 against expected 200 fails with the code - [ ] A missing keyword fails a 200 ## M2 · Scheduler and transitions Own intervals, capped concurrency, incidents that open and close. ### Steps 1. Loop every 5 seconds over due monitors, up to 10 at once, every exception caught 2. Down after FAILURES_BEFORE_DOWN, up on the first success, one incident per outage ### Done when - [ ] Cadences hold over ten minutes - [ ] A blip opens nothing; two failures open one incident - [ ] Flapping produces one incident ## M3 · Alerting Once down, once up with duration. ### Steps 1. Sender for ALERT_FORMAT with three retries 2. Recovery message carrying the incident duration ### Done when - [ ] Exactly one message down and one up - [ ] A broken webhook does not stall checks ## M4 · Status page Numbers with denominators, a sparkline, and a feed. ### Steps 1. Build /status with uptime over 24 h, 7 d, 30 d and the check counts printed, plus inline-SVG latency 2. Add /status.rss with one item per incident and a 30-second meta refresh on the page ### Done when - [ ] Percentages match a query - [ ] Renders with no history - [ ] Under 200 ms with 90 days seeded - [ ] The feed validates ## M5 · Retention and deploy Prune checks, keep incidents, live behind HTTPS. ### Steps 1. Nightly prune of checks beyond 90 days; /healthz 2. systemd, Caddy, README with the single-region caveat on the page itself Files: `README.md` ### Done when - [ ] Pruning does not distort the 30-day figure - [ ] The status page carries the caveat - [ ] Survives a reboot ## M6 · Operate it like a product (production only) Only for the product-builder path: know when the monitor is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443
# Operations · Hyperping ## Backup SQLite .backup nightly; incidents are the history. ## Restore Copy back; monitors resume. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Watch the watcher from a third provider. ## Incident checklist Down monitor means unwatched sites, not down sites. 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 - [ ] Different provider than any monitored site - [ ] Flap test produces one incident - [ ] One restore drill performed ## Launch constraint Do not market omitted Hyperping 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. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/status.db # Required · secret. Chat webhook. ALERT_WEBHOOK_URL=https://... # Required. discord, slack or telegram. ALERT_FORMAT=discord # Optional. Consecutive failures before down. FAILURES_BEFORE_DOWN=2 # Required. Public base for the page and feed. SITE_URL=https://status.yourdomain.com # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
$ choose a build depth, inspect the files, then open the complete pack in your agent
One machine cannot tell an outage from its own bad network. The paid product checks from several continents and hosts the status page on infrastructure that is not yours.
xmulti-region probes that separate "the site is down" from "my box is down"
x30-second intervals without hammering from one IP
xincident management and subscriber notifications
xa status page nobody has to host
Hyperping pricing
essentials$29/mo · monthly flat, 2 seats included · $348/yr
free tierThe free plan covers 20 monitors at a 5-minute interval with one basic status page.
verified 2026-09-04 · source ↗
Is Hyperping free?
The free plan covers 20 monitors at a 5-minute interval with one basic status page. Paid is Essentials at $29/mo (checked 2026-09-04).
Vibecode Hyperping
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Hyperping replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Hyperping cost?
Hyperping costs about $29/month (Essentials, checked 2026-09-04), which is $348 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Hyperping?
Honestly: multi-region probes that separate "the site is down" from "my box is down"; 30-second intervals without hammering from one IP; incident management and subscriber notifications; a status page nobody has to host. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Hyperping?
Yes: Uptime Kuma (self-hosted uptime monitor with status pages). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.