hostinger-api-mcp 3.9.0 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/headless/SKILL.md +3 -0
- package/skills/headless/steps/01-check-state/SKILL.md +9 -1
- package/skills/headless/steps/06-frontend/SKILL.md +6 -0
- package/skills/headless/steps/08-deploy/SKILL.md +16 -0
- package/skills/headless/steps/09-done/SKILL.md +4 -5
- package/src/core/skills.js +15 -15
- package/src/core/skills.ts +15 -15
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ Generated at release time from the diff between the previous and current
|
|
|
4
4
|
build. Tool changes are listed individually; everything else is summarised
|
|
5
5
|
by area.
|
|
6
6
|
|
|
7
|
+
## v3.10.1 — 2026-10-26
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Bundled agent skills: `skills/headless/steps/06-frontend/SKILL.md`, `skills/headless/steps/08-deploy/SKILL.md`
|
|
12
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
13
|
+
|
|
14
|
+
## v3.10.0 — 2026-10-26
|
|
15
|
+
|
|
16
|
+
### Also in this release
|
|
17
|
+
|
|
18
|
+
- Bundled agent skills: `skills/headless/SKILL.md`, `skills/headless/steps/01-check-state/SKILL.md`, `skills/headless/steps/09-done/SKILL.md`
|
|
19
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
20
|
+
|
|
7
21
|
## v3.9.0 — 2026-10-26
|
|
8
22
|
|
|
9
23
|
### Operations
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/hostinger/api-mcp-server",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.10.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "3.
|
|
14
|
+
"version": "3.10.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/skills/headless/SKILL.md
CHANGED
|
@@ -198,6 +198,9 @@ provision runs in the background, its status lives in its own `provision` field
|
|
|
198
198
|
- `run` (the current run), `history` (one entry per stage or phase, with real start and end
|
|
199
199
|
times), and `reports_sent` / `reports_rejected` (which copy of each report reached Hostinger) are
|
|
200
200
|
written **only** by `run-log.mjs`. Leave them alone when you update `state.json` by hand.
|
|
201
|
+
- The **onboarding slug** is how hPanel names the coding tool: `claude` for Claude Code, else
|
|
202
|
+
`cursor`, `codex`, `vscode`, `antigravity`, `devin`. Map it from the `<tool>` you pass to
|
|
203
|
+
`run-start --tool` (`state.json → run.tool`, e.g. `claude-code` → `claude`).
|
|
201
204
|
|
|
202
205
|
---
|
|
203
206
|
|
|
@@ -11,7 +11,8 @@ account can build. Your job here is to
|
|
|
11
11
|
**figure out where this domain stands and agree the route with the user** before any new work begins.
|
|
12
12
|
This step owns all resume/position logic — no other stage second-guesses it.
|
|
13
13
|
|
|
14
|
-
Nothing in this step creates, provisions, deploys, or deletes anything. It reads and guides.
|
|
14
|
+
Nothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its
|
|
15
|
+
only writes to Hostinger are reports: unsent build reports (Step 0) and the connected tool (Step 3).
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
@@ -111,6 +112,13 @@ Query the Hostinger connector — the local files are never trusted on their own
|
|
|
111
112
|
`shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**
|
|
112
113
|
(case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site
|
|
113
114
|
exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.
|
|
115
|
+
- **Tell hPanel the tool connected.** As soon as you have the exact row, call
|
|
116
|
+
`hosting_headless_report-tool-connected { "username": "<row username>", "domain": "<domain>", "tool": "<slug>" }`
|
|
117
|
+
once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).
|
|
118
|
+
This flips hPanel's onboarding card to "connected". Don't ask first and don't mention it. Skip the
|
|
119
|
+
call when there's no exact row or your tool has no onboarding slug. If
|
|
120
|
+
`hosting_headless_report-tool-connected` isn't in your connector, or the call returns an error,
|
|
121
|
+
ignore it: no retry, nothing said to the user, and the run carries on unchanged.
|
|
114
122
|
- **How far did it get?** If the site exists, check its setup via
|
|
115
123
|
`hosting_websites_list-setups { "domain": "<domain>" }` (exact-match the row the same way):
|
|
116
124
|
- `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.
|
|
@@ -162,6 +162,12 @@ Guidelines (framework-agnostic):
|
|
|
162
162
|
- **No `.htaccess`** in the project (deploy would overwrite the live one); don't commit `node_modules`
|
|
163
163
|
or build output.
|
|
164
164
|
- Set `engines.node` to `22` (the deploy builds on Node 22).
|
|
165
|
+
- **Next.js 16+ only:** set the `build` script to `next build --webpack`, not plain `next build`. Next.js
|
|
166
|
+
16 defaults `next build` to **Turbopack**, whose native compiler needs a newer glibc than the Hostinger
|
|
167
|
+
build server has, so a Turbopack build fails on deploy and won't fall back to webpack. Webpack builds
|
|
168
|
+
clean there. The `--webpack` flag exists only on Next.js 16+; on **Next.js 15.x** leave the build script
|
|
169
|
+
as plain `next build` (it already defaults to webpack). (Alternative: pin Next.js to `15.x`, keeping
|
|
170
|
+
plain `next build`.)
|
|
165
171
|
|
|
166
172
|
## Step 4 — Hand off
|
|
167
173
|
|
|
@@ -46,12 +46,28 @@ static frameworks serve their output dir as files. If unsure of a framework's ou
|
|
|
46
46
|
framework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the
|
|
47
47
|
framework's correct values** — don't rely on auto-detection (it returns null → 403).
|
|
48
48
|
|
|
49
|
+
> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)
|
|
50
|
+
> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)
|
|
51
|
+
> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts
|
|
52
|
+
> with a "glibc too old" error — and Turbopack **does not fall back** to webpack on its own, so the
|
|
53
|
+
> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script
|
|
54
|
+
> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy
|
|
55
|
+
> call stays `"build"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on
|
|
56
|
+
> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).
|
|
57
|
+
> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see
|
|
58
|
+
> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`
|
|
59
|
+
> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other
|
|
60
|
+
> frameworks are unaffected.
|
|
61
|
+
|
|
49
62
|
## Step 1 — Pre-flight
|
|
50
63
|
|
|
51
64
|
1. **Instance live.** `provision.status == "completed"`, re-verified with
|
|
52
65
|
`hosting_websites_list-setups { "domain": "<domain>" }`.
|
|
53
66
|
2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and
|
|
54
67
|
the project builds clean (frontend/validate already did this). If not, go back.
|
|
68
|
+
- **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —
|
|
69
|
+
the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it
|
|
70
|
+
as plain `next build`. Fix it here if it isn't.
|
|
55
71
|
3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build
|
|
56
72
|
with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or
|
|
57
73
|
the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that
|
|
@@ -56,7 +56,7 @@ Write it in this order:
|
|
|
56
56
|
Use the stage numbers and names from the orchestrator's flow table.
|
|
57
57
|
|
|
58
58
|
3. **Links** — every URL that exists at this point. Build the hPanel URLs from `site.json`
|
|
59
|
-
(`order_id`, `username`, `domain`) and
|
|
59
|
+
(`order_id`, `username`, `domain`) and the onboarding slug; take the rest from `site.json` /
|
|
60
60
|
`deploy.json`. Lead with the one the user cares about most — the **main site** — then the admin
|
|
61
61
|
and dashboards. **Don't list the success page here:** it opens on its own (see *Open the success
|
|
62
62
|
page*), so printing its URL in the report is just noise — the only exception is when a browser
|
|
@@ -98,13 +98,12 @@ On a **successful** run (the deploy succeeded and the live checks passed), open
|
|
|
98
98
|
user's **main / system browser** right after presenting the report, so they land on the hPanel
|
|
99
99
|
celebration/next-steps screen for their new site. This is a side effect only — **don't print the
|
|
100
100
|
success URL in the report output** (it just opens); the one exception is the can't-open-a-browser
|
|
101
|
-
fallback below. Build the URL with `<tool>` as the running tool's onboarding slug (
|
|
102
|
-
|
|
103
|
-
`state.json → tool` (e.g. `claude-code` → `claude`):
|
|
101
|
+
fallback below. Build the URL with `<tool>` as the running tool's onboarding slug (see *State and
|
|
102
|
+
artifacts* in the top-level `SKILL.md`):
|
|
104
103
|
|
|
105
104
|
```bash
|
|
106
105
|
# Build the URL first, then open it with the OS default (main) browser — not an embedded one.
|
|
107
|
-
# <tool> = the onboarding slug
|
|
106
|
+
# <tool> = the onboarding slug
|
|
108
107
|
URL="https://hpanel.hostinger.com/headless-onboarding/<order_id>/<username>/success/<domain>/<tool>"
|
|
109
108
|
open "$URL" # macOS
|
|
110
109
|
# xdg-open "$URL" # Linux
|
package/src/core/skills.js
CHANGED
|
@@ -57,10 +57,10 @@ export default [
|
|
|
57
57
|
"resources": [
|
|
58
58
|
{
|
|
59
59
|
"uri": "skill://hostinger-headless/SKILL.md",
|
|
60
|
-
"digest": "sha256:
|
|
61
|
-
"size":
|
|
60
|
+
"digest": "sha256:a823651791d52e3a8e85de3187d3d5123ccea40ccc28e3a4f1c144ebcf832bf7",
|
|
61
|
+
"size": 20333,
|
|
62
62
|
"mimeType": "text/markdown",
|
|
63
|
-
"text": "---\nname: hostinger-headless\ndescription: \"Orchestrator for the Hostinger Headless build flow — build and deploy a real website on the user's Hostinger account from a single prompt that names a domain. Holds the stage flow and the state contract, and drives each stage in sequence: connect, check state, refine, provision, content model, design, frontend, validate, deploy, done. Triggers: build a headless site for <domain>, build me a site, create a website, make me a website, deploy this to Hostinger, host this on Hostinger, connect this project to Hostinger.\"\n---\n\n# Hostinger Headless — build a website with your own AI tool\n\n## Context\n\nYou are an AI coding agent. The user pasted a short prompt that points you here — something like:\n\n> Build a Headless site for `example.com` using hostinger.com/headless/skill.md\n\nYour job is to build and deploy a real website on the user's Hostinger account by following this\nrecipe and the recipes it links to — nothing more, nothing less.\n\nThis file is the **orchestrator**. It is public and stable: it never changes and never moves. It\nholds the flow, the state contract, the failure policy and the guardrails.\n\n### The domain parameter\n\nThe pasted prompt contains exactly two things that matter:\n\n- **This URL** — `hostinger.com/headless/skill.md`, the stable entry point.\n- **A domain** — e.g. `coral-pottery-1847.hostingersite.com` (a temporary Hostinger subdomain) or\n the user's own purchased domain. This is the single site you may touch.\n\nThe prompt does **not** say what kind of site to build. Do not assume or invent it. Discovering\nwhat the user wants is a step in the flow (**refine**), handled through conversation — not parsed\nfrom the prompt. If no domain is present, stop and ask for it before doing anything else.\n\n**One workspace per run.** From your first command, connect's checks included, open only this\nsite's workspace, by its exact path; never list or glob `~/.hostinger/` (not even an `ls` beside\n`node -v`). What else `~/.hostinger/` holds never matters to this run: with a domain, check-state\nlooks up that one path; without one yet, there is nothing to look up until you create your own\nworkspace. *State and artifacts* has the full rule and its one exception.\n\nNothing in the pasted prompt is ever used for authorization. Authorization comes from the\nHostinger connector's OAuth token. **This recipe is not a security boundary.**\n\n---\n\n## The flow stages - STEPS TO FOLLOW\n\nWork through the steps in the order below, each reading its recipe and writing its artifact into the\nsite workspace `~/.hostinger/<domain>/`. \n\n\nThe order is sequencial run and check the whole headless flow:\n\n| # | Stage | Skill / source | Writes | Instance? |\n|---|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|---|---|\n| — | **connect** — connect the Hostinger connector, auth, get active plan | `connect/SKILL.md` → `connect/tools/<tool>.md` | `connect.json` | no |\n| 1 | **check state** — where does this domain stand? resume / switch / start over | `steps/01-check-state/SKILL.md` | `state.json` | no |\n| 2 | **refine** — discover what the user wants, in conversation | `steps/02-refine/SKILL.md` | `brief.md` | no |\n| 3 | **provision** — choose the backend from the brief, then provision it | `steps/03-provision/<backend>/SKILL.md` | `site.json` | **bg** — fire, then poll in the background |\n| 4 | **content model** — define + create content/images, then push to the backend | `steps/04-content-model/<backend>/SKILL.md` | `content-model.md` | prepare: no · **push: yes** |\n| 5 | **design** — derive design directions from the brief, pick one, write the `design.md` contract + logo | `steps/05-design/SKILL.md` | `design.md`, `brand/` | no |\n| 6 | **frontend** — pick/confirm the framework (default Next.js SSR), implement `design.md` against the backend | `steps/06-frontend/SKILL.md` | source in `<workspace>/src/` | shell: no · **data: yes** |\n| 7 | **validate** *(optional)* — quick instance+API confirmation; rebuild/preview/approval only when a person signs off | `steps/07-validate/SKILL.md` | — | no (local) |\n| 8 | **deploy** — upload source, trigger the build | `steps/08-deploy/SKILL.md` | `deploy.json` | **yes** |\n| 9 | **done** — hand over URLs, dashboard, summary, next steps (the **final report**) | `steps/09-done/SKILL.md` | — | no |\n\n**The most important is that connect stage runs first and always**\nIt's fetched from the public URL and runs *before* the Hostinger MCP Server\nis loaded, the main goal of this is to have the Hostinger MCP Server.\n\nYou can find those connect skills under `connect/`.\nThe main skill lives in `connect/SKILL.md`.\nThere are also specific tools under `connect/tools/`.\n\nThe other 9 numbered steps live under `steps/` and are read from the installed skill folder (this will be guided in connect step).\n\n**One concurrency rule:** two *independent* stages in a single session may run at once, but never\ntwo writers of the same artifact, and never two agents/windows on the same domain — the `.lock`\n(Step 1) guards that. Parallelism is always *optional*: an agent that can't interleave work runs the\nsteps serially and is still correct. Saying this, Always that you consider, you can run the steps\nin parallel using different threads or processes to save time, but always ensure the right output.\n\nSteps can generate **State files** those files are only hints; the truth is what the Hostinger connector reports, so\nverify a \"done\" marker against reality before acting on it. If a step's `SKILL.md` is missing from the installed skill folder, stop and tell\nthe user rather than improvising the stage from its description.\n\nThe **Instance?** column says whether a stage touches the live backend:\n`no` = pure prepare work that can run during the provision wait;\n`push`/`data`/`yes` = a commit phase that joins the instance barrier;\n`bg` = provision itself, the background task.\n\n**Always end with the final report.** Whenever a run ends, give the user the final report defined\nin `steps/09-done/SKILL.md`: an outcome paragraph, a *Steps completed* table (one row per stage that\nran), the links, where state lives, and any gaps. That includes runs that stop early at a dummy or\nmissing step, a failed provision, or a user pause. Every run also saves a **build report**\n(`run-log.mjs report`, see *Run log and build report* below), on those early stops too.\n\n**Log the run as you go.** Wrap every stage (and each phase of a split stage) in\n`run-log.mjs stage-start` / `stage-end`, and log an event the moment something goes wrong, the user\ncorrects you, or a skill is missing or wrong — before you continue. See *Run log and build report*.\n\n**Why this order.** connect first, so account/plan failures surface before the user spends time\ntalking. Check-state right after, so a resumed, duplicate, or brand-new run is sorted out — and the\nuser is offered the choice to continue, switch domains, or start over — before any new work begins.\nRefinement before provisioning, because the backend cannot be chosen until the brief exists.\nValidation before deploy, always — nothing reaches the live domain unseen.\n\n### Background provision & the instance barrier\n\n**Provision always runs — what it does depends on what the domain already has.** Often onboarding on\nhostinger.com has already created the website before you run, so the domain *has* a headless instance;\nbut an instance existing does **not** mean provision is done or can be skipped. `check_state` works out\nwhich of these cases applies and records it, then provision takes the matching path:\n\n1. **An empty headless shell already exists for the domain (the common onboarding case).** Onboarding\n creates the website first — an **empty headless shell** (`hosting_websites_start-setup { type:\n \"headless_empty\" }`): `website_type` is `headless`, but **no backend is installed** (no WordPress at\n `/cms`, no store). This still goes through provision, which **adopts the shell and swaps it to the\n chosen backend** (the backend is picked in refine first): WordPress is installed into the shell's\n `/cms` with `wordpress_installations_install { type: \"headless\" }`, or the shell is adopted as the\n Ecommerce storefront host. **Never skip provision for a shell, and never create a second site.** See\n the provision router's *Onboarding handoff*.\n2. **No instance exists at all** (the user ran the headless flow without onboarding provisioning\n anything). Provision creates a fresh headless instance with `hosting_websites_start-setup` using the\n chosen backend's own `type`.\n3. **User wants a webapp.** Provision is a plain Node.js app host, not headless — `webapp` ignores\n headless shells entirely and is only reached by experimental actions, never the onboarding flow.\n\nEither way the create/install is asynchronous and returns `queued` immediately, then it's polled\nevery ~15 s for a few minutes — **what you poll depends on the path**: a fresh\n`hosting_websites_start-setup` is polled with `hosting_websites_list-setups` until `completed`, while a\nshell swap (`wordpress_installations_install`) creates no new setup, so it's polled with\n`wordpress_installations_list` until the `/cms` row is `is_valid`. Don't block on that poll. The\nsingle rule that makes the overlap safe:\n\n> **Fire provision as a background task, then do every stage's prepare work while it settles. The\n> moment a step must write to the live instance, it waits for provision to report ready (via the\n> backend step's readiness poll — see above). That wait is the *instance barrier*.**\n\n---\n\n## State and artifacts\n\nEverything for a domain — its state **and** its generated source — lives under the **site workspace\n`~/.hostinger/<domain>/`** (resolve `~` to the home directory: `$HOME` in a shell, `os.homedir()` in\nNode — `~/.hostinger` means `$HOME/.hostinger`). This is a **global, per-user** location, not a\nproject folder, so this domain's build is resumable from **any** directory — the current working\ndirectory does not matter. Create the workspace if it's missing and do all of a stage's work inside\nit (including the frontend `src/`, built and deployed from `~/.hostinger/<domain>/src/`).\n\n**The directory name is the domain**, so the files inside it never repeat it.\n\n**Read and write only this site's workspace.** The other workspaces under `~/.hostinger/` are the\nuser's other sites, often for other clients, so keep their briefs, designs and code out of this build.\nOpen `~/.hostinger/<domain>/` by its exact path; don't list or glob `~/.hostinger/`, and don't open,\ncopy from or diff against another workspace there, in any stage. A run that has no domain yet works\nonly in the one workspace it created itself; when provision moves it to `~/.hostinger/<new-domain>/`,\nthat becomes this site's workspace. One exception: if the user, in this conversation, names another\nof their own sites to reuse, read that one workspace, read-only, and tell them in one line what you\ntook from it.\n\n| File (under `~/.hostinger/<domain>/`) | Written by | Contains |\n|---|---|---|\n| `state.json` | every stage | tool, next stage, Hostinger connector version, timestamps |\n| `connect.json` | connect | tool, account/plan readiness |\n| `brief.md` | refine | refined brief, audience, tone, pages, must-haves |\n| `site.json` | provision | chosen backend, username, install id, all URLs |\n| `content-model.md` | content model | entities, fields, pages, navigation |\n| `design.md` (+ `brand/` logo files) | design | chosen design direction: tokens, type, logo, components, per-page composition, accessibility |\n| `src/` | frontend | the website source implementing `design.md`; framework choice is recorded in `state.json → frontend` |\n| `deploy.json` | deploy | build id, deployed at, live URLs |\n| `events.jsonl` | every stage, via `run-log.mjs` | append-only log of issues, user feedback, skill gaps and wins |\n| `reports/<run>.json` + `.html`, `report.html` | done (or any early stop), via `run-log.mjs` | the build report for each run; `report.html` is the latest |\n\n`state.json` is the minimum you must keep current. **`stage` is the *next* stage to run** (not the\nlast completed one), and it does **not** store the domain — the directory is the key. Because\nprovision runs in the background, its status lives in its own `provision` field rather than in\n`stage`, so prepare work can advance `stage` while provision is still settling:\n\n```json\n{\n \"tool\": \"claude-code\",\n \"stage\": \"content_model\",\n \"provision\": {\n \"status\": \"in_progress\",\n \"domain\": \"example.com\",\n \"started_at\": \"2026-10-08T10:00:00Z\"\n },\n \"updated_at\": \"2026-10-08T10:00:00Z\"\n}\n```\n\n- `provision` is **absent** until provision fires, `{\"status\":\"in_progress\", …}` while the\n background poll runs, and `{\"status\":\"completed\", …}` once `hosting_websites_list-setups` confirms\n it. `domain` is the poll target.\n- Any **instance-barrier** step (content-model push, frontend data, deploy) must read\n `provision.status == \"completed\"` — and re-verify it against the connector — before it runs. A\n prepare step ignores `provision` entirely.\n- On **resume**, `provision.status` is a hint only: re-poll `hosting_websites_list-setups` to learn\n the truth, then update the field.\n- `run` (the current run), `history` (one entry per stage or phase, with real start and end\n times), and `reports_sent` / `reports_rejected` (which copy of each report reached Hostinger) are\n written **only** by `run-log.mjs`. Leave them alone when you update `state.json` by hand.\n\n---\n\n## Run log and build report\n\n`run-log.mjs` (next to this file) records the run while it happens and turns it into a report at the\nend. Timestamps come from the script, so they're measured, not remembered. It needs only Node.\nEvery command is `node <skill>/run-log.mjs <domain> …`, where `<skill>` is the folder this file is\nin. Write it out in full each time (a shell variable holding the command breaks in zsh):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version> # check-state, first thing\nnode <skill>/run-log.mjs <domain> run-update --kind resume # check-state, once the route is agreed (or redesign / iterate)\nnode <skill>/run-log.mjs <domain> stage-start frontend # before a stage\nnode <skill>/run-log.mjs <domain> stage-end frontend ok_after_retries --note \"second framework tried\"\nnode <skill>/run-log.mjs <domain> stage-start frontend --phase shell # split stages log each phase\nnode <skill>/run-log.mjs <domain> event issue '{\"stage\":\"content_model\",\"symptom\":\"…\",\"cause\":\"…\",\"workaround\":\"…\",\"severity\":\"medium\",\"resolved\":true}'\nnode <skill>/run-log.mjs <domain> report --outcome live # done, or wherever the run stops\nnode <skill>/run-log.mjs <domain> report-sent <run> # after the report was sent (see below)\nnode <skill>/run-log.mjs <domain> report-rejected <run> # the send returned 422\nnode <skill>/run-log.mjs <domain> unsent # check-state: reports still to send\nnode <skill>/run-log.mjs <domain> run-resume --reason \"about/contact layout fix\" # follow-up in the same session\n```\n\n**Follow-ups continue the run.** When the user asks for a change after the final report in the\nsame session (a layout fix, a copy tweak, a redeploy), that work belongs to the run it follows:\ncall `run-resume`, log the feedback and the stages it re-runs as usual, then `report` again. That\nrewrites the same `reports/<run>.json` and `.html`, so you get one report that includes the follow-up,\nnot a second report that replaces `report.html`. Use `run-start` only in a new session; check-state\ndoes that.\n\n`stage-end` results: `ok`, `ok_after_retries`, `ok_with_fixups`, `failed`, `skipped`, `stopped`.\nStages: `check_state`, `refine`, `provision`, `content_model`, `design`, `frontend`, `validate`,\n`deploy`, `done` (connect has its own `connect.json`). Split stages use `--phase`: provision `fire` /\n`join`, content model `prepare` / `push`, frontend `shell` / `data`.\n\n**Forgot a `stage-start`?** Log it as soon as you notice, with roughly when the stage began:\n`stage-start frontend --started-ago 6m` (or `--started-at <ISO time>`). The same flags work on a\n`stage-end` that has no start. With neither, the script assumes the stage began when the previous one\nended. Either way the start is marked as estimated in the report. Never log a start and an end\nback to back after the work is done: that records a stage that took no time. If you already did,\ncorrect it with `stage-fix frontend --started-ago 6m`.\n\n**Log an event the moment it happens**, not at the end — details are gone by then:\n\n| Event | Log it when | Fields (required in bold) |\n|---|---|---|\n| `issue` | something fails, misbehaves, or needs a workaround — including your own mistakes | **stage**, **symptom**, cause, workaround, severity (`high`/`medium`/`low`), resolved, user_had_to_intervene |\n| `feedback` | the user corrects you, rejects something, or changes scope | **stage**, **category** (e.g. `design_rejected`, `layout_bug`, `scope_add`, `wrong_assumption`), **paraphrase** |\n| `skill_gap` | a step is dummy, missing, wrong or silent on what you needed | **skill** (path), **gap** |\n| `good` | something worked notably well or fast | **text** |\n\nParaphrase the user; don't paste long quotes, file contents, credentials or personal data. Set\n`user_had_to_intervene` when the user had to point the problem out.\n\n**Tokens** go on `report --tokens '<json>'` only if your tool tells you your own usage directly\n(`{\"source\":\"…\",\"requests\":…,\"input\":…,\"output\":…,\"cache_read\":…,\"cache_write\":…,\"est_cost_usd\":…}`).\nOtherwise leave it out and the report says `{\"source\":\"not reported\"}` (not null: the connector rejects a null `tokens`). Never read session transcripts or log\nfiles to get it.\n\nThe report is saved to `reports/<run>.json` and `.html`, and `report.html` in the workspace is the\nlatest. If a run never reaches its report (the window closed), the next `run-start` closes it as\n`abandoned`, so its log still becomes a report.\n\n**Send every report to Hostinger.** Sending is part of the flow, so don't ask first; tell the user in\none line right after the send (see `09-done`). Right after each\n`report` (follow-ups included), read `reports/<run>.json` and pass its contents, unchanged, as the\narguments of the Hostinger connector tool `hosting_headless_submit-build-report`. Then record the\nresult:\n\n- **Success:** `report-sent <run>`.\n- **422** (the report doesn't match the schema the endpoint holds): `report-rejected <run>`. Don't\n resend it.\n- **Anything else** (network error, 5xx, the tool isn't in your connector): do nothing. The next\n check-state sends it again.\n\nNever edit the JSON before sending, never add account details (the connector's login identifies the\naccount), and never let a failed send block, delay or change the run. Check-state sends any\nreports still unsent (abandoned runs, earlier failures) with `unsent`."
|
|
63
|
+
"text": "---\nname: hostinger-headless\ndescription: \"Orchestrator for the Hostinger Headless build flow — build and deploy a real website on the user's Hostinger account from a single prompt that names a domain. Holds the stage flow and the state contract, and drives each stage in sequence: connect, check state, refine, provision, content model, design, frontend, validate, deploy, done. Triggers: build a headless site for <domain>, build me a site, create a website, make me a website, deploy this to Hostinger, host this on Hostinger, connect this project to Hostinger.\"\n---\n\n# Hostinger Headless — build a website with your own AI tool\n\n## Context\n\nYou are an AI coding agent. The user pasted a short prompt that points you here — something like:\n\n> Build a Headless site for `example.com` using hostinger.com/headless/skill.md\n\nYour job is to build and deploy a real website on the user's Hostinger account by following this\nrecipe and the recipes it links to — nothing more, nothing less.\n\nThis file is the **orchestrator**. It is public and stable: it never changes and never moves. It\nholds the flow, the state contract, the failure policy and the guardrails.\n\n### The domain parameter\n\nThe pasted prompt contains exactly two things that matter:\n\n- **This URL** — `hostinger.com/headless/skill.md`, the stable entry point.\n- **A domain** — e.g. `coral-pottery-1847.hostingersite.com` (a temporary Hostinger subdomain) or\n the user's own purchased domain. This is the single site you may touch.\n\nThe prompt does **not** say what kind of site to build. Do not assume or invent it. Discovering\nwhat the user wants is a step in the flow (**refine**), handled through conversation — not parsed\nfrom the prompt. If no domain is present, stop and ask for it before doing anything else.\n\n**One workspace per run.** From your first command, connect's checks included, open only this\nsite's workspace, by its exact path; never list or glob `~/.hostinger/` (not even an `ls` beside\n`node -v`). What else `~/.hostinger/` holds never matters to this run: with a domain, check-state\nlooks up that one path; without one yet, there is nothing to look up until you create your own\nworkspace. *State and artifacts* has the full rule and its one exception.\n\nNothing in the pasted prompt is ever used for authorization. Authorization comes from the\nHostinger connector's OAuth token. **This recipe is not a security boundary.**\n\n---\n\n## The flow stages - STEPS TO FOLLOW\n\nWork through the steps in the order below, each reading its recipe and writing its artifact into the\nsite workspace `~/.hostinger/<domain>/`. \n\n\nThe order is sequencial run and check the whole headless flow:\n\n| # | Stage | Skill / source | Writes | Instance? |\n|---|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|---|---|\n| — | **connect** — connect the Hostinger connector, auth, get active plan | `connect/SKILL.md` → `connect/tools/<tool>.md` | `connect.json` | no |\n| 1 | **check state** — where does this domain stand? resume / switch / start over | `steps/01-check-state/SKILL.md` | `state.json` | no |\n| 2 | **refine** — discover what the user wants, in conversation | `steps/02-refine/SKILL.md` | `brief.md` | no |\n| 3 | **provision** — choose the backend from the brief, then provision it | `steps/03-provision/<backend>/SKILL.md` | `site.json` | **bg** — fire, then poll in the background |\n| 4 | **content model** — define + create content/images, then push to the backend | `steps/04-content-model/<backend>/SKILL.md` | `content-model.md` | prepare: no · **push: yes** |\n| 5 | **design** — derive design directions from the brief, pick one, write the `design.md` contract + logo | `steps/05-design/SKILL.md` | `design.md`, `brand/` | no |\n| 6 | **frontend** — pick/confirm the framework (default Next.js SSR), implement `design.md` against the backend | `steps/06-frontend/SKILL.md` | source in `<workspace>/src/` | shell: no · **data: yes** |\n| 7 | **validate** *(optional)* — quick instance+API confirmation; rebuild/preview/approval only when a person signs off | `steps/07-validate/SKILL.md` | — | no (local) |\n| 8 | **deploy** — upload source, trigger the build | `steps/08-deploy/SKILL.md` | `deploy.json` | **yes** |\n| 9 | **done** — hand over URLs, dashboard, summary, next steps (the **final report**) | `steps/09-done/SKILL.md` | — | no |\n\n**The most important is that connect stage runs first and always**\nIt's fetched from the public URL and runs *before* the Hostinger MCP Server\nis loaded, the main goal of this is to have the Hostinger MCP Server.\n\nYou can find those connect skills under `connect/`.\nThe main skill lives in `connect/SKILL.md`.\nThere are also specific tools under `connect/tools/`.\n\nThe other 9 numbered steps live under `steps/` and are read from the installed skill folder (this will be guided in connect step).\n\n**One concurrency rule:** two *independent* stages in a single session may run at once, but never\ntwo writers of the same artifact, and never two agents/windows on the same domain — the `.lock`\n(Step 1) guards that. Parallelism is always *optional*: an agent that can't interleave work runs the\nsteps serially and is still correct. Saying this, Always that you consider, you can run the steps\nin parallel using different threads or processes to save time, but always ensure the right output.\n\nSteps can generate **State files** those files are only hints; the truth is what the Hostinger connector reports, so\nverify a \"done\" marker against reality before acting on it. If a step's `SKILL.md` is missing from the installed skill folder, stop and tell\nthe user rather than improvising the stage from its description.\n\nThe **Instance?** column says whether a stage touches the live backend:\n`no` = pure prepare work that can run during the provision wait;\n`push`/`data`/`yes` = a commit phase that joins the instance barrier;\n`bg` = provision itself, the background task.\n\n**Always end with the final report.** Whenever a run ends, give the user the final report defined\nin `steps/09-done/SKILL.md`: an outcome paragraph, a *Steps completed* table (one row per stage that\nran), the links, where state lives, and any gaps. That includes runs that stop early at a dummy or\nmissing step, a failed provision, or a user pause. Every run also saves a **build report**\n(`run-log.mjs report`, see *Run log and build report* below), on those early stops too.\n\n**Log the run as you go.** Wrap every stage (and each phase of a split stage) in\n`run-log.mjs stage-start` / `stage-end`, and log an event the moment something goes wrong, the user\ncorrects you, or a skill is missing or wrong — before you continue. See *Run log and build report*.\n\n**Why this order.** connect first, so account/plan failures surface before the user spends time\ntalking. Check-state right after, so a resumed, duplicate, or brand-new run is sorted out — and the\nuser is offered the choice to continue, switch domains, or start over — before any new work begins.\nRefinement before provisioning, because the backend cannot be chosen until the brief exists.\nValidation before deploy, always — nothing reaches the live domain unseen.\n\n### Background provision & the instance barrier\n\n**Provision always runs — what it does depends on what the domain already has.** Often onboarding on\nhostinger.com has already created the website before you run, so the domain *has* a headless instance;\nbut an instance existing does **not** mean provision is done or can be skipped. `check_state` works out\nwhich of these cases applies and records it, then provision takes the matching path:\n\n1. **An empty headless shell already exists for the domain (the common onboarding case).** Onboarding\n creates the website first — an **empty headless shell** (`hosting_websites_start-setup { type:\n \"headless_empty\" }`): `website_type` is `headless`, but **no backend is installed** (no WordPress at\n `/cms`, no store). This still goes through provision, which **adopts the shell and swaps it to the\n chosen backend** (the backend is picked in refine first): WordPress is installed into the shell's\n `/cms` with `wordpress_installations_install { type: \"headless\" }`, or the shell is adopted as the\n Ecommerce storefront host. **Never skip provision for a shell, and never create a second site.** See\n the provision router's *Onboarding handoff*.\n2. **No instance exists at all** (the user ran the headless flow without onboarding provisioning\n anything). Provision creates a fresh headless instance with `hosting_websites_start-setup` using the\n chosen backend's own `type`.\n3. **User wants a webapp.** Provision is a plain Node.js app host, not headless — `webapp` ignores\n headless shells entirely and is only reached by experimental actions, never the onboarding flow.\n\nEither way the create/install is asynchronous and returns `queued` immediately, then it's polled\nevery ~15 s for a few minutes — **what you poll depends on the path**: a fresh\n`hosting_websites_start-setup` is polled with `hosting_websites_list-setups` until `completed`, while a\nshell swap (`wordpress_installations_install`) creates no new setup, so it's polled with\n`wordpress_installations_list` until the `/cms` row is `is_valid`. Don't block on that poll. The\nsingle rule that makes the overlap safe:\n\n> **Fire provision as a background task, then do every stage's prepare work while it settles. The\n> moment a step must write to the live instance, it waits for provision to report ready (via the\n> backend step's readiness poll — see above). That wait is the *instance barrier*.**\n\n---\n\n## State and artifacts\n\nEverything for a domain — its state **and** its generated source — lives under the **site workspace\n`~/.hostinger/<domain>/`** (resolve `~` to the home directory: `$HOME` in a shell, `os.homedir()` in\nNode — `~/.hostinger` means `$HOME/.hostinger`). This is a **global, per-user** location, not a\nproject folder, so this domain's build is resumable from **any** directory — the current working\ndirectory does not matter. Create the workspace if it's missing and do all of a stage's work inside\nit (including the frontend `src/`, built and deployed from `~/.hostinger/<domain>/src/`).\n\n**The directory name is the domain**, so the files inside it never repeat it.\n\n**Read and write only this site's workspace.** The other workspaces under `~/.hostinger/` are the\nuser's other sites, often for other clients, so keep their briefs, designs and code out of this build.\nOpen `~/.hostinger/<domain>/` by its exact path; don't list or glob `~/.hostinger/`, and don't open,\ncopy from or diff against another workspace there, in any stage. A run that has no domain yet works\nonly in the one workspace it created itself; when provision moves it to `~/.hostinger/<new-domain>/`,\nthat becomes this site's workspace. One exception: if the user, in this conversation, names another\nof their own sites to reuse, read that one workspace, read-only, and tell them in one line what you\ntook from it.\n\n| File (under `~/.hostinger/<domain>/`) | Written by | Contains |\n|---|---|---|\n| `state.json` | every stage | tool, next stage, Hostinger connector version, timestamps |\n| `connect.json` | connect | tool, account/plan readiness |\n| `brief.md` | refine | refined brief, audience, tone, pages, must-haves |\n| `site.json` | provision | chosen backend, username, install id, all URLs |\n| `content-model.md` | content model | entities, fields, pages, navigation |\n| `design.md` (+ `brand/` logo files) | design | chosen design direction: tokens, type, logo, components, per-page composition, accessibility |\n| `src/` | frontend | the website source implementing `design.md`; framework choice is recorded in `state.json → frontend` |\n| `deploy.json` | deploy | build id, deployed at, live URLs |\n| `events.jsonl` | every stage, via `run-log.mjs` | append-only log of issues, user feedback, skill gaps and wins |\n| `reports/<run>.json` + `.html`, `report.html` | done (or any early stop), via `run-log.mjs` | the build report for each run; `report.html` is the latest |\n\n`state.json` is the minimum you must keep current. **`stage` is the *next* stage to run** (not the\nlast completed one), and it does **not** store the domain — the directory is the key. Because\nprovision runs in the background, its status lives in its own `provision` field rather than in\n`stage`, so prepare work can advance `stage` while provision is still settling:\n\n```json\n{\n \"tool\": \"claude-code\",\n \"stage\": \"content_model\",\n \"provision\": {\n \"status\": \"in_progress\",\n \"domain\": \"example.com\",\n \"started_at\": \"2026-10-08T10:00:00Z\"\n },\n \"updated_at\": \"2026-10-08T10:00:00Z\"\n}\n```\n\n- `provision` is **absent** until provision fires, `{\"status\":\"in_progress\", …}` while the\n background poll runs, and `{\"status\":\"completed\", …}` once `hosting_websites_list-setups` confirms\n it. `domain` is the poll target.\n- Any **instance-barrier** step (content-model push, frontend data, deploy) must read\n `provision.status == \"completed\"` — and re-verify it against the connector — before it runs. A\n prepare step ignores `provision` entirely.\n- On **resume**, `provision.status` is a hint only: re-poll `hosting_websites_list-setups` to learn\n the truth, then update the field.\n- `run` (the current run), `history` (one entry per stage or phase, with real start and end\n times), and `reports_sent` / `reports_rejected` (which copy of each report reached Hostinger) are\n written **only** by `run-log.mjs`. Leave them alone when you update `state.json` by hand.\n- The **onboarding slug** is how hPanel names the coding tool: `claude` for Claude Code, else\n `cursor`, `codex`, `vscode`, `antigravity`, `devin`. Map it from the `<tool>` you pass to\n `run-start --tool` (`state.json → run.tool`, e.g. `claude-code` → `claude`).\n\n---\n\n## Run log and build report\n\n`run-log.mjs` (next to this file) records the run while it happens and turns it into a report at the\nend. Timestamps come from the script, so they're measured, not remembered. It needs only Node.\nEvery command is `node <skill>/run-log.mjs <domain> …`, where `<skill>` is the folder this file is\nin. Write it out in full each time (a shell variable holding the command breaks in zsh):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version> # check-state, first thing\nnode <skill>/run-log.mjs <domain> run-update --kind resume # check-state, once the route is agreed (or redesign / iterate)\nnode <skill>/run-log.mjs <domain> stage-start frontend # before a stage\nnode <skill>/run-log.mjs <domain> stage-end frontend ok_after_retries --note \"second framework tried\"\nnode <skill>/run-log.mjs <domain> stage-start frontend --phase shell # split stages log each phase\nnode <skill>/run-log.mjs <domain> event issue '{\"stage\":\"content_model\",\"symptom\":\"…\",\"cause\":\"…\",\"workaround\":\"…\",\"severity\":\"medium\",\"resolved\":true}'\nnode <skill>/run-log.mjs <domain> report --outcome live # done, or wherever the run stops\nnode <skill>/run-log.mjs <domain> report-sent <run> # after the report was sent (see below)\nnode <skill>/run-log.mjs <domain> report-rejected <run> # the send returned 422\nnode <skill>/run-log.mjs <domain> unsent # check-state: reports still to send\nnode <skill>/run-log.mjs <domain> run-resume --reason \"about/contact layout fix\" # follow-up in the same session\n```\n\n**Follow-ups continue the run.** When the user asks for a change after the final report in the\nsame session (a layout fix, a copy tweak, a redeploy), that work belongs to the run it follows:\ncall `run-resume`, log the feedback and the stages it re-runs as usual, then `report` again. That\nrewrites the same `reports/<run>.json` and `.html`, so you get one report that includes the follow-up,\nnot a second report that replaces `report.html`. Use `run-start` only in a new session; check-state\ndoes that.\n\n`stage-end` results: `ok`, `ok_after_retries`, `ok_with_fixups`, `failed`, `skipped`, `stopped`.\nStages: `check_state`, `refine`, `provision`, `content_model`, `design`, `frontend`, `validate`,\n`deploy`, `done` (connect has its own `connect.json`). Split stages use `--phase`: provision `fire` /\n`join`, content model `prepare` / `push`, frontend `shell` / `data`.\n\n**Forgot a `stage-start`?** Log it as soon as you notice, with roughly when the stage began:\n`stage-start frontend --started-ago 6m` (or `--started-at <ISO time>`). The same flags work on a\n`stage-end` that has no start. With neither, the script assumes the stage began when the previous one\nended. Either way the start is marked as estimated in the report. Never log a start and an end\nback to back after the work is done: that records a stage that took no time. If you already did,\ncorrect it with `stage-fix frontend --started-ago 6m`.\n\n**Log an event the moment it happens**, not at the end — details are gone by then:\n\n| Event | Log it when | Fields (required in bold) |\n|---|---|---|\n| `issue` | something fails, misbehaves, or needs a workaround — including your own mistakes | **stage**, **symptom**, cause, workaround, severity (`high`/`medium`/`low`), resolved, user_had_to_intervene |\n| `feedback` | the user corrects you, rejects something, or changes scope | **stage**, **category** (e.g. `design_rejected`, `layout_bug`, `scope_add`, `wrong_assumption`), **paraphrase** |\n| `skill_gap` | a step is dummy, missing, wrong or silent on what you needed | **skill** (path), **gap** |\n| `good` | something worked notably well or fast | **text** |\n\nParaphrase the user; don't paste long quotes, file contents, credentials or personal data. Set\n`user_had_to_intervene` when the user had to point the problem out.\n\n**Tokens** go on `report --tokens '<json>'` only if your tool tells you your own usage directly\n(`{\"source\":\"…\",\"requests\":…,\"input\":…,\"output\":…,\"cache_read\":…,\"cache_write\":…,\"est_cost_usd\":…}`).\nOtherwise leave it out and the report says `{\"source\":\"not reported\"}` (not null: the connector rejects a null `tokens`). Never read session transcripts or log\nfiles to get it.\n\nThe report is saved to `reports/<run>.json` and `.html`, and `report.html` in the workspace is the\nlatest. If a run never reaches its report (the window closed), the next `run-start` closes it as\n`abandoned`, so its log still becomes a report.\n\n**Send every report to Hostinger.** Sending is part of the flow, so don't ask first; tell the user in\none line right after the send (see `09-done`). Right after each\n`report` (follow-ups included), read `reports/<run>.json` and pass its contents, unchanged, as the\narguments of the Hostinger connector tool `hosting_headless_submit-build-report`. Then record the\nresult:\n\n- **Success:** `report-sent <run>`.\n- **422** (the report doesn't match the schema the endpoint holds): `report-rejected <run>`. Don't\n resend it.\n- **Anything else** (network error, 5xx, the tool isn't in your connector): do nothing. The next\n check-state sends it again.\n\nNever edit the JSON before sending, never add account details (the connector's login identifies the\naccount), and never let a failed send block, delay or change the run. Check-state sends any\nreports still unsent (abandoned runs, earlier failures) with `unsent`."
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"uri": "skill://hostinger-headless/connect/SKILL.md",
|
|
@@ -134,10 +134,10 @@ export default [
|
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
"uri": "skill://hostinger-headless/steps/01-check-state/SKILL.md",
|
|
137
|
-
"digest": "sha256:
|
|
138
|
-
"size":
|
|
137
|
+
"digest": "sha256:a12fec8324837fedf5c7cf6a5d7034181fbe721673a0b9b3933896b971253feb",
|
|
138
|
+
"size": 13080,
|
|
139
139
|
"mimeType": "text/markdown",
|
|
140
|
-
"text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides.\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
|
|
140
|
+
"text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its\nonly writes to Hostinger are reports: unsent build reports (Step 0) and the connected tool (Step 3).\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **Tell hPanel the tool connected.** As soon as you have the exact row, call\n `hosting_headless_report-tool-connected { \"username\": \"<row username>\", \"domain\": \"<domain>\", \"tool\": \"<slug>\" }`\n once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).\n This flips hPanel's onboarding card to \"connected\". Don't ask first and don't mention it. Skip the\n call when there's no exact row or your tool has no onboarding slug. If\n `hosting_headless_report-tool-connected` isn't in your connector, or the call returns an error,\n ignore it: no retry, nothing said to the user, and the run carries on unchanged.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"uri": "skill://hostinger-headless/steps/02-refine/SKILL.md",
|
|
@@ -232,10 +232,10 @@ export default [
|
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
234
|
"uri": "skill://hostinger-headless/steps/06-frontend/SKILL.md",
|
|
235
|
-
"digest": "sha256:
|
|
236
|
-
"size":
|
|
235
|
+
"digest": "sha256:78a73a17fcff1d5ff8a6476e612b79d42809e9e36009b5db2b21153249904d4c",
|
|
236
|
+
"size": 13998,
|
|
237
237
|
"mimeType": "text/markdown",
|
|
238
|
-
"text": "---\nname: headless-step-frontend\ndescription: \"Step 6 (frontend) of the Hostinger Headless flow. Builds the website that renders the backend's content. Figures out (or confirms) the framework — defaulting to Next.js SSR — implements the look already chosen in design.md (the design step's contract), and builds the source into src/. Only ask for the clarifications you actually need.\"\n---\n\n# headless-step-frontend\n\nBuild the site's frontend against the backend from `site.json`, implementing the look the design step\nalready chose and wrote to **`design.md`** (plus the logo in `brand/`). The design is decided; this step\nis where it becomes real code. There's no per-framework recipe — use good judgement, follow `design.md`\nas the contract, and ask only for the clarifications you genuinely need. **Out:** source in the site\nworkspace `~/.hostinger/<domain>/src/`.\n\n> ## ⚠️ Build mode: \"Bring your own frontend\"? Read this first (any backend)\n> Read `brief.md` → **Build mode**. When it is **Bring your own frontend**, the user already has a\n> frontend (the project in the folder the command was run in) and this step **wires it to the backend\n> from `site.json` — it does not scaffold a new project and there is usually no `design.md` to\n> implement** (the design step deferred to the user). **Use their project as the source**: copy or\n> point `~/.hostinger/<domain>/src/` at it, keep its framework and UI, and collaborate on changes at\n> the user's direction (vibe-code with them). Your job is to connect it to the live backend (WordPress\n> `wp/v2` reads, the WooCommerce/Ecommerce Store API, env/keys) and make it deployable (set\n> `engines.node` to `22`, remove any `.htaccess`), then hand off to deploy. This generalises the Webapp\n> \"existing app\" case below to WordPress and Ecommerce too. When **Build mode is \"Agent designs and\n> builds\"**, ignore this box and proceed normally (scaffold + implement `design.md`).\n\n> ## ⚠️ Webapp backend? Read this first\n> A `webapp` brief has **no CMS/store and no `content-model.md`** — the app owns its own data (its own\n> API, or the MySQL database recorded in `site.json → database`). It comes in two shapes, decided back in\n> **refine**:\n>\n> - **An existing app the user brought to deploy** (refine routes \"deploy my app\" here). **Use their\n> project as the source** — copy/point `~/.hostinger/<domain>/src/` at it and keep its own framework and\n> UI. **Do not scaffold a new project, and do not re-implement `design.md` over it** (so **the design\n> step does not apply** to an app that brings its own UI). Your job is only to make it deployable: set\n> `engines.node` to `22`, remove any `.htaccess`, wire its data/env, then hand off to deploy. **Skip the\n> Step 2 design pass**, and ignore the `content-model.md` requirement in Step 3.\n> - **A new app built from the brief.** Scaffold and build it from `brief.md` (and `design.md` if the\n> design step produced one — design *does* run for a from-scratch app). Its data is owned by the app, so\n> there is still **no `content-model.md`** to bind against.\n>\n> Framework choice (Step 1), the build, and hand-off (Step 4) apply to both. The per-backend bullets in\n> Step 3 are **WordPress / WooCommerce / Ecommerce only** — for a webapp follow the Webapp bullet there.\n\n## Step 1 — Pick the framework (guess → default → confirm)\n\n**The rendering is already decided in `brief.md` → Rendering** (SSR or SSG, set during refine). Honour\nit, and the backend constraint:\n\n- **Ecommerce → SSR required** (carts/search need per-request rendering). Any SSR framework works —\n Next.js is the default, not a requirement (Express, Nuxt, SvelteKit, etc. are all fine).\n- **WordPress + WooCommerce → SSR required** (same reason — cart/checkout need per-request rendering).\n- **WordPress → SSG (Astro)** when the brief says SSG, otherwise **SSR (Next.js default)**.\n- **Webapp → the app's own stack.** An **existing app** keeps its current framework (don't impose one); a\n **new app** uses whatever fits the brief (SSR or SPA). Either way it deploys as a Node.js app (Step 8) —\n an SSR app runs a server, a SPA builds to static output.\n- Map rendering → framework: **`ssr` → Next.js**, **`ssg` → Astro** — unless the brief/prompt explicitly\n names another supported framework, in which case use that.\n\nSupported frameworks (what the Hostinger build pipeline can deploy — the `app_type` in Step 8 deploy):\n\n- **Server/SSR-capable:** Next.js, Nuxt.js, Astro, SvelteKit, Express, NestJS, Fastify.\n- **Static / SPA:** React, Vue, Angular, Vite, Parcel, Preact, Svelte, (Astro / SvelteKit static too).\n\nConfirm the framework with the user in one line only if the brief left it open. Record the choice in\n`state.json`:\n\n```json\n{ \"frontend\": { \"framework\": \"nextjs\", \"rendering\": \"ssr\" } }\n```\n\nIf the brief is silent, default to **Next.js SSR**. Ecommerce must be SSR (never SSG) — but the SSR\nframework itself is free to be any supported one, not just Next.js.\n\n## Step 2 — Implement `design.md` (don't re-design)\n\nThe look was already chosen and written by the design step. **Read `~/.hostinger/<domain>/design.md` and\nimplement it faithfully** — tokens (colour, type, spacing), components, per-page composition, imagery and\naccessibility — plus the logo files in `brand/`. Don't re-run design questions or invent a new direction here.\n\n- Treat `design.md` as the contract: the built pages should match its tokens, type scale, components and\n per-page composition, and the 8-point grid / WCAG 2.2 AA rules it carries.\n- Use the **logo** from `brand/` as-is (wordmark, icon mark, favicon); never recolour or redraw it.\n- If `design.md` is **missing** (e.g. a resumed older build), fall back to `brief.md → Design direction`\n (reference site, colours, brand, adjectives) and a tasteful, accessible default — but prefer `design.md`\n whenever it exists.\n- **Don't gate on approval here** — the result is shown at **validate** (Step 7).\n\n**Only ask about the code, and only if it matters** (design decisions belong in the design step):\n- Where the **source should live** — default `~/.hostinger/<domain>/src/`; ask only if they want a\n specific folder or an existing repo.\n- **Git** — initialise / connect / push (default: a local project, no remote).\n- A specific **component library** or technical must-have, if they implied one.\n\n## Step 3 — Build against the backend\n\nTwo phases around the instance barrier (see the top-level `SKILL.md`): scaffold + build the shell while\nprovision settles, then wire live data once `provision.status == \"completed\"`.\n\nRead the backend from `site.json`:\n\n- **WordPress** → `urls.rest_api` (`wp/v2`), public read. Content is in the posts/pages/categories/media\n the content-model push created; nav/plan come from `content-model.md`. Map each item's **SEO** from its\n `yoast_head_json` into the page `<head>` (title, description, Open Graph, JSON-LD schema) — Yoast is\n installed by default at provision (`site.json → seo.enabled: true`). If `seo.enabled` is `false`\n (Yoast absent), fall back to generating per-page title/description/OG tags from each item's content.\n - **Contact form** (`site.json → contact_form.enabled: true`, any WordPress backend) → render the\n contact page's form and submit it **client-side** to Contact Form 7's public feedback endpoint. Put a\n `submitContactForm(data)` helper in the one data module: build a `FormData` with the field names from\n `contact_form.fields` (e.g. `your-name`, `your-email`, `your-subject`, `your-message`) **plus CF7's\n hidden fields** — `_wpcf7` = `contact_form.form_id`, `_wpcf7_version`, `_wpcf7_locale` (e.g. `en_US`),\n `_wpcf7_unit_tag` (any stable string), `_wpcf7_container_post` = `0` — and `POST` it to\n `contact_form.feedback_url` **without setting `Content-Type`** (let the browser add the multipart\n boundary). `/cms` is same-origin, so no CORS and no secret. Branch on the JSON `status`: `mail_sent`\n → success state; `validation_failed` → map `invalid_fields[].field` to per-input errors;\n `spam`/`aborted`/`mail_failed` → a generic \"couldn't send, try again\" message. Include the honeypot\n decoy field named in `contact_form.honeypot_field` (e.g. `your_website`), rendered hidden/off-screen,\n **empty**, `autocomplete=\"off\"`, `tabindex=\"-1\"` — the deployed spam guard rejects it if filled. A user\n validation error is a **normal UI state, not a build error**. When `contact_form.enabled` is `false` or absent, render the contact page with\n plain contact details and **no form** — graceful, like disabled checkout; never half-build. Full\n contract: `../04-content-model/wordpress/contact-form.md` §3.\n- **WordPress + WooCommerce** (`site.json → woocommerce.enabled: true`) → the WordPress bullet above **plus**\n the store. Read the catalog over the **public WooCommerce Store API** (`site.json → urls.store_api`,\n `/cms/wp-json/wc/store/v1`, no auth, open CORS): `GET /products` (prices are **integer minor units** on\n `prices.price` with `prices.currency_minor_unit` — divide to display), `GET /products/categories`. Cart,\n coupons and checkout are **client-side** against the Store API with the `Nonce` + `Cart-Token` headers\n (`GET /cart`, `POST /cart/add-item`, `/cart/apply-coupon`, `POST /checkout`) — see\n `../04-content-model/wordpress/woocommerce.md`. **Never embed a consumer key client-side**; the\n storefront is fully public. Blog/pages on the same site still come from `urls.rest_api` (`wp/v2`) + Yoast.\n - **Honour `site.json → woocommerce.payment_enabled`.** It is `false` until the owner enables a payment\n gateway + shipping in wp-admin (that config isn't reachable headlessly — see the provision note). While\n `false`, build the full catalog, product pages and the client-side cart as normal, but render a\n graceful disabled-checkout state (e.g. a disabled \"Checkout\" with \"Checkout isn't set up yet\"), **don't\n call `/checkout`**, and don't treat it as a build error. When `true`, wire checkout (`payment_method`\n `\"cod\"` works out of the box).\n- **Ecommerce** → the **public Hostinger Storefront Core V2 API** (`https://api-ecommerce.hostinger.com/v2`,\n no auth, open CORS), keyed on the **custom sales channel** (`site.json → ecommerce.sales_channel.id`):\n `GET /channels/<scha_*>/products` (prices are **minor units** on variants), `POST /channels/<scha_*>/checkout`\n for buying. Cart is client-side; never embed a token. Read\n `ecommerce_miscellaneous_custom-storefront-setup-instructions` for the authoritative, current guide.\n - **Honour `site.json → ecommerce.payment_enabled`.** When it is `false` (free account / no paid\n Ecommerce plan — see the provision gate), there is **no checkout**: build the full catalog, product\n pages and the client-side cart as normal, but **do not call the checkout endpoint**. Instead render\n a clear, graceful state at the checkout step — e.g. a disabled \"Checkout\" button with a short note\n like \"Online checkout isn't available on this plan yet\" (and/or a contact/enquiry fallback). A\n missing checkout is **not** a build error here — never leave the storefront half-built, throw, or\n retry checkout because payment is off. When `payment_enabled` is `true`, wire checkout normally.\n- **Webapp** → **no `content-model.md` and no headless backend to read**. The app uses its own data source:\n the MySQL database from `site.json → database` (through the `DB_*` / `DATABASE_URL` env vars — set at\n provision/deploy, see `../03-provision/webapp/SKILL.md` Step 3; `127.0.0.1` only resolves on the server,\n not locally), or whatever external API the app calls. For an existing app, keep its data layer as-is.\n\nGuidelines (framework-agnostic):\n\n- **One data module** is the single place that talks to the backend (e.g. `src/lib/data.ts`) — one file\n to inspect, one base URL to switch. Keep it swappable so the shell can build against stubs first.\n- **Fetch at request time for SSR** (fresh content, no rebuild); a static build fetches at build time.\n- **A failed or incomplete fetch is an error, not a silent empty/placeholder.** Planned content missing\n from the API is a build error too — a site that builds with holes ships with holes. *(WordPress/Ecommerce;\n a webapp has no planned content to check.)*\n- **`content-model.md` is the source for what should exist** (pages, nav, collections mapping) — for\n WordPress/Ecommerce only; a **webapp has no `content-model.md`**.\n- **No `.htaccess`** in the project (deploy would overwrite the live one); don't commit `node_modules`\n or build output.\n- Set `engines.node` to `22` (the deploy builds on Node 22).\n\n## Step 4 — Hand off\n\nConfirm the project builds clean locally against the live backend, no placeholder/fixture content\nremains, and `state.json → frontend` matches what you built. Update `state.json` (`stage: \"validate\"`).\nValidate (Step 7) owns the clean rebuild + user preview; deploy (Step 8) ships it with the `app_type`\nand output directory that match the chosen framework + rendering.\n\n> **Webapp:** just confirm the app builds clean and `state.json → frontend` is right — there's no\n> `content-model.md` or headless backend to check. A DB-backed app can't reach its database locally\n> (`127.0.0.1` is server-only), so its data layer is verified **after** deploy, not here.\n"
|
|
238
|
+
"text": "---\nname: headless-step-frontend\ndescription: \"Step 6 (frontend) of the Hostinger Headless flow. Builds the website that renders the backend's content. Figures out (or confirms) the framework — defaulting to Next.js SSR — implements the look already chosen in design.md (the design step's contract), and builds the source into src/. Only ask for the clarifications you actually need.\"\n---\n\n# headless-step-frontend\n\nBuild the site's frontend against the backend from `site.json`, implementing the look the design step\nalready chose and wrote to **`design.md`** (plus the logo in `brand/`). The design is decided; this step\nis where it becomes real code. There's no per-framework recipe — use good judgement, follow `design.md`\nas the contract, and ask only for the clarifications you genuinely need. **Out:** source in the site\nworkspace `~/.hostinger/<domain>/src/`.\n\n> ## ⚠️ Build mode: \"Bring your own frontend\"? Read this first (any backend)\n> Read `brief.md` → **Build mode**. When it is **Bring your own frontend**, the user already has a\n> frontend (the project in the folder the command was run in) and this step **wires it to the backend\n> from `site.json` — it does not scaffold a new project and there is usually no `design.md` to\n> implement** (the design step deferred to the user). **Use their project as the source**: copy or\n> point `~/.hostinger/<domain>/src/` at it, keep its framework and UI, and collaborate on changes at\n> the user's direction (vibe-code with them). Your job is to connect it to the live backend (WordPress\n> `wp/v2` reads, the WooCommerce/Ecommerce Store API, env/keys) and make it deployable (set\n> `engines.node` to `22`, remove any `.htaccess`), then hand off to deploy. This generalises the Webapp\n> \"existing app\" case below to WordPress and Ecommerce too. When **Build mode is \"Agent designs and\n> builds\"**, ignore this box and proceed normally (scaffold + implement `design.md`).\n\n> ## ⚠️ Webapp backend? Read this first\n> A `webapp` brief has **no CMS/store and no `content-model.md`** — the app owns its own data (its own\n> API, or the MySQL database recorded in `site.json → database`). It comes in two shapes, decided back in\n> **refine**:\n>\n> - **An existing app the user brought to deploy** (refine routes \"deploy my app\" here). **Use their\n> project as the source** — copy/point `~/.hostinger/<domain>/src/` at it and keep its own framework and\n> UI. **Do not scaffold a new project, and do not re-implement `design.md` over it** (so **the design\n> step does not apply** to an app that brings its own UI). Your job is only to make it deployable: set\n> `engines.node` to `22`, remove any `.htaccess`, wire its data/env, then hand off to deploy. **Skip the\n> Step 2 design pass**, and ignore the `content-model.md` requirement in Step 3.\n> - **A new app built from the brief.** Scaffold and build it from `brief.md` (and `design.md` if the\n> design step produced one — design *does* run for a from-scratch app). Its data is owned by the app, so\n> there is still **no `content-model.md`** to bind against.\n>\n> Framework choice (Step 1), the build, and hand-off (Step 4) apply to both. The per-backend bullets in\n> Step 3 are **WordPress / WooCommerce / Ecommerce only** — for a webapp follow the Webapp bullet there.\n\n## Step 1 — Pick the framework (guess → default → confirm)\n\n**The rendering is already decided in `brief.md` → Rendering** (SSR or SSG, set during refine). Honour\nit, and the backend constraint:\n\n- **Ecommerce → SSR required** (carts/search need per-request rendering). Any SSR framework works —\n Next.js is the default, not a requirement (Express, Nuxt, SvelteKit, etc. are all fine).\n- **WordPress + WooCommerce → SSR required** (same reason — cart/checkout need per-request rendering).\n- **WordPress → SSG (Astro)** when the brief says SSG, otherwise **SSR (Next.js default)**.\n- **Webapp → the app's own stack.** An **existing app** keeps its current framework (don't impose one); a\n **new app** uses whatever fits the brief (SSR or SPA). Either way it deploys as a Node.js app (Step 8) —\n an SSR app runs a server, a SPA builds to static output.\n- Map rendering → framework: **`ssr` → Next.js**, **`ssg` → Astro** — unless the brief/prompt explicitly\n names another supported framework, in which case use that.\n\nSupported frameworks (what the Hostinger build pipeline can deploy — the `app_type` in Step 8 deploy):\n\n- **Server/SSR-capable:** Next.js, Nuxt.js, Astro, SvelteKit, Express, NestJS, Fastify.\n- **Static / SPA:** React, Vue, Angular, Vite, Parcel, Preact, Svelte, (Astro / SvelteKit static too).\n\nConfirm the framework with the user in one line only if the brief left it open. Record the choice in\n`state.json`:\n\n```json\n{ \"frontend\": { \"framework\": \"nextjs\", \"rendering\": \"ssr\" } }\n```\n\nIf the brief is silent, default to **Next.js SSR**. Ecommerce must be SSR (never SSG) — but the SSR\nframework itself is free to be any supported one, not just Next.js.\n\n## Step 2 — Implement `design.md` (don't re-design)\n\nThe look was already chosen and written by the design step. **Read `~/.hostinger/<domain>/design.md` and\nimplement it faithfully** — tokens (colour, type, spacing), components, per-page composition, imagery and\naccessibility — plus the logo files in `brand/`. Don't re-run design questions or invent a new direction here.\n\n- Treat `design.md` as the contract: the built pages should match its tokens, type scale, components and\n per-page composition, and the 8-point grid / WCAG 2.2 AA rules it carries.\n- Use the **logo** from `brand/` as-is (wordmark, icon mark, favicon); never recolour or redraw it.\n- If `design.md` is **missing** (e.g. a resumed older build), fall back to `brief.md → Design direction`\n (reference site, colours, brand, adjectives) and a tasteful, accessible default — but prefer `design.md`\n whenever it exists.\n- **Don't gate on approval here** — the result is shown at **validate** (Step 7).\n\n**Only ask about the code, and only if it matters** (design decisions belong in the design step):\n- Where the **source should live** — default `~/.hostinger/<domain>/src/`; ask only if they want a\n specific folder or an existing repo.\n- **Git** — initialise / connect / push (default: a local project, no remote).\n- A specific **component library** or technical must-have, if they implied one.\n\n## Step 3 — Build against the backend\n\nTwo phases around the instance barrier (see the top-level `SKILL.md`): scaffold + build the shell while\nprovision settles, then wire live data once `provision.status == \"completed\"`.\n\nRead the backend from `site.json`:\n\n- **WordPress** → `urls.rest_api` (`wp/v2`), public read. Content is in the posts/pages/categories/media\n the content-model push created; nav/plan come from `content-model.md`. Map each item's **SEO** from its\n `yoast_head_json` into the page `<head>` (title, description, Open Graph, JSON-LD schema) — Yoast is\n installed by default at provision (`site.json → seo.enabled: true`). If `seo.enabled` is `false`\n (Yoast absent), fall back to generating per-page title/description/OG tags from each item's content.\n - **Contact form** (`site.json → contact_form.enabled: true`, any WordPress backend) → render the\n contact page's form and submit it **client-side** to Contact Form 7's public feedback endpoint. Put a\n `submitContactForm(data)` helper in the one data module: build a `FormData` with the field names from\n `contact_form.fields` (e.g. `your-name`, `your-email`, `your-subject`, `your-message`) **plus CF7's\n hidden fields** — `_wpcf7` = `contact_form.form_id`, `_wpcf7_version`, `_wpcf7_locale` (e.g. `en_US`),\n `_wpcf7_unit_tag` (any stable string), `_wpcf7_container_post` = `0` — and `POST` it to\n `contact_form.feedback_url` **without setting `Content-Type`** (let the browser add the multipart\n boundary). `/cms` is same-origin, so no CORS and no secret. Branch on the JSON `status`: `mail_sent`\n → success state; `validation_failed` → map `invalid_fields[].field` to per-input errors;\n `spam`/`aborted`/`mail_failed` → a generic \"couldn't send, try again\" message. Include the honeypot\n decoy field named in `contact_form.honeypot_field` (e.g. `your_website`), rendered hidden/off-screen,\n **empty**, `autocomplete=\"off\"`, `tabindex=\"-1\"` — the deployed spam guard rejects it if filled. A user\n validation error is a **normal UI state, not a build error**. When `contact_form.enabled` is `false` or absent, render the contact page with\n plain contact details and **no form** — graceful, like disabled checkout; never half-build. Full\n contract: `../04-content-model/wordpress/contact-form.md` §3.\n- **WordPress + WooCommerce** (`site.json → woocommerce.enabled: true`) → the WordPress bullet above **plus**\n the store. Read the catalog over the **public WooCommerce Store API** (`site.json → urls.store_api`,\n `/cms/wp-json/wc/store/v1`, no auth, open CORS): `GET /products` (prices are **integer minor units** on\n `prices.price` with `prices.currency_minor_unit` — divide to display), `GET /products/categories`. Cart,\n coupons and checkout are **client-side** against the Store API with the `Nonce` + `Cart-Token` headers\n (`GET /cart`, `POST /cart/add-item`, `/cart/apply-coupon`, `POST /checkout`) — see\n `../04-content-model/wordpress/woocommerce.md`. **Never embed a consumer key client-side**; the\n storefront is fully public. Blog/pages on the same site still come from `urls.rest_api` (`wp/v2`) + Yoast.\n - **Honour `site.json → woocommerce.payment_enabled`.** It is `false` until the owner enables a payment\n gateway + shipping in wp-admin (that config isn't reachable headlessly — see the provision note). While\n `false`, build the full catalog, product pages and the client-side cart as normal, but render a\n graceful disabled-checkout state (e.g. a disabled \"Checkout\" with \"Checkout isn't set up yet\"), **don't\n call `/checkout`**, and don't treat it as a build error. When `true`, wire checkout (`payment_method`\n `\"cod\"` works out of the box).\n- **Ecommerce** → the **public Hostinger Storefront Core V2 API** (`https://api-ecommerce.hostinger.com/v2`,\n no auth, open CORS), keyed on the **custom sales channel** (`site.json → ecommerce.sales_channel.id`):\n `GET /channels/<scha_*>/products` (prices are **minor units** on variants), `POST /channels/<scha_*>/checkout`\n for buying. Cart is client-side; never embed a token. Read\n `ecommerce_miscellaneous_custom-storefront-setup-instructions` for the authoritative, current guide.\n - **Honour `site.json → ecommerce.payment_enabled`.** When it is `false` (free account / no paid\n Ecommerce plan — see the provision gate), there is **no checkout**: build the full catalog, product\n pages and the client-side cart as normal, but **do not call the checkout endpoint**. Instead render\n a clear, graceful state at the checkout step — e.g. a disabled \"Checkout\" button with a short note\n like \"Online checkout isn't available on this plan yet\" (and/or a contact/enquiry fallback). A\n missing checkout is **not** a build error here — never leave the storefront half-built, throw, or\n retry checkout because payment is off. When `payment_enabled` is `true`, wire checkout normally.\n- **Webapp** → **no `content-model.md` and no headless backend to read**. The app uses its own data source:\n the MySQL database from `site.json → database` (through the `DB_*` / `DATABASE_URL` env vars — set at\n provision/deploy, see `../03-provision/webapp/SKILL.md` Step 3; `127.0.0.1` only resolves on the server,\n not locally), or whatever external API the app calls. For an existing app, keep its data layer as-is.\n\nGuidelines (framework-agnostic):\n\n- **One data module** is the single place that talks to the backend (e.g. `src/lib/data.ts`) — one file\n to inspect, one base URL to switch. Keep it swappable so the shell can build against stubs first.\n- **Fetch at request time for SSR** (fresh content, no rebuild); a static build fetches at build time.\n- **A failed or incomplete fetch is an error, not a silent empty/placeholder.** Planned content missing\n from the API is a build error too — a site that builds with holes ships with holes. *(WordPress/Ecommerce;\n a webapp has no planned content to check.)*\n- **`content-model.md` is the source for what should exist** (pages, nav, collections mapping) — for\n WordPress/Ecommerce only; a **webapp has no `content-model.md`**.\n- **No `.htaccess`** in the project (deploy would overwrite the live one); don't commit `node_modules`\n or build output.\n- Set `engines.node` to `22` (the deploy builds on Node 22).\n- **Next.js 16+ only:** set the `build` script to `next build --webpack`, not plain `next build`. Next.js\n 16 defaults `next build` to **Turbopack**, whose native compiler needs a newer glibc than the Hostinger\n build server has, so a Turbopack build fails on deploy and won't fall back to webpack. Webpack builds\n clean there. The `--webpack` flag exists only on Next.js 16+; on **Next.js 15.x** leave the build script\n as plain `next build` (it already defaults to webpack). (Alternative: pin Next.js to `15.x`, keeping\n plain `next build`.)\n\n## Step 4 — Hand off\n\nConfirm the project builds clean locally against the live backend, no placeholder/fixture content\nremains, and `state.json → frontend` matches what you built. Update `state.json` (`stage: \"validate\"`).\nValidate (Step 7) owns the clean rebuild + user preview; deploy (Step 8) ships it with the `app_type`\nand output directory that match the chosen framework + rendering.\n\n> **Webapp:** just confirm the app builds clean and `state.json → frontend` is right — there's no\n> `content-model.md` or headless backend to check. A DB-backed app can't reach its database locally\n> (`127.0.0.1` is server-only), so its data layer is verified **after** deploy, not here.\n"
|
|
239
239
|
},
|
|
240
240
|
{
|
|
241
241
|
"uri": "skill://hostinger-headless/steps/07-validate/SKILL.md",
|
|
@@ -246,17 +246,17 @@ export default [
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
"uri": "skill://hostinger-headless/steps/08-deploy/SKILL.md",
|
|
249
|
-
"digest": "sha256:
|
|
250
|
-
"size":
|
|
249
|
+
"digest": "sha256:c6f2ef2d62ae4ba0ab7686148e1898585c8c93bcb22e91c2bcf25b80f7bd987d",
|
|
250
|
+
"size": 12010,
|
|
251
251
|
"mimeType": "text/markdown",
|
|
252
|
-
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
252
|
+
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)\n> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)\n> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts\n> with a \"glibc too old\" error — and Turbopack **does not fall back** to webpack on its own, so the\n> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script\n> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy\n> call stays `\"build\"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on\n> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).\n> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see\n> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`\n> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other\n> frameworks are unaffected.\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n - **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —\n the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it\n as plain `next build`. Fix it here if it isn't.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
"uri": "skill://hostinger-headless/steps/09-done/SKILL.md",
|
|
256
|
-
"digest": "sha256:
|
|
257
|
-
"size":
|
|
256
|
+
"digest": "sha256:62da301dbc366d1c2c7e051e2b124e7eb59b19e6c2517e7cb2eb3420b3f8bbd5",
|
|
257
|
+
"size": 10482,
|
|
258
258
|
"mimeType": "text/markdown",
|
|
259
|
-
"text": "---\nname: headless-step-done\ndescription: \"Step 9 (done) of the Hostinger Headless flow. Verifies the live site, then hands over the URLs (hPanel success page, main site, WordPress/store admin, dashboard), opens the success page in the browser on a successful run, and gives a summary of what was built and what the user can do next. Its final report format is also used whenever a run stops early.\"\n---\n\n# headless-step-done\n\nVerify the live site, then hand over the URLs (success page, main site, WordPress admin, dashboard), a\nsummary of what was built, and what the user can do next. **On a successful run, open the success page\nfor the user** (see *Open the success page*).\n\n## Verify the live site (brief)\n\nOn a run that deployed this session, re-confirm what you're about to hand over actually works (deploy\nalready checked this — this is a quick re-confirm, not a full re-run):\n\n- **Main site:** `GET https://<domain>/` → 200 (Next.js SSR: `x-powered-by: Next.js`; Astro SSG: static\n HTML with real content).\n- **WordPress admin:** `GET {urls.wp_admin}` → reachable (a login page, 200/302), not 404.\n- **Backend API:** `GET {urls.rest_api}/posts?per_page=1` → 200 (WordPress); for a **WooCommerce** store\n (`site.json → woocommerce.enabled`) also `GET {urls.store_api}/products?per_page=1` → 200; or the\n storefront API for the sales channel → 200 (Ecommerce).\n- **Contact form** (`site.json → contact_form.enabled`): validate already sent a test submission, so\n don't send another here. In the handover, tell the owner the contact page is live and enquiries email\n the **WordPress admin address** (via `wp_mail`) — and that for the most reliable delivery that address\n should be a Hostinger mailbox on the domain. If validate reported `mail_failed`, say so plainly instead\n of claiming the form sends.\n\nIf a check fails, say so in the outcome paragraph and don't claim the site is live.\n\n## Final report\n\nEvery run ends with this report, whether it finishes here or stops at an earlier stage (a dummy or\nmissing step, a failed provision, the user pausing). Build it from `state.json`, `site.json`,\n`deploy.json` and what you actually did and checked in this run. Report only what you verified. Don't\nclaim a stage passed if you didn't check it.\n\nWrite it in this order:\n\n1. **One-paragraph outcome.** What was created and on which domain (bold it). If the run stopped\n early, name the stage it stopped at, why, and what the user will see right now (e.g. \"the site\n root returns 403 until the frontend is deployed\").\n\n2. **Steps completed** — a table with one row per stage that ran:\n\n | Stage | Result |\n |---|---|\n | connect | How the connector was reached, MCP version, how sign-in happened. |\n | 1 check state | New build / resumed at `<stage>` / adopted an existing site. |\n | 2 refine | What the brief is and which backend it chose (say if a sample brief was used). |\n | 3 provision | Plan and order used, how long setup took, which verification checks passed. |\n | 4 content model | What was created (counts of pages, posts, terms, menus, media, SEO) and how it was verified. |\n | … | One row for each further stage that ran (design, frontend, validate, deploy, done). |\n\n Stages that didn't run are left out of the table and named in the outcome paragraph instead.\n Use the stage numbers and names from the orchestrator's flow table.\n\n3. **Links** — every URL that exists at this point. Build the hPanel URLs from `site.json`\n (`order_id`, `username`, `domain`) and `state.json` (`tool`); take the rest from `site.json` /\n `deploy.json`. Lead with the one the user cares about most — the **main site** — then the admin\n and dashboards. **Don't list the success page here:** it opens on its own (see *Open the success\n page*), so printing its URL in the report is just noise — the only exception is when a browser\n can't be opened (handled in that section):\n\n - **Main site (live):** `urls.site` (`https://<domain>`) — only once deployed.\n - **WordPress admin (one-click auto-login):** for a WordPress backend, **mint and hand over the\n auto-login redirect URL** — `wordpress_login_create-links { username, software: <site.json\n wordpress.install_id> }` returns a temporary link that logs the user straight into wp-admin. Give\n that URL as the admin link (it lands on `/cms/wp-admin`), so the user never needs a password. Also\n note the plain `urls.wp_admin` as a fallback. **Never print or store a password.** For a\n **WordPress + WooCommerce** store, this *same* wp-admin login is the store admin (wp-admin →\n WooCommerce → Products/Orders) — do **not** use the Ecommerce `store_admin` path. **Call out the one\n setup step under *Gaps / follow-ups*:** when `site.json → woocommerce.payment_enabled` is `false`,\n the owner must enable a payment method + a shipping zone in WooCommerce → Settings before checkout\n works (the catalog and cart already work). For a Hostinger **Ecommerce** store, link the store admin\n from `urls.store_admin` instead.\n - **hPanel site dashboard:** `https://hpanel.hostinger.com/websites/<domain>` (the headless website\n is installed by now).\n - **Onboarding (resume):** `https://hpanel.hostinger.com/headless-onboarding/<order_id>/<username>/`\n - **REST API** (for developers): `urls.rest_api`.\n\n > URL shape note: `<username>` may be dropped from the onboarding/success URLs in a future hPanel\n > release. If a built URL 404s, retry the same path without the `/<username>` segment before\n > reporting it as broken.\n\n4. **Where state lives** — the workspace path `~/.hostinger/<domain>/`, the `stage` that a\n resumed run will start from, and the build report `~/.hostinger/<domain>/report.html`.\n\n5. **Gaps / follow-ups** (only if any) — a short numbered list of anything skipped, approximated or\n left for the user (e.g. no images generated, default WordPress content left in place).\n\nNever include passwords, JWTs or other secrets in the report. For wp-admin access, give the URL and\noffer a one-click login link (`wordpress_login_create-links`), not credentials.\n\n## Open the success page\n\nOn a **successful** run (the deploy succeeded and the live checks passed), open the success page in the\nuser's **main / system browser** right after presenting the report, so they land on the hPanel\ncelebration/next-steps screen for their new site. This is a side effect only — **don't print the\nsuccess URL in the report output** (it just opens); the one exception is the can't-open-a-browser\nfallback below. Build the URL with `<tool>` as the running tool's onboarding slug (`claude` for\nClaude Code, else `cursor`, `codex`, `vscode`, `antigravity`, `devin`), mapped from\n`state.json → tool` (e.g. `claude-code` → `claude`):\n\n```bash\n# Build the URL first, then open it with the OS default (main) browser — not an embedded one.\n# <tool> = the onboarding slug (claude for Claude Code, else cursor/codex/vscode/antigravity/devin)\nURL=\"https://hpanel.hostinger.com/headless-onboarding/<order_id>/<username>/success/<domain>/<tool>\"\nopen \"$URL\" # macOS\n# xdg-open \"$URL\" # Linux\n# start \"\" \"$URL\" # Windows\n```\n\n- **Main browser only.** Use the OS opener above (`open` / `xdg-open` / `start`) so the page lands in\n Chrome, Safari, Edge, or whatever the user actually uses. **Do not** open it in an embedded or\n sandboxed browser (Cursor IDE browser / `cursor-ide-browser`, Playwright, Puppeteer, WebView, or\n any in-agent browser tab). Those stay inside the tool session and the user won't see the\n celebration page where they expect it.\n- Open it **once**, and only on a successful run — never on an early stop or a failed deploy.\n- If your tool can't open a browser (the command isn't available, or `open`/`xdg-open`/`start`\n fails), don't treat it as a failure and **don't fall back to an embedded browser**. This is the\n **one** case where you print the success URL — show it once in the report and tell the user to\n open it themselves in their main browser.\n\n## Build report\n\nRight after the final report, save the build report for this run. It turns the run log (stage times\nfrom `state.json`, events from `events.jsonl`) into `reports/<run>.json` and `.html`, and copies the\nHTML to `report.html` in the workspace:\n\n```bash\nnode <skill>/run-log.mjs <domain> event good '{\"text\":\"<something that went well>\"}' # one per thing, if not logged yet\nnode <skill>/run-log.mjs <domain> stage-end done ok\nnode <skill>/run-log.mjs <domain> report --outcome <live|stopped|failed> [--tokens '<json>']\n```\n\nThen send it to Hostinger: pass `reports/<run>.json` unchanged to `hosting_headless_submit-build-report`\nand run `report-sent <run>` (or `report-rejected <run>` on a 422). The `report` command prints the\nexact path and run id. Don't ask first; see *Run log and build report* in the top-level `SKILL.md`\nfor failures. Then tell the user in one line: \"Sent the build report to Hostinger to help improve\nthe flow.\" On a 422 say it's saved locally only; on any other failure say it'll be sent next session.\n\nIf the user asks for more changes after this report in the same session, call\n`run-log.mjs <domain> run-resume --reason \"<what they asked for>\"` before the work. Then log as\nusual and run `report` again when it's done. It updates this run's report in place, and the report\nlists the follow-up. Send the updated report the same way.\n\n- **Outcome:** `live` when the deploy succeeded this run, `failed` when a stage failed and you\n couldn't recover, `stopped` for everything else (the user paused, a dummy or missing step). Leave\n `--outcome` out and the script decides from the log.\n- **Before you write it,** check that every issue, correction and skill gap from this run is in\n `events.jsonl`. Log any that are missing now; the report only shows what was logged.\n- **After you write it,** read the `warning:` lines `report` prints. They list logging gaps: a stage\n that never ended, a start that was estimated, a stage logged with no duration, or (on a live build)\n a stage that was never logged. Fix what you can: `stage-end` an open stage, or correct a start with\n `stage-fix <stage> --started-ago <time>`, then run `report` again. What you can't fix stays in the report's\n *Logging gaps* section.\n- **Tokens:** only what your tool tells you about your own usage (see *Run log and build report* in\n the top-level `SKILL.md`).\n- In the final report's *Where state lives*, add the path to `report.html` so the user can open it.\n\nDo this on every run, including the early stops: those reports are the most useful ones.\n"
|
|
259
|
+
"text": "---\nname: headless-step-done\ndescription: \"Step 9 (done) of the Hostinger Headless flow. Verifies the live site, then hands over the URLs (hPanel success page, main site, WordPress/store admin, dashboard), opens the success page in the browser on a successful run, and gives a summary of what was built and what the user can do next. Its final report format is also used whenever a run stops early.\"\n---\n\n# headless-step-done\n\nVerify the live site, then hand over the URLs (success page, main site, WordPress admin, dashboard), a\nsummary of what was built, and what the user can do next. **On a successful run, open the success page\nfor the user** (see *Open the success page*).\n\n## Verify the live site (brief)\n\nOn a run that deployed this session, re-confirm what you're about to hand over actually works (deploy\nalready checked this — this is a quick re-confirm, not a full re-run):\n\n- **Main site:** `GET https://<domain>/` → 200 (Next.js SSR: `x-powered-by: Next.js`; Astro SSG: static\n HTML with real content).\n- **WordPress admin:** `GET {urls.wp_admin}` → reachable (a login page, 200/302), not 404.\n- **Backend API:** `GET {urls.rest_api}/posts?per_page=1` → 200 (WordPress); for a **WooCommerce** store\n (`site.json → woocommerce.enabled`) also `GET {urls.store_api}/products?per_page=1` → 200; or the\n storefront API for the sales channel → 200 (Ecommerce).\n- **Contact form** (`site.json → contact_form.enabled`): validate already sent a test submission, so\n don't send another here. In the handover, tell the owner the contact page is live and enquiries email\n the **WordPress admin address** (via `wp_mail`) — and that for the most reliable delivery that address\n should be a Hostinger mailbox on the domain. If validate reported `mail_failed`, say so plainly instead\n of claiming the form sends.\n\nIf a check fails, say so in the outcome paragraph and don't claim the site is live.\n\n## Final report\n\nEvery run ends with this report, whether it finishes here or stops at an earlier stage (a dummy or\nmissing step, a failed provision, the user pausing). Build it from `state.json`, `site.json`,\n`deploy.json` and what you actually did and checked in this run. Report only what you verified. Don't\nclaim a stage passed if you didn't check it.\n\nWrite it in this order:\n\n1. **One-paragraph outcome.** What was created and on which domain (bold it). If the run stopped\n early, name the stage it stopped at, why, and what the user will see right now (e.g. \"the site\n root returns 403 until the frontend is deployed\").\n\n2. **Steps completed** — a table with one row per stage that ran:\n\n | Stage | Result |\n |---|---|\n | connect | How the connector was reached, MCP version, how sign-in happened. |\n | 1 check state | New build / resumed at `<stage>` / adopted an existing site. |\n | 2 refine | What the brief is and which backend it chose (say if a sample brief was used). |\n | 3 provision | Plan and order used, how long setup took, which verification checks passed. |\n | 4 content model | What was created (counts of pages, posts, terms, menus, media, SEO) and how it was verified. |\n | … | One row for each further stage that ran (design, frontend, validate, deploy, done). |\n\n Stages that didn't run are left out of the table and named in the outcome paragraph instead.\n Use the stage numbers and names from the orchestrator's flow table.\n\n3. **Links** — every URL that exists at this point. Build the hPanel URLs from `site.json`\n (`order_id`, `username`, `domain`) and the onboarding slug; take the rest from `site.json` /\n `deploy.json`. Lead with the one the user cares about most — the **main site** — then the admin\n and dashboards. **Don't list the success page here:** it opens on its own (see *Open the success\n page*), so printing its URL in the report is just noise — the only exception is when a browser\n can't be opened (handled in that section):\n\n - **Main site (live):** `urls.site` (`https://<domain>`) — only once deployed.\n - **WordPress admin (one-click auto-login):** for a WordPress backend, **mint and hand over the\n auto-login redirect URL** — `wordpress_login_create-links { username, software: <site.json\n wordpress.install_id> }` returns a temporary link that logs the user straight into wp-admin. Give\n that URL as the admin link (it lands on `/cms/wp-admin`), so the user never needs a password. Also\n note the plain `urls.wp_admin` as a fallback. **Never print or store a password.** For a\n **WordPress + WooCommerce** store, this *same* wp-admin login is the store admin (wp-admin →\n WooCommerce → Products/Orders) — do **not** use the Ecommerce `store_admin` path. **Call out the one\n setup step under *Gaps / follow-ups*:** when `site.json → woocommerce.payment_enabled` is `false`,\n the owner must enable a payment method + a shipping zone in WooCommerce → Settings before checkout\n works (the catalog and cart already work). For a Hostinger **Ecommerce** store, link the store admin\n from `urls.store_admin` instead.\n - **hPanel site dashboard:** `https://hpanel.hostinger.com/websites/<domain>` (the headless website\n is installed by now).\n - **Onboarding (resume):** `https://hpanel.hostinger.com/headless-onboarding/<order_id>/<username>/`\n - **REST API** (for developers): `urls.rest_api`.\n\n > URL shape note: `<username>` may be dropped from the onboarding/success URLs in a future hPanel\n > release. If a built URL 404s, retry the same path without the `/<username>` segment before\n > reporting it as broken.\n\n4. **Where state lives** — the workspace path `~/.hostinger/<domain>/`, the `stage` that a\n resumed run will start from, and the build report `~/.hostinger/<domain>/report.html`.\n\n5. **Gaps / follow-ups** (only if any) — a short numbered list of anything skipped, approximated or\n left for the user (e.g. no images generated, default WordPress content left in place).\n\nNever include passwords, JWTs or other secrets in the report. For wp-admin access, give the URL and\noffer a one-click login link (`wordpress_login_create-links`), not credentials.\n\n## Open the success page\n\nOn a **successful** run (the deploy succeeded and the live checks passed), open the success page in the\nuser's **main / system browser** right after presenting the report, so they land on the hPanel\ncelebration/next-steps screen for their new site. This is a side effect only — **don't print the\nsuccess URL in the report output** (it just opens); the one exception is the can't-open-a-browser\nfallback below. Build the URL with `<tool>` as the running tool's onboarding slug (see *State and\nartifacts* in the top-level `SKILL.md`):\n\n```bash\n# Build the URL first, then open it with the OS default (main) browser — not an embedded one.\n# <tool> = the onboarding slug\nURL=\"https://hpanel.hostinger.com/headless-onboarding/<order_id>/<username>/success/<domain>/<tool>\"\nopen \"$URL\" # macOS\n# xdg-open \"$URL\" # Linux\n# start \"\" \"$URL\" # Windows\n```\n\n- **Main browser only.** Use the OS opener above (`open` / `xdg-open` / `start`) so the page lands in\n Chrome, Safari, Edge, or whatever the user actually uses. **Do not** open it in an embedded or\n sandboxed browser (Cursor IDE browser / `cursor-ide-browser`, Playwright, Puppeteer, WebView, or\n any in-agent browser tab). Those stay inside the tool session and the user won't see the\n celebration page where they expect it.\n- Open it **once**, and only on a successful run — never on an early stop or a failed deploy.\n- If your tool can't open a browser (the command isn't available, or `open`/`xdg-open`/`start`\n fails), don't treat it as a failure and **don't fall back to an embedded browser**. This is the\n **one** case where you print the success URL — show it once in the report and tell the user to\n open it themselves in their main browser.\n\n## Build report\n\nRight after the final report, save the build report for this run. It turns the run log (stage times\nfrom `state.json`, events from `events.jsonl`) into `reports/<run>.json` and `.html`, and copies the\nHTML to `report.html` in the workspace:\n\n```bash\nnode <skill>/run-log.mjs <domain> event good '{\"text\":\"<something that went well>\"}' # one per thing, if not logged yet\nnode <skill>/run-log.mjs <domain> stage-end done ok\nnode <skill>/run-log.mjs <domain> report --outcome <live|stopped|failed> [--tokens '<json>']\n```\n\nThen send it to Hostinger: pass `reports/<run>.json` unchanged to `hosting_headless_submit-build-report`\nand run `report-sent <run>` (or `report-rejected <run>` on a 422). The `report` command prints the\nexact path and run id. Don't ask first; see *Run log and build report* in the top-level `SKILL.md`\nfor failures. Then tell the user in one line: \"Sent the build report to Hostinger to help improve\nthe flow.\" On a 422 say it's saved locally only; on any other failure say it'll be sent next session.\n\nIf the user asks for more changes after this report in the same session, call\n`run-log.mjs <domain> run-resume --reason \"<what they asked for>\"` before the work. Then log as\nusual and run `report` again when it's done. It updates this run's report in place, and the report\nlists the follow-up. Send the updated report the same way.\n\n- **Outcome:** `live` when the deploy succeeded this run, `failed` when a stage failed and you\n couldn't recover, `stopped` for everything else (the user paused, a dummy or missing step). Leave\n `--outcome` out and the script decides from the log.\n- **Before you write it,** check that every issue, correction and skill gap from this run is in\n `events.jsonl`. Log any that are missing now; the report only shows what was logged.\n- **After you write it,** read the `warning:` lines `report` prints. They list logging gaps: a stage\n that never ended, a start that was estimated, a stage logged with no duration, or (on a live build)\n a stage that was never logged. Fix what you can: `stage-end` an open stage, or correct a start with\n `stage-fix <stage> --started-ago <time>`, then run `report` again. What you can't fix stays in the report's\n *Logging gaps* section.\n- **Tokens:** only what your tool tells you about your own usage (see *Run log and build report* in\n the top-level `SKILL.md`).\n- In the final report's *Where state lives*, add the path to `report.html` so the user can open it.\n\nDo this on every run, including the early stops: those reports are the most useful ones.\n"
|
|
260
260
|
}
|
|
261
261
|
]
|
|
262
262
|
},
|