**Operator directive**: "this HQ is meant to be a replacement for that [Cloudflare], so however Cloudflare works would be good. think about that"
**The framing**: HQ is single-tenant, operator-focused, Mavis-powered. Not multi-tenant Cloudflare. The goal: every Cloudflare-shaped power, none of the multi-tenant complexity. G + Mavis threads are the only "users."
| Cloudflare thing | HQ equivalent | Status |
|---|---|---|
| Pages (git push → URL) | `/api/mavis/deploy` (POST returns 202 + job_id) | ✅ works, but thread must call it |
| Wildcard certs for `*.freshvibeapps.com` | Caddy on-demand (a1806) | ✅ done |
| Custom domain support | Allowlist of 35 subdomains (hardcoded in `panel/src/mavis-hq.js`) | 🟡 partial — needs DB-driven allowlist |
| R2 (object storage) | Artifacts (`/api/artifacts`, descriptions enforced) | ✅ done, but artefact-shaped not general |
| Tunnels (`cloudflared`) | None | ❌ gap — Mavis sandboxes can't expose themselves |
| Access (auth on URLs) | PocketBase auth shim on each subdomain | 🟡 partial — per-app, not platform-level |
| DNS UI | None | ❌ gap — adding a subdomain needs SSH/bulletin |
| Single dashboard | Operator panel at `panel.freshvibeapps.com` | ✅ done, but operator-only not Mavis-facing |
| Pages preview URLs per PR | Preview subdomains (`<name>.freshvibeapps.com`) | 🟡 partial — no per-PR automation |
| Workers (serverless) | `/api/mavis/*` endpoints | ✅ done |
| Workers KV / D1 | None | ❌ gap |
| Queue | None | ❌ gap (deploy jobs run in-process, no retry queue) |
| Stream (video) | App.html storage | 🟡 partial — files only, no streaming |
| Images (optimization) | None | ❌ gap |
| Email routing | None | ❌ gap |
| WAF / rate limiting | Vault rate limits, general apiLimiter | 🟡 partial |
| Analytics | Mavis HQ timeline | ✅ done, but not visitor analytics |
| Logs | `/var/log/*.log` on VPS | 🟡 partial — not queryable from HQ |
**Current state**: a Mavis thread calls `POST /api/mavis/deploy` with `repo_url + ref + dest + operator_command`. Panel clones, builds, rsyncs. ~30s-3min depending on build. Returns job_id, bulletin posted when done.
**The gap**: thread must explicitly call the endpoint. Cloudflare auto-deploys on push.
**Phase 1 plan** (the killer feature):
1. **Mavis-pushable deploy key** — every Mavis thread gets a deploy key at registration time. `https://<key>@api.panel.freshvibeapps.com/deploy/auto?dest=<name>&ref=<branch>`
2. **GitHub webhook receiver** — `POST /api/mavis/auto-deploy` accepts GitHub push webhooks. If the repo's deploy key is configured, panel clones, builds, deploys automatically.
3. **Per-repo config** — `.hq-deploy.yml` in the repo declares: `dest`, `build_cmd`, `dist_subdir`, `branch`. No API call needed.
4. **Result**: `git push` → URL live in 30s. Zero Mavis involvement.
**Current state**: a new Mavis thread has to discover HQ via `/api/mavis/me` and figure out which endpoints to use. No recipe.
**Phase 2 plan** (the template):
1. **`HQ_DESIGN_TEMPLATE.md`** in HQ — first thing a Mavis reads. Tells it:
- "If asked to design X, here's the 4-step recipe"
- Step 1: post 1 bulletin (status: started)
- Step 2: upload 1-N artifacts (mockups, with descriptions)
- Step 3: ask G for review
- Step 4: on deploy request, call `/api/mavis/deploy` with repo + dest
2. **Design-mode flag** — `/api/mavis/me` returns `capabilities.design_template` pointing to the URL
3. **The "design X" affordance** — a Mavis that's been told "design X" can pull this template and follow it
**Current state**: 35 hardcoded subdomains in the allowlist. New subdomains need DB edit.
**Phase 3 plan** (Cloudflare Pages preview URLs per PR):
1. **DB-driven allowlist** — replace hardcoded array with `SELECT name FROM preview_clients` where the directory exists
2. **Random subdomain generator** — `POST /api/mavis/preview/spawn {prefix}` returns `https://<prefix>-<6-char-random>.freshvibeapps.com`. Caddy serves from `/var/www/freshvibeapps/preview-clients/<name>/`.
3. **Auto-cleanup** — previews expire after 7 days (longer than the 15d artifact TTL, shorter than the 30d default for permanent sites)
4. **Use case**: "show me a variant real quick" → spawn `test-vibe-2k3f9x.freshvibeapps.com` → drop files → URL live in 5 seconds
**Current state**: artifacts are tied to "Mavis thread uploaded this." No general storage.
**Phase 4 plan** (R2-style):
1. **`/api/storage`** — POST a file, get back an ID + URL. No thread binding. No description required (this is storage, not artifact).
2. **TTL + pin** — like app.html, 15d default, `pinned=1` for permanent
3. **Path structure** — `storage.freshvibeapps.com/<id>` or `*.freshvibeapps.com/storage/<id>` via the wildcard cert
4. **Use case**: Mavis wants to drop a binary (PDF, image, video) anywhere without it being "an artifact of a Mavis session"
**Current state**: operator panel at `panel.freshvibeapps.com/mavis`. Operator-only. Mavis sessions use API only.
**Phase 5 plan** (the dashboard):
1. **`/mavis/my`** — when a Mavis thread loads the URL, it sees its own: threads, deployments, artifacts, previews, storage items
2. **Token in URL** — `https://panel.freshvibeapps.com/mavis/my?token=<thread-token>` (no separate login for Mavis)
3. **What it shows**: every URL I've ever deployed, every preview I spawned, every artifact I uploaded, with "delete" / "redeploy" / "open" buttons
4. **The Cloudflare Pages dashboard feel** — single page, all my stuff, all action buttons
Imagine a fresh Mavis session. First turn:
```
G: "design a website for my cousin's bakery, then deploy it to the VPS"
Mavis (registers with HQ → gets token)
→ reads HQ_DESIGN_TEMPLATE.md
→ posts b1 "started: bakery website design"
→ uploads artifact a1 "homepage mockup" (with description)
→ uploads artifact a2 "menu page mockup"
→ posts b2 "design done, ready for review"
→ waits
G: "looks good, deploy it"
Mavis
→ POST /api/mavis/deploy {dest: "bakery", repo_url: "https://github.com/.../bakery", operator_command: "deploy bakery site"}
→ gets job_id, polls
→ posts b3 "deploying bakery..."
→ 60s later, deploy bulletin auto-posts
→ posts b4 "bakery.freshvibeapps.com is LIVE"
```
**Compare to current state**: a Mavis today has to discover the deploy endpoint, figure out the allowlist, post bulletins correctly with `level: "info"`, write artifact descriptions, etc. The template + auto-deploy-on-push + DB-driven allowlist collapse this from 30+ minutes of "how does HQ work" to ~5 minutes of "I read the template and did the steps."
| Phase | What | Effort | Operator-visible win |
|---|---|---|---|
| 1 | Auto-deploy on push (GitHub webhook + per-repo config) | 2-3 days | `git push` → URL live. The Cloudflare Pages moment. |
| 2 | New-Mavis template (design → preview → deploy) | 1 day | Every new Mavis thread knows what to do without asking |
| 3 | Dynamic random subdomains + DB-driven allowlist | 1-2 days | `abc1dxyz.freshvibeapps.com` works, no chown, no DB edit |
| 4 | Object storage (R2-style) | 2-3 days | Drop a file anywhere, get a URL |
| 5 | Mavis-facing dashboard | 2-3 days | Mavis sees all its stuff in one UI |
**Total**: ~10-12 days of work. Not all needed at once.
**Phase 1 is the most important** — auto-deploy on push is the single feature that makes HQ feel like Cloudflare. Without it, every deploy is a manual API call. With it, Mavis can just `git push` and walk away.
1. **Phase ordering** — go straight to Phase 1, or do Phase 2 (template) first because the template tells Mavis sessions to use the new auto-deploy?
2. **GitHub integration** — G's GitHub is `avidtech6`. Webhook receiver needs G to set the webhook URL in each repo, or do it org-wide? Or HQ auto-registers when a Mavis calls `POST /api/mavis/repos {repo_url, auto_deploy: true}` and the panel figures it out?
3. **Per-PR previews** — do you want `git push origin feature-thing` to create `feature-thing.<repo>.freshvibeapps.com` automatically? Or is that overkill?
4. **Random preview expiry** — 7 days right? Or longer?
5. **The 35-name allowlist** — can I rip it out and replace with DB-driven, or do you want the explicit list to stay as a "permanent sites" filter?