From hypervibe
Adds automated Neon database backups to the current project using a shared Cloudflare Worker. Handles registration, snapshot scheduling, and retention policy.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hypervibe:add-backup-dbThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- **Interactive mode (default)**: the user called `/add-backup-db` directly. You show the detailed checklist and each `↳ … ✅`. You finish with the full summary (Step 8).
Interactive mode (default): the user called /add-backup-db directly. You show the detailed checklist and each ↳ … ✅. You finish with the full summary (Step 8).
quiet mode: called silently by /add-db (or another caller) at the end of its flow. You display nothing other than a single final status line intended for the caller, which must be one of these exact values:
STATUS:ok:created - the shared worker got the snapshot job for the first time (this project is registered as its first target)STATUS:ok:added - the snapshot job already existed, this project was appended as a new targetSTATUS:ok:already-registered - the project was already registered, no-opSTATUS:skipped:no-neon-key - NEON_API_KEY not found (neither env nor rc nor vault) → we could not prompt in quiet modeSTATUS:skipped:cloudflare-missing - Cloudflare token missing or not authenticatedSTATUS:skipped:no-db - the Neon DB is not wired into this project (db_ok=false)STATUS:error:<short-reason> - any other error, with a short reasonNo checklist, no ↳ …, no prompts. No user-facing output. The caller uses the status to decide what to show in its global summary.
Detect the mode: if $ARGUMENTS contains --quiet (or if the invocation passes quiet=true), switch to quiet mode and apply the rules above at every step.
wrangler command (do this FIRST)eval "$(node "${CLAUDE_SKILL_DIR}/../../scripts/wrangler-env-init.mjs")"
This line loads CLOUDFLARE_API_TOKEN from the User scope (Windows registry / shell rc on Mac/Linux) if it is not already in process.env, and adds the pnpm bin to the PATH (for bash sessions where pnpm setup has not propagated yet). Without it, the deploy step fails with "command not found" on Mac (Spotlight), or may use a different Cloudflare account than the one the user expects.
You add automatic backups for the current project's Neon database.
Cloudflare Worker "hypervibe-jobs" (ONE worker, ONE cron slot, ALL account-wide jobs)
├─ job "neon-backups" (kind: snapshot, default cadence: 1st and 15th of the month, 3am UTC)
│ └─ For each registered target:
│ └─ Neon API → create a branch snapshot + clean up the old ones
├─ job "quota-monitor" (kind: quota, if configured by /quotas)
└─ ping jobs (kind: ping, if configured by /add-cron or /add-automation)
A single unified shared Worker for the whole account: it is the shared clock of all Hypervibe background jobs, not just backups. Each call to add-backup-db registers the current project as one more target of the neon-backups snapshot job. A single Cloudflare cron slot consumed, regardless of the number of projects and jobs.
The job list lives in a git-versioned registry: ~/.hypervibe-jobs/jobs.js. Every change is committed there and the worker is redeployed, all handled by the plugin scripts. You never scaffold or edit the worker files by hand.
| Type | When a new one is created | Deleted when | Max branches |
|---|---|---|---|
| Rolling | On each run (every ~2 weeks) | Only the 2 most recent are kept | 2 |
| Aging | When the most recent aging is > 3 months (90 days) old | When it exceeds 9 months (270 days) | 3 (in steady state) |
Total: 5 Neon branches max per project (out of 20 on the free tier).
In steady state, the 3 aging branches cover the 0-3 months, 3-6 months and 6-9 months ranges. The oldest one (9 months) is deleted when a new one is created.
The worker lives in $HOME/.hypervibe-jobs/ (outside any repo, because it is shared across projects; itself a small git repo so the registry history is versioned). Contains: worker.js + jobs.js (the registry) + wrangler.toml. Managed exclusively through the plugin scripts below.
Invoke _setup-wrangler to make sure Wrangler is installed and authenticated.
Invoke _check-deps db to verify that a real Neon cloud database is wired up:
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/check-deps.mjs" db)
db_ok=$(echo "$result" | node -e "console.log(JSON.parse(require('fs').readFileSync(0,'utf8')).db.ok)")
The helper handles the heuristics (rejecting T3 localhost defaults, placeholder, missing drizzle.config...). Do NOT re-implement these checks inline.
If db_ok = false:
STATUS:skipped:no-db and exit. No message to the user.This project does not have a real database wired up yet (just a default setting that is not connected to anything). Tell me "add a database" and I will take care of it, then we will set up the automatic backups.
Also check the Cloudflare token:
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/check-deps.mjs" cloudflare)
cf_ok=$(echo "$result" | node -e "console.log(JSON.parse(require('fs').readFileSync(0,'utf8')).cloudflare.ok)")
If cf_ok = false:
STATUS:skipped:cloudflare-missing and exit./start to configure Cloudflare.List the projects via the Neon REST API (key NEON.api_key from the vault) and match against the current project (by comparing the name or the host in DATABASE_URL):
NTOK=$(node "${CLAUDE_SKILL_DIR}/../../scripts/vault/vault.mjs" get NEON api_key)
curl -s -H "Authorization: Bearer $NTOK" "https://console.neon.tech/api/v2/projects?limit=400"
(_get-secret pattern for NTOK: RC 2/3 → unlock; RC 4 → add NEON to the vault.)
Match the DATABASE_URL host (ep-xxx...) with a project from the list (via GET /projects/{id}/connection_uri or by comparing the endpoints). As a last resort, ask the user for the Neon project ID (visible on https://console.neon.tech → project → Settings).
Store: NEON_PROJECT_ID and PROJECT_NAME (the project's short name, e.g. hypervibe). PROJECT_NAME must be kebab-case (lowercase, digits, hyphens): normalize it if needed, the registry rejects anything else.
One call replaces all the scaffolding: it creates ~/.hypervibe-jobs/ (git repo, worker code, registry, wrangler config), deploys the worker on the user's Cloudflare account, and sets up the admin token used for manual triggers. If everything already exists, it returns fast without touching anything (it may self-heal: sync the worker code with the plugin version, re-init git, regenerate the admin token).
eval "$(node "${CLAUDE_SKILL_DIR}/../../scripts/wrangler-env-init.mjs")"
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/ensure.mjs")
Parse the JSON result:
{ ok: true, status: "created" | "already_present", dir, workerName: "hypervibe-jobs", workerUrl, deployed, jobs, adminTokenVar: "HYPERVIBE_JOBS_ADMIN_TOKEN", healed: [] }{ ok: false, error, howTo } on failure.If ok = false:
STATUS:skipped:cloudflare-missing; otherwise emit STATUS:error:<short-reason>. Exit.howTo instruction (usually: run /start). Abort.Keep workerUrl and dir for later steps. If healed is non-empty in interactive mode, you may mention in one short line that the backup system was refreshed; no details needed.
Before 2026-07 backups ran on a standalone worker scaffolded in ~/.db-backup-worker/. Right after the preflight, check for it:
test -f "$HOME/.db-backup-worker/wrangler.toml" && echo "legacy" || echo "clean"
If legacy (and you are in interactive mode):
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/migrate-live.mjs" --put-secrets)
verification list.decommission_after_verification (a wrangler delete in each legacy folder, which frees their cron slots, then removing the folders). Explain to the user in plain words what you are about to do (deleting the OLD standalone workers, now redundant) and do it.In quiet mode: never migrate (that flow needs the user in the loop). Just continue; the legacy worker keeps running untouched and the migration will be offered next time this skill runs interactively.
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/register.mjs" --list)
In the returned jobs array, look for the job named neon-backups (kind snapshot) and, inside its targets, an entry whose name equals PROJECT_NAME.
If the target is already there:
STATUS:ok:already-registered and exit.Otherwise continue to 4.b. Also note whether the neon-backups job exists at all (needed in 4.b).
The worker authenticates to Neon with a NEON_API_KEY secret. Check that the key is readable locally:
node "${CLAUDE_SKILL_DIR}/../../scripts/_read-user-env.mjs" NEON_API_KEY
If the command returns a non-empty value, move on to Step 5. (On this machine, the key is usually already saved by /start.)
If it is empty:
neon-backups job already exists (seen in 4.a), the worker already holds the secret from a previous registration: continue to Step 5 anyway. If the job does not exist yet, emit STATUS:skipped:no-neon-key and exit. Do not prompt - that would be disruptive in the middle of /add-db. The caller will read this status and show the warning in its summary.For the backups to work automatically, I need a Neon API key.
- Go to https://console.neon.tech/app/settings/api-keys
- Click Create new API key
- Name:
claude-code(or whatever you want)- Copy the key and paste it here
(The key gives access to all your Neon projects by default, which is perfect for backing up multiple projects with the same key.)
Wait for the key. Store it in the vault (reusable for all projects) - a masked-input window, the value does not pass through Claude:
node "${CLAUDE_SKILL_DIR}/../../scripts/vault/launch.mjs" add --name NEON --service Neon --fields api_key:secret
Store: item NEON, field api_key. (_read-user-env.mjs NEON_API_KEY will then read it back automatically, and so will the registration script.) If the vault is not set up yet, run _add-keyring first.
The project is already registered for backups. Offer to change the frequency rather than just skipping.
In the --list output from 4.a, read the cron field of the neon-backups job. Convert the cron expression into a human-readable label:
| Cron expression | Label shown to the user |
|---|---|
0 3 * * * | Every day (at 3am UTC) |
0 3 * * 1 | Every week (Monday 3am UTC) |
0 3 1,15 * * | Every 2 weeks (1st and 15th of the month) |
0 3 1 * * | Every month (1st of the month) |
| Other | Custom: show the raw expression |
Show exactly (replacing <CURRENT> with the label):
📦 This project is already backed up
Current backup cadence: .
Do you want to change it?
- ⏰ Every day - maximum protection, fast rotation
- 🗓️ Every week - a good compromise for an active project
- 🔄 Every 2 weeks (Hypervibe default) - economical for most projects
- 📅 Every month - low-activity projects
- ❌ Change nothing - keep the current cadence
⚠️ Important: the cadence is shared across all your backed-up projects (a single shared backup job to save Cloudflare slots). Changing it here will affect ALL your backup projects.
--cron updates the shared cadence, commits and redeploys in one call):
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/register.mjs" \
--kind snapshot --target-name "<PROJECT_NAME>" --neon-project-id "<NEON_PROJECT_ID>" \
--cron "<CRON_EXPR>")
ok: true (the action will be target-updated), then confirm with --list that the neon-backups job now carries the new cron.One call does everything: creates the neon-backups job on first use, appends (or updates) this project's target, uploads the NEON_API_KEY secret read from the vault, commits the registry change to git, and redeploys the worker.
result=$(node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/register.mjs" \
--kind snapshot --target-name "<PROJECT_NAME>" --neon-project-id "<NEON_PROJECT_ID>" --put-secrets)
Do NOT pass --cron here: new registrations join the shared cadence as it is (default 0 3 1,15 * *). Changing the cadence is Step 4.bis territory and affects all projects.
Parse the JSON result:
{ ok: true, action: "target-added" | "target-updated", job: "neon-backups", target, targetCount, dir, deployed, workerUrl, uploadedSecrets, missingSecrets, nextSteps }Handle it:
| Outcome | Quiet mode | Interactive mode |
|---|---|---|
ok:true, action:"target-added", targetCount = 1 | emit STATUS:ok:created and exit | continue to Step 6 |
ok:true, action:"target-added", targetCount > 1 | emit STATUS:ok:added and exit | continue to Step 6 |
ok:true, action:"target-updated" | emit STATUS:ok:already-registered and exit | continue to Step 6 |
ok:true but missingSecrets contains NEON_API_KEY | emit STATUS:skipped:no-neon-key and exit (the target is saved; re-running the skill later will finish the job) | prompt for the key as in Step 4.b, store it in the vault, then re-run the exact same registration command (idempotent) so the secret gets uploaded |
ok:false | emit STATUS:error:<short-reason> and exit | explain in plain words, relay nextSteps if present, abort |
Quiet mode ends here in every case (skip Steps 6 to 8, the caller handles the rest).
The registration output already confirms deployed: true and gives workerUrl. For a stronger check, or whenever the user wants a backup right now, trigger the job manually:
ADMIN=$(node "${CLAUDE_SKILL_DIR}/../../scripts/_read-user-env.mjs" HYPERVIBE_JOBS_ADMIN_TOKEN)
curl -s -X POST -H "Authorization: Bearer $ADMIN" "<workerUrl>/trigger?name=neon-backups"
To watch it run live:
cd ~/.hypervibe-jobs && npx wrangler tail
After a manual run you can confirm the new bk-<PROJECT_NAME>-r-... branch in the Neon console. Keep this quick: one trigger + one confirmation line is enough.
Invoke _update-claude-md with:
custom:
## BackupsAutomatic backups of the Neon database via the unified shared Cloudflare Worker (`hypervibe-jobs`).
- **Schedule**: 1st and 15th of the month at 3am UTC (~every 2 weeks), cadence shared by all backed-up projects
- **Retention**: 2 rolling (last 2 weeks) + up to 3 aging (checkpoints ~3 months, kept 9 months max)
- **Neon branches**: 5 max per project (out of 20 on the free tier)
- **Job registry**: `~/.hypervibe-jobs/jobs.js` (git-versioned; job name: `neon-backups`)
- **Logs**: `cd ~/.hypervibe-jobs && npx wrangler tail`
- **Run a backup now**: ask me to run a backup right now (I trigger the `neon-backups` job manually)
- **Add another project**: re-run `/add-backup-db` from that project
✅ Backups enabled
Your database <PROJECT_NAME> (
<NEON_PROJECT_ID>) is now backed up automatically by your shared backup system (hypervibe-jobs).
Schedule 1st and 15th of the month at 3am UTC (cadence shared by all your backed-up projects) Rolling 2 recent backups (0 and ~2 weeks) Checkpoints A new one every ~3 months, kept for 9 months Neon branches 5 max out of the 20 on the free tier Good to know:
- Want a backup right now? Just ask me to run a backup right now and I will trigger it.
- View the logs live:
cd ~/.hypervibe-jobs && npx wrangler tail- Add another project: re-run
/add-backup-dbin that project- Stop backing up THIS project: ask me to disable its backups (I only remove this project from the list; the other projects keep their backups)
node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/register.mjs" --list
node "${CLAUDE_SKILL_DIR}/../../scripts/shared-worker/register.mjs" --kind snapshot --remove-target "<PROJECT_NAME>"
Never run wrangler delete for this: the worker is shared, other projects may still depend on it for their backups and for the other jobs (quota watch, cron pings). Deleting the whole hypervibe-jobs worker is an account-wide decision that kills every job for every project; only consider it if the user explicitly wants to dismantle the entire shared system, and say so clearly first.ADMIN + curl .../trigger?name=neon-backups pair from Step 6.cd ~/.hypervibe-jobs && npx wrangler tailnpx claudepluginhub flavien-ia/hypervibe-harness --plugin hypervibeAdds a Neon Postgres database with Drizzle ORM to a T3 project. Provisions the database, configures ORM, pushes schema, and handles monorepo setup.
Generates production-ready backup scripts for PostgreSQL, MySQL, MongoDB, and SQLite with scheduling, compression, encryption, and retention policies.
Guides and best practices for Neon Serverless Postgres: setup, connections, branching, autoscaling, scale-to-zero, read replicas, connection pooling, Neon Auth, CLI, MCP server, REST API, TypeScript SDK, and Python SDK.