hostinger-api-mcp 3.13.0 → 3.14.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +25 -3
  3. package/gemini-extension.json +1 -1
  4. package/package.json +1 -1
  5. package/server.json +2 -2
  6. package/skills/headless/steps/02-refine/SKILL.md +60 -32
  7. package/skills/headless/steps/03-provision/webapp/SKILL.md +2 -0
  8. package/src/core/runtime.js +1 -1
  9. package/src/core/runtime.ts +1 -1
  10. package/src/core/skills.js +6 -6
  11. package/src/core/skills.ts +6 -6
  12. package/src/core/tools/all.js +37 -2
  13. package/src/core/tools/all.ts +37 -2
  14. package/src/core/tools/hosting.js +37 -2
  15. package/src/core/tools/hosting.ts +37 -2
  16. package/src/servers/agency-hosting.js +1 -1
  17. package/src/servers/agency-hosting.ts +1 -1
  18. package/src/servers/agent-readiness.js +1 -1
  19. package/src/servers/agent-readiness.ts +1 -1
  20. package/src/servers/all.js +1 -1
  21. package/src/servers/all.ts +1 -1
  22. package/src/servers/billing.js +1 -1
  23. package/src/servers/billing.ts +1 -1
  24. package/src/servers/dns.js +1 -1
  25. package/src/servers/dns.ts +1 -1
  26. package/src/servers/domains.js +1 -1
  27. package/src/servers/domains.ts +1 -1
  28. package/src/servers/ecommerce.js +1 -1
  29. package/src/servers/ecommerce.ts +1 -1
  30. package/src/servers/horizons.js +1 -1
  31. package/src/servers/horizons.ts +1 -1
  32. package/src/servers/hosting.js +1 -1
  33. package/src/servers/hosting.ts +1 -1
  34. package/src/servers/mail.js +1 -1
  35. package/src/servers/mail.ts +1 -1
  36. package/src/servers/reach.js +1 -1
  37. package/src/servers/reach.ts +1 -1
  38. package/src/servers/vps.js +1 -1
  39. package/src/servers/vps.ts +1 -1
  40. package/src/servers/wordpress.js +1 -1
  41. package/src/servers/wordpress.ts +1 -1
  42. package/types.d.ts +33 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ 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.14.1 — 2026-10-27
8
+
9
+ ### Operations
10
+
11
+ - Added `hosting_cache_status` — Get cache status
12
+ - Changed the description of `hosting_cache_toggle-cacheless`
13
+ - Changed the description of `hosting_cache_toggle-website`
14
+
15
+ ### Also in this release
16
+
17
+ - Documentation: `README.md`
18
+ - Request handling and authentication: `src/core/runtime.js`, `src/core/runtime.ts`
19
+ - Build and packaging: `types.d.ts`
20
+
21
+ ## v3.14.0 — 2026-10-27
22
+
23
+ ### Also in this release
24
+
25
+ - Bundled agent skills: `skills/headless/steps/02-refine/SKILL.md`, `skills/headless/steps/03-provision/webapp/SKILL.md`
26
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
27
+
7
28
  ## v3.13.0 — 2026-10-27
8
29
 
9
30
  ### Also in this release
package/README.md CHANGED
@@ -69,7 +69,7 @@ pnpm update -g @hostinger/mcp
69
69
 
70
70
  This package installs the following MCP server commands:
71
71
 
72
- - `hostinger-api-mcp` — unified server over every operation (416 total)
72
+ - `hostinger-api-mcp` — unified server over every operation (417 total)
73
73
  - `hostinger-agency-hosting-mcp` — 42 operations for agency-hosting
74
74
  - `hostinger-agent-readiness-mcp` — 3 operations for agent-readiness
75
75
  - `hostinger-billing-mcp` — 9 operations for billing
@@ -77,7 +77,7 @@ This package installs the following MCP server commands:
77
77
  - `hostinger-domains-mcp` — 42 operations for domains
78
78
  - `hostinger-ecommerce-mcp` — 29 operations for ecommerce
79
79
  - `hostinger-horizons-mcp` — 6 operations for horizons
80
- - `hostinger-hosting-mcp` — 66 operations for hosting
80
+ - `hostinger-hosting-mcp` — 67 operations for hosting
81
81
  - `hostinger-mail-mcp` — 38 operations for mail
82
82
  - `hostinger-reach-mcp` — 69 operations for reach
83
83
  - `hostinger-vps-mcp` — 67 operations for vps
@@ -1840,6 +1840,24 @@ installation living in a subdirectory, pass the directory query parameter to cle
1840
1840
  - **Method**: `DELETE`
1841
1841
  - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/cache/clear`
1842
1842
 
1843
+ #### hosting_cache_status
1844
+
1845
+ Returns the caching state of the website: whether server-side caching is enabled and whether
1846
+ development (cacheless) mode is on. Works for the main domain, addon websites, parked domains
1847
+ and subdomains of the account; each reports its own settings and does not inherit them from
1848
+ its parent website.
1849
+
1850
+ `is_enabled` is the setting changed by `Toggle website cache` and reflects a toggle
1851
+ immediately. `is_cacheless_mode_enabled` is true while development mode is on; caching is then
1852
+ bypassed even when `is_enabled` is true. `Toggle cacheless mode` is applied in the background,
1853
+ so this flag can take a few minutes to follow it.
1854
+
1855
+ The domain must be the exact website name as listed on the account, without a `www.` prefix.
1856
+ Returns 404 when the domain is not a website on the account.
1857
+
1858
+ - **Method**: `GET`
1859
+ - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/cache/status`
1860
+
1843
1861
  #### hosting_cache_toggle-cacheless
1844
1862
 
1845
1863
  Turns development (cacheless) mode on or off, based on the enabled flag. When enabled, nothing
@@ -1847,6 +1865,9 @@ is cached, effectively turning off all caching for the website; use it while act
1847
1865
  testing changes, debugging issues, or when real-time updates must be visible. Disable it after
1848
1866
  finishing development work to restore the performance benefits of caching.
1849
1867
 
1868
+ The change is applied in the background and can take a few minutes. Use `Get cache status` to
1869
+ check the current state.
1870
+
1850
1871
  - **Method**: `PATCH`
1851
1872
  - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/cacheless-mode/toggle`
1852
1873
 
@@ -1857,7 +1878,8 @@ faster page loads, reduced server load, and improved user experience; recommende
1857
1878
  websites. Disabling may impact performance; to temporarily bypass caching while developing or
1858
1879
  debugging, prefer toggling cacheless mode instead.
1859
1880
 
1860
- Does nothing if caching is already in the requested state.
1881
+ Does nothing if caching is already in the requested state. Use `Get cache status` to check the
1882
+ current state.
1861
1883
 
1862
1884
  - **Method**: `PATCH`
1863
1885
  - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/cache/toggle`
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.13.0",
3
+ "version": "3.14.1",
4
4
  "description": "Hostinger API — manage domains, DNS, hosting, VPS, billing and more from Gemini CLI.",
5
5
  "mcpServers": {
6
6
  "hostinger": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.13.0",
3
+ "version": "3.14.1",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
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.13.0",
9
+ "version": "3.14.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@hostinger/mcp",
14
- "version": "3.13.0",
14
+ "version": "3.14.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -9,8 +9,9 @@ Find out what the user wants to build, guess which backend fits, and write it do
9
9
  **conversation, not a form**: the pasted prompt names a domain and nothing else, so everything about
10
10
  the site comes from talking to the user.
11
11
 
12
- **Keep it short.** The only thing you must know is *what they are building* — enough to guess the
13
- backend. Every other question is optional: the more the user gives, the better the site, but never
12
+ **Keep it short.** You must know what they are building, whether they sell and whether they'll
13
+ publish content over time — enough to guess the backend — plus, when Step 2 found a project in the folder, the
14
+ **Existing project** confirms. Every other question is optional: the more the user gives, the better the site, but never
14
15
  hold the build hostage to refinement. Fill gaps with sensible defaults and say so in the brief. A
15
16
  skipped question tool is not the user passing on a question; see *When you ask the user* in the
16
17
  top-level `SKILL.md`.
@@ -52,8 +53,8 @@ Before asking anything, collect what the user has already given you, so you don'
52
53
  framework config (`next.config.*`, `astro.config.*`, `vite.config.*`, `svelte.config.*`, `nuxt.*`),
53
54
  a `src/` or `app/` tree, `index.html`, a storefront/cart, etc. A real project here usually means the
54
55
  user **already has their own frontend** and mainly needs a Hostinger backend to wire it to — lean
55
- toward "bring your own frontend" in Step 3. If the folder has nothing site-like, assume they don't
56
- and lean toward "agent builds it".
56
+ toward "bring your own frontend" in Step 3. That is what *Step 2 found a project in the folder*
57
+ means everywhere below.
57
58
  - Anything in the pasted prompt beyond the domain (a sentence, a link, a screenshot).
58
59
  - Files in the current project that look like site material (copy, logos, product lists, images).
59
60
  - What the agent already knows about the user from local memory or this conversation (a prior build,
@@ -62,10 +63,13 @@ Before asking anything, collect what the user has already given you, so you don'
62
63
 
63
64
  ## Step 3 — Ask
64
65
 
65
- If Step 2 already answers *what are you building* and *will they sell*, skip the Round 1–3 questions
66
- below, but still ask **Build mode** before moving on.
66
+ When Step 2 found a project in the folder, ask **Build mode** and the **Existing project** confirms
67
+ below in your first message, after Round 1 when Round 1 still needs asking; they don't count toward
68
+ the 3 rounds, and code that answers Round 1 never skips them. Otherwise don't ask Build mode: it
69
+ defaults (see *Build mode* below).
67
70
 
68
- Otherwise open with one line and **wait for the user's answer**: *"Tell me what you want to build — a
71
+ If Step 2 already answers the three Round 1 questions, skip the Round 1–3 questions below. Otherwise
72
+ open with one line and **wait for the user's answer**: *"Tell me what you want to build — a
69
73
  sentence is enough. Links, screenshots or files help too. Skip anything you don't want to answer."*
70
74
 
71
75
  > **"What do you want to build?" is an OPEN question — ask it in plain prose and wait.** Never turn it
@@ -84,18 +88,19 @@ decide" means *use defaults*, not *ask again*. A skipped tool result is not thei
84
88
  that round in plain text, as *When you ask the user* in the top-level `SKILL.md` says. The re-ask
85
89
  repeats the round; it doesn't count toward the 3.
86
90
 
87
- **Round 1 — what it is (the only required round):**
91
+ **Round 1 — what it is (required; these three decide the backend):**
88
92
 
89
93
  1. **What are you building?** The business or project, and what the site is for.
90
94
  2. **Will you sell anything on it?** Physical products, digital products, services with checkout —
91
- or nothing. *This decides the backend.* Skip it only when the answer is already unmistakable
92
- ("an online shop for my candles", "my personal blog"); when in doubt, ask.
93
- 3. **Who is it for?** The audience, and how they'll find the site. *(optional)*
95
+ or nothing. Skip it only when the answer is already unmistakable ("an online shop for my
96
+ candles", "my personal blog"); when in doubt, ask.
97
+ 3. **Will you publish content over time?** Blog posts, articles, news, recipes, portfolio pieces,
98
+ case studies, and how often — or a handful of pages that rarely change. Skip it only when the
99
+ answer is already unmistakable ("my personal blog", an app the user is deploying).
94
100
 
95
101
  **Round 2 — what goes on it (optional, adapt to Round 1):**
96
102
 
97
- - **Content they'll publish over time** — blog posts, articles, news, recipes, portfolio pieces,
98
- case studies? How often? Or is it a handful of pages that rarely change?
103
+ - **Who is it for?** The audience, and how they'll find the site.
99
104
  - **Pages** — propose a page list from what you know and ask them to add or remove.
100
105
  - If they sell: **what and how many** — a rough product count, categories, whether products have
101
106
  variants (size, colour), and the shipping regions.
@@ -110,6 +115,20 @@ the design directions, so it is **not** asked again later; gather it here:
110
115
  - **Colours & brand** — brand/accent colours, logo, fonts, existing copy, photos. Note where they live.
111
116
  - **Must-haves and no-gos** — contact form, newsletter, bookings, languages, things to avoid.
112
117
 
118
+ **Existing project — when Step 2 found a project in the folder (required, in the Build mode message):**
119
+
120
+ Build mode answers the look. The code shows the rest, but the user knows what has to survive the
121
+ move, so ask each as a confirm of what you found, naming it, so the user only says yes or corrects you:
122
+
123
+ 1. **Routes that open directly** — the client-side routes you found (name them, e.g. `/stats`,
124
+ `/settings`) must open from a typed link and survive a refresh? Ask only when the code routes in
125
+ the browser (React Router, Vue Router, a history or hash router); a server-routed app has none.
126
+ 2. **Data** — the database and environment variable names you found (or "none"): is that all the
127
+ app needs? Hostinger gives the app MySQL only: a different database (Postgres, MongoDB, a SQLite
128
+ file) goes under **Out of scope** as a migration the user owns, not as `Database: MySQL`.
129
+
130
+ Record the routes and data in **Existing project**.
131
+
113
132
  If the user asks for something outside the scope (user accounts, member areas, bookings with a
114
133
  calendar backend, user-generated content), say plainly that the headless flow doesn't support it yet
115
134
  and record it under **Out of scope** — don't promise it.
@@ -121,25 +140,27 @@ and record it under **Out of scope** — don't promise it.
121
140
  > for it. (A form that needs accounts, payments or a booking calendar is still out of scope — this is a
122
141
  > plain name/email/message enquiry form.)
123
142
 
124
- ### Build mode — ask once (this one *is* a user choice)
143
+ ### Build mode — a user choice when the folder holds a project
125
144
 
126
- The backend is your call, but **how the frontend gets built is the user's** — because most of the time
127
- the user already has their own frontend and just needs a Hostinger backend to wire it to. After the
128
- folder + memory inspection (Step 2), ask **one** question (a structured choice is ideal here), seeding
129
- the default from what you found:
145
+ The backend is your call, but **how the frontend gets built is the user's**. When Step 2 found a
146
+ project in the folder, ask **one** question (a structured choice is ideal here); it also settles the
147
+ app's look:
130
148
 
131
- - **Bring your own frontend** *(default when Step 2 found a real project in the folder)* — "I already
132
- have a frontend / I'll drive the design and we vibe-code it together." The agent **uses the user's
149
+ - **Bring your own frontend — keep my app's look** *(the default)* — the agent **uses the user's
133
150
  existing project in the current folder as the source**, collaborates on design/code at the user's
134
151
  direction, and wires it to the provisioned backend. There is **no agent-generated design** — the
135
- design step defers to the user (see the Build mode box at the top of the design step), and the frontend step builds on their project
136
- rather than scaffolding a new one.
137
- - **Agent designs and builds it** *(default when the folder has nothing site-like)* — "Give me ideas
138
- — you design and code it." Today's flow: the design step proposes directions and the frontend step
139
- scaffolds and implements them.
152
+ design step defers to the user (see the Build mode box at the top of the design step), and the
153
+ frontend step builds on their project rather than scaffolding a new one. Write "Keep the look we
154
+ have: the app in this folder" in **Design direction**.
155
+ - **Agent designs and builds it — a new design** — the design step proposes directions and the
156
+ frontend step scaffolds and implements them.
157
+
158
+ A skipped tool result is not a pick: re-ask in plain text, and use the default only when the user
159
+ says so (see *When you ask the user* in the top-level `SKILL.md`).
140
160
 
141
- A skipped tool result is not a pick: re-ask in plain text, and use the seeded default only when the
142
- user says so (see *When you ask the user* in the top-level `SKILL.md`).
161
+ Otherwise don't ask: Build mode is **Agent designs and builds**. When the user says they have their
162
+ own code elsewhere, ask for its path, inspect it as in Step 2, and from then on treat it as a project
163
+ found in the folder.
143
164
 
144
165
  Record the answer as the brief's **Build mode**. This is the one frontend/technology question you *do*
145
166
  put to the user; everything else about the stack (framework, SSR/SSG) is still inferred. The deliverable
@@ -195,6 +216,8 @@ site, not the backend:
195
216
 
196
217
  - **The plan** — what it is, audience, pages, products (if any), tone. Mark anything you defaulted
197
218
  (*"assumed: warm, minimal look"*) so the user sees what they skipped.
219
+ - **Your app** *(when Step 2 found a project in the folder)* — one line: keeps its look or gets a new
220
+ one, the routes that must open directly, and whether a database is created.
198
221
  - **How it's built** — state the backend as a *consequence* in one plain line, focused on what it
199
222
  means for them day to day ("I'll build this on WordPress, so you edit pages and posts in a normal
200
223
  WordPress admin"), **not** as a choice you're asking them to make. Don't ask "is WordPress OK?" or
@@ -208,8 +231,9 @@ theirs and note it in the brief.
208
231
  ## Step 6 — Write `brief.md` and hand off
209
232
 
210
233
  Write `brief.md` from the template below. Fill every section from the conversation, defaulting what
211
- the user skipped and tagging it `(assumed)`; leave out **Products** when nothing is sold. Use the
212
- user's own words where you can. Then update `state.json`:
234
+ the user skipped and tagging it `(assumed)`; leave out **Products** when nothing is sold and
235
+ **Existing project** unless Step 2 found a project in the folder. Use the user's own words where you
236
+ can. Then update `state.json`:
213
237
  `"stage": "provision"`, fresh `updated_at`.
214
238
 
215
239
  The **Suggested backend** section is the contract with `03-provision/SKILL.md`, which routes on it.
@@ -239,7 +263,7 @@ routes it to the `wordpress` subfolder with the store enabled (it is **not** a s
239
263
  (layout, structure, mood); colours / brand palette; any screenshots. This is where ALL design questions
240
264
  land — the frontend step reads this and does not re-ask. Write "Agent's choice: <short style>" when the
241
265
  user didn't specify. Record in the user's own words a request to keep their current look ("Keep the look
242
- we have: <site URL | screenshots | brand guide>") and any logo idea they describe; the design step acts
266
+ we have: <site URL | screenshots | brand guide | the app in this folder>") and any logo idea they describe; the design step acts
243
267
  on both.>
244
268
 
245
269
  ## Pages
@@ -261,14 +285,18 @@ on both.>
261
285
  ## Assets
262
286
  <logo, colours, photos, copy the user provided and where they are; "None yet" if none>
263
287
 
288
+ ## Existing project
289
+ <only when Step 2 found a project in the folder: its path; the framework; the client-side
290
+ routes that must open directly and on refresh (or "None"); Database: none | MySQL; the env var names
291
+ the app reads — names only, never values>
292
+
264
293
  ## Suggested backend
265
294
  **<WordPress | Ecommerce | WordPress + Ecommerce | WordPress + WooCommerce | Webapp>** — <one-line reason>.
266
295
  <"Confirmed by the user." or "User chose this over the recommended <X> because <reason>.">
267
296
 
268
297
  ## Build mode
269
298
  **<Bring your own frontend | Agent designs and builds>** — <one line>. <When "Bring your own frontend":
270
- the path to the user's existing project in the current folder (the frontend source), and how they want
271
- to drive design/code. When "Agent designs and builds": leave the design + frontend steps to run as
299
+ how they want to drive design/code; the project's path is under **Existing project**. When "Agent designs and builds": leave the design + frontend steps to run as
272
300
  normal.> The design step and the frontend step both read this: **Bring your own frontend** → design
273
301
  defers to the user and frontend builds on their project; **Agent designs and builds** → both run as normal.
274
302
 
@@ -58,6 +58,8 @@ All website/database operations below go through the Hostinger connector (`execu
58
58
  ## Step 3 — Database (only if the app needs one)
59
59
 
60
60
  Skip this step entirely for an app with no database (static SPA, an app that talks to an external API).
61
+ The app needs one when `brief.md` → **Existing project** says **Database: MySQL**, or, for a new app,
62
+ when the brief describes data the app stores itself.
61
63
  This is **join-half** work — it needs the live website, so run it once `provision.status == "completed"`.
62
64
 
63
65
  **Preferred — let the platform create the database and inject the credentials:**
@@ -66,7 +66,7 @@ const GetSkillRequestSchema = z.object({
66
66
 
67
67
  /**
68
68
  * MCP Server for Hostinger API
69
- * Generated from OpenAPI spec version 2.4.2
69
+ * Generated from OpenAPI spec version 2.4.3
70
70
  */
71
71
  class MCPServer {
72
72
  constructor({ name, version, tools, instructions, skills = [] }) {
@@ -97,7 +97,7 @@ interface Skill { uri: string; frontmatter: Record<string, unknown>; resources:
97
97
 
98
98
  /**
99
99
  * MCP Server for Hostinger API
100
- * Generated from OpenAPI spec version 2.4.2
100
+ * Generated from OpenAPI spec version 2.4.3
101
101
  */
102
102
  class MCPServer {
103
103
  private readonly name: string;
@@ -141,10 +141,10 @@ export default [
141
141
  },
142
142
  {
143
143
  "uri": "skill://hostinger-headless/steps/02-refine/SKILL.md",
144
- "digest": "sha256:e186715938d72ad7590ffb6b36ea3a48b1ea64c4f04fe667c3b2d027631e3ad8",
145
- "size": 18861,
144
+ "digest": "sha256:44e51eee7c03239b06eb73e83aa231d92069a96e519d5642145a774ad4dcda45",
145
+ "size": 20752,
146
146
  "mimeType": "text/markdown",
147
- "text": "---\nname: headless-step-refine\ndescription: \"Step 2 (refine) of the Hostinger Headless flow. Turns the user's seed — often just a domain — into a brief through conversation: asks what they want to build (purpose, audience, tone, pages, products, must-haves; images, local files, links all welcome), then *infers* the backend that fits (WordPress, Ecommerce, WordPress + Ecommerce, WordPress + WooCommerce for a one-site content+store, or Webapp for a custom app/SPA) — it never asks the user to pick a backend or a technology; the user confirms the plan in plain language, then it writes brief.md. Hostinger Ecommerce stays the default for selling; WooCommerce is chosen only when the user explicitly asks for it or wants content and a store on one WordPress site.\"\n---\n\n# headless-step-refine\n\nFind out what the user wants to build, guess which backend fits, and write it down. This is a\n**conversation, not a form**: the pasted prompt names a domain and nothing else, so everything about\nthe site comes from talking to the user.\n\n**Keep it short.** The only thing you must know is *what they are building* — enough to guess the\nbackend. Every other question is optional: the more the user gives, the better the site, but never\nhold the build hostage to refinement. Fill gaps with sensible defaults and say so in the brief. A\nskipped question tool is not the user passing on a question; see *When you ask the user* in the\ntop-level `SKILL.md`.\n\n> **Never ask the user to choose a backend or a technology.** Don't ask \"WordPress or Ecommerce?\",\n> don't present WordPress/Ecommerce/WooCommerce/Webapp as options, and don't surface the backend in a\n> multiple-choice UI. Open with **\"What do you want to build?\"** and infer the backend yourself in\n> Step 4 from what they describe (and whether they sell). The backend is a technical decision the flow\n> makes *for* the user — they confirm the **plan** (what the site is and does), not the plumbing. The\n> only exception is when the user *volunteers* a backend (\"build it on WordPress\", \"use WooCommerce\"):\n> then honour their choice and note it in the brief.\n\n**In:** the domain (the workspace directory name), plus anything the user already shared.\n**Out:** `~/.hostinger/<domain>/brief.md`.\n\nNothing in this step touches Hostinger. It only asks, decides and writes.\n\n---\n\n## Step 1 — Read the workspace, then the resume check\n\nRead everything already in `~/.hostinger/<domain>/` (`state.json`, `connect.json`, any notes, images\nor files the user dropped there) — it all counts as input.\n\nIf `brief.md` already exists for this domain, **don't overwrite it**. Show the user a three-line\nsummary (what the site is, its pages, the suggested backend) and ask: *keep it, or change\nsomething?* Keep → go to Step 6. Change → edit only what they asked for, then re-confirm (Step 5). A\nskipped tool result is not Keep; see *When you ask the user* in the top-level `SKILL.md`.\n\nIf the site is already provisioned (`site.json` exists), the backend is **fixed** — never change the\nSuggested backend on a provisioned site. Explain that switching backends means a new domain.\n\n## Step 2 — Gather what you already have\n\nBefore asking anything, collect what the user has already given you, so you don't ask for it again:\n\n- **Inspect the directory the command was run in** — this is the biggest signal for the build mode\n (Step 3). Look for an existing **frontend / webapp / commerce project**: a `package.json`, a\n framework config (`next.config.*`, `astro.config.*`, `vite.config.*`, `svelte.config.*`, `nuxt.*`),\n a `src/` or `app/` tree, `index.html`, a storefront/cart, etc. A real project here usually means the\n user **already has their own frontend** and mainly needs a Hostinger backend to wire it to — lean\n toward \"bring your own frontend\" in Step 3. If the folder has nothing site-like, assume they don't\n and lean toward \"agent builds it\".\n- Anything in the pasted prompt beyond the domain (a sentence, a link, a screenshot).\n- Files in the current project that look like site material (copy, logos, product lists, images).\n- What the agent already knows about the user from local memory or this conversation (a prior build,\n stack preferences, a frontend they always use).\n- The domain name itself — a hint only (`coral-pottery.com` suggests pottery), never a conclusion.\n\n## Step 3 — Ask\n\nIf Step 2 already answers *what are you building* and *will they sell*, skip the Round 1–3 questions\nbelow, but still ask **Build mode** before moving on.\n\nOtherwise open with one line and **wait for the user's answer**: *\"Tell me what you want to build — a\nsentence is enough. Links, screenshots or files help too. Skip anything you don't want to answer.\"*\n\n> **\"What do you want to build?\" is an OPEN question — ask it in plain prose and wait.** Never turn it\n> into a multiple-choice list of pre-baked site ideas (\"Coffee shop / Travel blog / Portfolio …\"):\n> that puts words in the user's mouth and defeats the point of asking. Let them describe it in their\n> own words. The structured question UI is only for genuinely *choice-shaped* follow-ups (selling\n> yes/no, which pages, tone adjectives, the build mode) — not for the opening question, and never for\n> the backend.\n\nAsk in **short rounds of 2–3 questions**, and adapt each round to the previous answers. If your tool\nhas a structured question UI (multiple choice with an \"other\" field), use it for the choice-shaped\nfollow-up questions (pages, selling yes/no, tone) — **never for the opening \"what do you want to\nbuild?\" and never for the backend** (that's your decision, not theirs). **At most 3 rounds**, and\nstop early as soon as you can guess the backend and the user seems done — \"just build it\" or \"you\ndecide\" means *use defaults*, not *ask again*. A skipped tool result is not their answer: re-ask\nthat round in plain text, as *When you ask the user* in the top-level `SKILL.md` says. The re-ask\nrepeats the round; it doesn't count toward the 3.\n\n**Round 1 — what it is (the only required round):**\n\n1. **What are you building?** The business or project, and what the site is for.\n2. **Will you sell anything on it?** Physical products, digital products, services with checkout —\n or nothing. *This decides the backend.* Skip it only when the answer is already unmistakable\n (\"an online shop for my candles\", \"my personal blog\"); when in doubt, ask.\n3. **Who is it for?** The audience, and how they'll find the site. *(optional)*\n\n**Round 2 — what goes on it (optional, adapt to Round 1):**\n\n- **Content they'll publish over time** — blog posts, articles, news, recipes, portfolio pieces,\n case studies? How often? Or is it a handful of pages that rarely change?\n- **Pages** — propose a page list from what you know and ask them to add or remove.\n- If they sell: **what and how many** — a rough product count, categories, whether products have\n variants (size, colour), and the shipping regions.\n\n**Round 3 — how it should feel (optional, skip what you already know):**\n\nThis is the round that captures **all design input** — the design step reads it from `brief.md` to derive\nthe design directions, so it is **not** asked again later; gather it here:\n\n- **Reference & look** — a **site they like (URL)** and what to take from it, plus a few adjectives for\n the vibe. Screenshots welcome.\n- **Colours & brand** — brand/accent colours, logo, fonts, existing copy, photos. Note where they live.\n- **Must-haves and no-gos** — contact form, newsletter, bookings, languages, things to avoid.\n\nIf the user asks for something outside the scope (user accounts, member areas, bookings with a\ncalendar backend, user-generated content), say plainly that the headless flow doesn't support it yet\nand record it under **Out of scope** — don't promise it.\n\n> **A contact form *is* supported (WordPress backends).** If the user wants one, keep it in\n> **Must-haves** and name it \"Contact form\". Provision installs **Contact Form 7** on `/cms` and the\n> frontend wires a contact page whose form emails the site owner through WordPress's built-in\n> `wp_mail()` — no third-party service and no API keys. It's **opt-in**: built only when the brief asks\n> for it. (A form that needs accounts, payments or a booking calendar is still out of scope — this is a\n> plain name/email/message enquiry form.)\n\n### Build mode — ask once (this one *is* a user choice)\n\nThe backend is your call, but **how the frontend gets built is the user's** — because most of the time\nthe user already has their own frontend and just needs a Hostinger backend to wire it to. After the\nfolder + memory inspection (Step 2), ask **one** question (a structured choice is ideal here), seeding\nthe default from what you found:\n\n- **Bring your own frontend** *(default when Step 2 found a real project in the folder)* — \"I already\n have a frontend / I'll drive the design and we vibe-code it together.\" The agent **uses the user's\n existing project in the current folder as the source**, collaborates on design/code at the user's\n direction, and wires it to the provisioned backend. There is **no agent-generated design** — the\n design step defers to the user (see the Build mode box at the top of the design step), and the frontend step builds on their project\n rather than scaffolding a new one.\n- **Agent designs and builds it** *(default when the folder has nothing site-like)* — \"Give me ideas\n — you design and code it.\" Today's flow: the design step proposes directions and the frontend step\n scaffolds and implements them.\n\nA skipped tool result is not a pick: re-ask in plain text, and use the seeded default only when the\nuser says so (see *When you ask the user* in the top-level `SKILL.md`).\n\nRecord the answer as the brief's **Build mode**. This is the one frontend/technology question you *do*\nput to the user; everything else about the stack (framework, SSR/SSG) is still inferred. The deliverable\nis the same either way — a working site on the backend; only *who drives the frontend* changes.\n\n## Step 4 — Decide the backend (your call, from the brief — not a question)\n\nFrom what the user described, **you** pick the backend that fits — this is an internal decision, never\na question put back to the user. Pick the backend that fits what the user told you:\n\n| Backend | Choose when | What it gives the owner |\n|---|---|---|\n| **WordPress** | No selling. The owner publishes and edits content: pages, blog, articles, portfolio. | Headless WordPress at `/cms` — write and edit content in wp-admin. |\n| **Ecommerce** | Selling is the point, and the rest is a few static pages (About, Contact, FAQ, policies). No ongoing blog. | Hostinger Ecommerce store — products, collections, checkout, payments, shipping, orders. |\n| **WordPress + Ecommerce** | Selling **and** regular editorial content (a blog, guides, lookbooks, recipes) the owner keeps adding to, run as **two** backends (WordPress site + separate Hostinger Ecommerce store). | Both: WordPress for content, the Ecommerce store for products and checkout. |\n| **WordPress + WooCommerce** | **Only** when the user explicitly names WooCommerce, or explicitly wants content **and** a store on **one** WordPress site (one admin, one domain). The one-site alternative to WordPress + Ecommerce. | A single headless WordPress at `/cms` with the WooCommerce plugin — products, cart, coupons and checkout (Cash on Delivery) over the WooCommerce Store API, plus WP posts/pages on the same site. |\n| **Webapp** | A custom **app, SPA or web application** that brings its own code and data — a dashboard, tool, calculator, booking app, a project the user is deploying — **not** a content site or a store. No CMS and no store engine. | A plain Node.js app host (an empty website, no `/cms`), with a MySQL database when the app needs one. The app owns all its routes and data. |\n\nRules of thumb:\n\n- **Is it an app, or a site?** A custom application the user builds/brings (its own logic, its own data,\n maybe a database) → **Webapp**. A content site or a shop → one of the three above. When the user says\n \"my app / SPA / dashboard / tool\", or hands you an existing app project to deploy, that's **Webapp**.\n- **Selling decides first (for sites).** No selling → WordPress. Selling → Ecommerce or\n WordPress + Ecommerce.\n- **Then the content.** Selling plus only a few fixed pages → Ecommerce. Selling plus content the\n owner will keep publishing → WordPress + Ecommerce.\n- **Hostinger Ecommerce is the default for selling — WooCommerce is never the default and is never\n recommended on its own.** Choose **WordPress + WooCommerce** only when the user explicitly names\n WooCommerce, or explicitly wants content **and** a store on one WordPress site (one admin, one\n domain — the one-site alternative to WordPress + Ecommerce). For every other selling case, keep\n Hostinger Ecommerce. If the user wants both content and a store but hasn't said \"one site\" or\n \"WooCommerce\", offer it as an option — don't switch to it silently.\n- **Don't add WordPress \"just in case\".** A second backend is a second admin to learn. Recommend the\n combination only when the user described ongoing content.\n- Selling a single service by enquiry (no checkout, no cart) is **not** commerce → WordPress with a\n contact page.\n- **Webapp never uses WordPress or a store.** If the user wants a custom app *and* a CMS/shop, that's two\n different sites — build the app as a Webapp and tell them the content/commerce backend is a separate\n domain.\n\n**Then set rendering** (write it in the brief's **Rendering** section): **Ecommerce → SSR required**\n(carts/search need per-request rendering — any SSR framework works; Next.js is the default, not a\nrequirement); **WordPress + WooCommerce → SSR required** (same reason — cart/checkout need per-request\nrendering); **WordPress → SSG (Astro)** if the site is mostly static and the owner is fine rebuilding\nto publish, otherwise **SSR**; **Webapp → whatever the app is** (SSR for a server app, SPA for a\nclient-only app) — it deploys as a Node.js app either way. When unsure, default to **Next.js SSR**.\n\n## Step 5 — Confirm the plan in one message\n\nSend **one** message that confirms **what you'll build** — the plan, not the plumbing. Lead with the\nsite, not the backend:\n\n- **The plan** — what it is, audience, pages, products (if any), tone. Mark anything you defaulted\n (*\"assumed: warm, minimal look\"*) so the user sees what they skipped.\n- **How it's built** — state the backend as a *consequence* in one plain line, focused on what it\n means for them day to day (\"I'll build this on WordPress, so you edit pages and posts in a normal\n WordPress admin\"), **not** as a choice you're asking them to make. Don't ask \"is WordPress OK?\" or\n offer alternatives unless it was a genuine close call *and* the difference matters to the user.\n\nEnd with *\"Good to go, or anything to change?\"* A yes (or \"go\") → write `brief.md`. A skipped tool\nresult is not a yes (see *When you ask the user* in the top-level `SKILL.md`). Changes → apply them\nand write it; don't loop back into questions. If the user *volunteers* a different backend, use\ntheirs and note it in the brief.\n\n## Step 6 — Write `brief.md` and hand off\n\nWrite `brief.md` from the template below. Fill every section from the conversation, defaulting what\nthe user skipped and tagging it `(assumed)`; leave out **Products** when nothing is sold. Use the\nuser's own words where you can. Then update `state.json`:\n`\"stage\": \"provision\"`, fresh `updated_at`.\n\nThe **Suggested backend** section is the contract with `03-provision/SKILL.md`, which routes on it.\nIts first line must be exactly one of `**WordPress**`, `**Ecommerce**`, `**WordPress + Ecommerce**`,\n`**WordPress + WooCommerce**` or `**Webapp**`, followed by an em dash and the one-line reason.\n`**WordPress + WooCommerce**` is the WordPress backend with the WooCommerce store turned on — provision\nroutes it to the `wordpress` subfolder with the store enabled (it is **not** a separate backend).\n\n## `brief.md` template\n\n```markdown\n# Brief — <domain>\n\n> Brief agreed with the user on <today>.\n\n## Purpose\n<what the site is, who runs it, what it's for — 2–3 sentences>\n\n## Audience\n<who visits, how they arrive, what device they mostly use>\n\n## Tone\n<voice in a sentence or two>\n\n## Design direction\n<look & feel in a few adjectives; **reference site(s)** the user named (URLs) and what to take from them\n(layout, structure, mood); colours / brand palette; any screenshots. This is where ALL design questions\nland — the frontend step reads this and does not re-ask. Write \"Agent's choice: <short style>\" when the\nuser didn't specify. Record in the user's own words a request to keep their current look (\"Keep the look\nwe have: <site URL | screenshots | brand guide>\") and any logo idea they describe; the design step acts\non both.>\n\n## Pages\n- **<Page>** — <one-line purpose>\n- …\n\n## Products\n<only when selling: what is sold, rough count, categories, variants, shipping regions, payments>\n\n## Content\n<what the owner will publish over time and how often — or \"A few fixed pages; no ongoing content.\">\n\n## Must-haves\n- …\n\n## Out of scope\n- …\n\n## Assets\n<logo, colours, photos, copy the user provided and where they are; \"None yet\" if none>\n\n## Suggested backend\n**<WordPress | Ecommerce | WordPress + Ecommerce | WordPress + WooCommerce | Webapp>** — <one-line reason>.\n<\"Confirmed by the user.\" or \"User chose this over the recommended <X> because <reason>.\">\n\n## Build mode\n**<Bring your own frontend | Agent designs and builds>** — <one line>. <When \"Bring your own frontend\":\nthe path to the user's existing project in the current folder (the frontend source), and how they want\nto drive design/code. When \"Agent designs and builds\": leave the design + frontend steps to run as\nnormal.> The design step and the frontend step both read this: **Bring your own frontend** → design\ndefers to the user and frontend builds on their project; **Agent designs and builds** → both run as normal.\n\n## Rendering\n**<SSR | SSG>** — <reason>. Rules: **Ecommerce requires SSR** and **WordPress + WooCommerce requires\nSSR** (carts/checkout need per-request rendering — any SSR framework works; Next.js is the default, not\na requirement); **WordPress may be SSG (Astro)** when the site is mostly static and rarely changes,\notherwise **SSR**. Default when unsure: **Next.js SSR**.\n```\n\n## Example — WordPress + Ecommerce\n\n> A ceramicist sells around 30 handmade pieces and writes a monthly studio journal.\n\n```markdown\n## Suggested backend\n**WordPress + Ecommerce** — sells ~30 products with checkout, and the owner publishes a monthly\nstudio journal they want to edit themselves.\nConfirmed by the user.\n```\n"
147
+ "text": "---\nname: headless-step-refine\ndescription: \"Step 2 (refine) of the Hostinger Headless flow. Turns the user's seed — often just a domain — into a brief through conversation: asks what they want to build (purpose, audience, tone, pages, products, must-haves; images, local files, links all welcome), then *infers* the backend that fits (WordPress, Ecommerce, WordPress + Ecommerce, WordPress + WooCommerce for a one-site content+store, or Webapp for a custom app/SPA) — it never asks the user to pick a backend or a technology; the user confirms the plan in plain language, then it writes brief.md. Hostinger Ecommerce stays the default for selling; WooCommerce is chosen only when the user explicitly asks for it or wants content and a store on one WordPress site.\"\n---\n\n# headless-step-refine\n\nFind out what the user wants to build, guess which backend fits, and write it down. This is a\n**conversation, not a form**: the pasted prompt names a domain and nothing else, so everything about\nthe site comes from talking to the user.\n\n**Keep it short.** You must know what they are building, whether they sell and whether they'll\npublish content over time — enough to guess the backend — plus, when Step 2 found a project in the folder, the\n**Existing project** confirms. Every other question is optional: the more the user gives, the better the site, but never\nhold the build hostage to refinement. Fill gaps with sensible defaults and say so in the brief. A\nskipped question tool is not the user passing on a question; see *When you ask the user* in the\ntop-level `SKILL.md`.\n\n> **Never ask the user to choose a backend or a technology.** Don't ask \"WordPress or Ecommerce?\",\n> don't present WordPress/Ecommerce/WooCommerce/Webapp as options, and don't surface the backend in a\n> multiple-choice UI. Open with **\"What do you want to build?\"** and infer the backend yourself in\n> Step 4 from what they describe (and whether they sell). The backend is a technical decision the flow\n> makes *for* the user — they confirm the **plan** (what the site is and does), not the plumbing. The\n> only exception is when the user *volunteers* a backend (\"build it on WordPress\", \"use WooCommerce\"):\n> then honour their choice and note it in the brief.\n\n**In:** the domain (the workspace directory name), plus anything the user already shared.\n**Out:** `~/.hostinger/<domain>/brief.md`.\n\nNothing in this step touches Hostinger. It only asks, decides and writes.\n\n---\n\n## Step 1 — Read the workspace, then the resume check\n\nRead everything already in `~/.hostinger/<domain>/` (`state.json`, `connect.json`, any notes, images\nor files the user dropped there) — it all counts as input.\n\nIf `brief.md` already exists for this domain, **don't overwrite it**. Show the user a three-line\nsummary (what the site is, its pages, the suggested backend) and ask: *keep it, or change\nsomething?* Keep → go to Step 6. Change → edit only what they asked for, then re-confirm (Step 5). A\nskipped tool result is not Keep; see *When you ask the user* in the top-level `SKILL.md`.\n\nIf the site is already provisioned (`site.json` exists), the backend is **fixed** — never change the\nSuggested backend on a provisioned site. Explain that switching backends means a new domain.\n\n## Step 2 — Gather what you already have\n\nBefore asking anything, collect what the user has already given you, so you don't ask for it again:\n\n- **Inspect the directory the command was run in** — this is the biggest signal for the build mode\n (Step 3). Look for an existing **frontend / webapp / commerce project**: a `package.json`, a\n framework config (`next.config.*`, `astro.config.*`, `vite.config.*`, `svelte.config.*`, `nuxt.*`),\n a `src/` or `app/` tree, `index.html`, a storefront/cart, etc. A real project here usually means the\n user **already has their own frontend** and mainly needs a Hostinger backend to wire it to — lean\n toward \"bring your own frontend\" in Step 3. That is what *Step 2 found a project in the folder*\n means everywhere below.\n- Anything in the pasted prompt beyond the domain (a sentence, a link, a screenshot).\n- Files in the current project that look like site material (copy, logos, product lists, images).\n- What the agent already knows about the user from local memory or this conversation (a prior build,\n stack preferences, a frontend they always use).\n- The domain name itself — a hint only (`coral-pottery.com` suggests pottery), never a conclusion.\n\n## Step 3 — Ask\n\nWhen Step 2 found a project in the folder, ask **Build mode** and the **Existing project** confirms\nbelow in your first message, after Round 1 when Round 1 still needs asking; they don't count toward\nthe 3 rounds, and code that answers Round 1 never skips them. Otherwise don't ask Build mode: it\ndefaults (see *Build mode* below).\n\nIf Step 2 already answers the three Round 1 questions, skip the Round 1–3 questions below. Otherwise\nopen with one line and **wait for the user's answer**: *\"Tell me what you want to build — a\nsentence is enough. Links, screenshots or files help too. Skip anything you don't want to answer.\"*\n\n> **\"What do you want to build?\" is an OPEN question — ask it in plain prose and wait.** Never turn it\n> into a multiple-choice list of pre-baked site ideas (\"Coffee shop / Travel blog / Portfolio …\"):\n> that puts words in the user's mouth and defeats the point of asking. Let them describe it in their\n> own words. The structured question UI is only for genuinely *choice-shaped* follow-ups (selling\n> yes/no, which pages, tone adjectives, the build mode) — not for the opening question, and never for\n> the backend.\n\nAsk in **short rounds of 2–3 questions**, and adapt each round to the previous answers. If your tool\nhas a structured question UI (multiple choice with an \"other\" field), use it for the choice-shaped\nfollow-up questions (pages, selling yes/no, tone) — **never for the opening \"what do you want to\nbuild?\" and never for the backend** (that's your decision, not theirs). **At most 3 rounds**, and\nstop early as soon as you can guess the backend and the user seems done — \"just build it\" or \"you\ndecide\" means *use defaults*, not *ask again*. A skipped tool result is not their answer: re-ask\nthat round in plain text, as *When you ask the user* in the top-level `SKILL.md` says. The re-ask\nrepeats the round; it doesn't count toward the 3.\n\n**Round 1 — what it is (required; these three decide the backend):**\n\n1. **What are you building?** The business or project, and what the site is for.\n2. **Will you sell anything on it?** Physical products, digital products, services with checkout —\n or nothing. Skip it only when the answer is already unmistakable (\"an online shop for my\n candles\", \"my personal blog\"); when in doubt, ask.\n3. **Will you publish content over time?** Blog posts, articles, news, recipes, portfolio pieces,\n case studies, and how often — or a handful of pages that rarely change. Skip it only when the\n answer is already unmistakable (\"my personal blog\", an app the user is deploying).\n\n**Round 2 — what goes on it (optional, adapt to Round 1):**\n\n- **Who is it for?** The audience, and how they'll find the site.\n- **Pages** — propose a page list from what you know and ask them to add or remove.\n- If they sell: **what and how many** — a rough product count, categories, whether products have\n variants (size, colour), and the shipping regions.\n\n**Round 3 — how it should feel (optional, skip what you already know):**\n\nThis is the round that captures **all design input** — the design step reads it from `brief.md` to derive\nthe design directions, so it is **not** asked again later; gather it here:\n\n- **Reference & look** — a **site they like (URL)** and what to take from it, plus a few adjectives for\n the vibe. Screenshots welcome.\n- **Colours & brand** — brand/accent colours, logo, fonts, existing copy, photos. Note where they live.\n- **Must-haves and no-gos** — contact form, newsletter, bookings, languages, things to avoid.\n\n**Existing project — when Step 2 found a project in the folder (required, in the Build mode message):**\n\nBuild mode answers the look. The code shows the rest, but the user knows what has to survive the\nmove, so ask each as a confirm of what you found, naming it, so the user only says yes or corrects you:\n\n1. **Routes that open directly** — the client-side routes you found (name them, e.g. `/stats`,\n `/settings`) must open from a typed link and survive a refresh? Ask only when the code routes in\n the browser (React Router, Vue Router, a history or hash router); a server-routed app has none.\n2. **Data** — the database and environment variable names you found (or \"none\"): is that all the\n app needs? Hostinger gives the app MySQL only: a different database (Postgres, MongoDB, a SQLite\n file) goes under **Out of scope** as a migration the user owns, not as `Database: MySQL`.\n\nRecord the routes and data in **Existing project**.\n\nIf the user asks for something outside the scope (user accounts, member areas, bookings with a\ncalendar backend, user-generated content), say plainly that the headless flow doesn't support it yet\nand record it under **Out of scope** — don't promise it.\n\n> **A contact form *is* supported (WordPress backends).** If the user wants one, keep it in\n> **Must-haves** and name it \"Contact form\". Provision installs **Contact Form 7** on `/cms` and the\n> frontend wires a contact page whose form emails the site owner through WordPress's built-in\n> `wp_mail()` — no third-party service and no API keys. It's **opt-in**: built only when the brief asks\n> for it. (A form that needs accounts, payments or a booking calendar is still out of scope — this is a\n> plain name/email/message enquiry form.)\n\n### Build mode — a user choice when the folder holds a project\n\nThe backend is your call, but **how the frontend gets built is the user's**. When Step 2 found a\nproject in the folder, ask **one** question (a structured choice is ideal here); it also settles the\napp's look:\n\n- **Bring your own frontend — keep my app's look** *(the default)* — the agent **uses the user's\n existing project in the current folder as the source**, collaborates on design/code at the user's\n direction, and wires it to the provisioned backend. There is **no agent-generated design** — the\n design step defers to the user (see the Build mode box at the top of the design step), and the\n frontend step builds on their project rather than scaffolding a new one. Write \"Keep the look we\n have: the app in this folder\" in **Design direction**.\n- **Agent designs and builds it — a new design** — the design step proposes directions and the\n frontend step scaffolds and implements them.\n\nA skipped tool result is not a pick: re-ask in plain text, and use the default only when the user\nsays so (see *When you ask the user* in the top-level `SKILL.md`).\n\nOtherwise don't ask: Build mode is **Agent designs and builds**. When the user says they have their\nown code elsewhere, ask for its path, inspect it as in Step 2, and from then on treat it as a project\nfound in the folder.\n\nRecord the answer as the brief's **Build mode**. This is the one frontend/technology question you *do*\nput to the user; everything else about the stack (framework, SSR/SSG) is still inferred. The deliverable\nis the same either way — a working site on the backend; only *who drives the frontend* changes.\n\n## Step 4 — Decide the backend (your call, from the brief — not a question)\n\nFrom what the user described, **you** pick the backend that fits — this is an internal decision, never\na question put back to the user. Pick the backend that fits what the user told you:\n\n| Backend | Choose when | What it gives the owner |\n|---|---|---|\n| **WordPress** | No selling. The owner publishes and edits content: pages, blog, articles, portfolio. | Headless WordPress at `/cms` — write and edit content in wp-admin. |\n| **Ecommerce** | Selling is the point, and the rest is a few static pages (About, Contact, FAQ, policies). No ongoing blog. | Hostinger Ecommerce store — products, collections, checkout, payments, shipping, orders. |\n| **WordPress + Ecommerce** | Selling **and** regular editorial content (a blog, guides, lookbooks, recipes) the owner keeps adding to, run as **two** backends (WordPress site + separate Hostinger Ecommerce store). | Both: WordPress for content, the Ecommerce store for products and checkout. |\n| **WordPress + WooCommerce** | **Only** when the user explicitly names WooCommerce, or explicitly wants content **and** a store on **one** WordPress site (one admin, one domain). The one-site alternative to WordPress + Ecommerce. | A single headless WordPress at `/cms` with the WooCommerce plugin — products, cart, coupons and checkout (Cash on Delivery) over the WooCommerce Store API, plus WP posts/pages on the same site. |\n| **Webapp** | A custom **app, SPA or web application** that brings its own code and data — a dashboard, tool, calculator, booking app, a project the user is deploying — **not** a content site or a store. No CMS and no store engine. | A plain Node.js app host (an empty website, no `/cms`), with a MySQL database when the app needs one. The app owns all its routes and data. |\n\nRules of thumb:\n\n- **Is it an app, or a site?** A custom application the user builds/brings (its own logic, its own data,\n maybe a database) → **Webapp**. A content site or a shop → one of the three above. When the user says\n \"my app / SPA / dashboard / tool\", or hands you an existing app project to deploy, that's **Webapp**.\n- **Selling decides first (for sites).** No selling → WordPress. Selling → Ecommerce or\n WordPress + Ecommerce.\n- **Then the content.** Selling plus only a few fixed pages → Ecommerce. Selling plus content the\n owner will keep publishing → WordPress + Ecommerce.\n- **Hostinger Ecommerce is the default for selling — WooCommerce is never the default and is never\n recommended on its own.** Choose **WordPress + WooCommerce** only when the user explicitly names\n WooCommerce, or explicitly wants content **and** a store on one WordPress site (one admin, one\n domain — the one-site alternative to WordPress + Ecommerce). For every other selling case, keep\n Hostinger Ecommerce. If the user wants both content and a store but hasn't said \"one site\" or\n \"WooCommerce\", offer it as an option — don't switch to it silently.\n- **Don't add WordPress \"just in case\".** A second backend is a second admin to learn. Recommend the\n combination only when the user described ongoing content.\n- Selling a single service by enquiry (no checkout, no cart) is **not** commerce → WordPress with a\n contact page.\n- **Webapp never uses WordPress or a store.** If the user wants a custom app *and* a CMS/shop, that's two\n different sites — build the app as a Webapp and tell them the content/commerce backend is a separate\n domain.\n\n**Then set rendering** (write it in the brief's **Rendering** section): **Ecommerce → SSR required**\n(carts/search need per-request rendering — any SSR framework works; Next.js is the default, not a\nrequirement); **WordPress + WooCommerce → SSR required** (same reason — cart/checkout need per-request\nrendering); **WordPress → SSG (Astro)** if the site is mostly static and the owner is fine rebuilding\nto publish, otherwise **SSR**; **Webapp → whatever the app is** (SSR for a server app, SPA for a\nclient-only app) — it deploys as a Node.js app either way. When unsure, default to **Next.js SSR**.\n\n## Step 5 — Confirm the plan in one message\n\nSend **one** message that confirms **what you'll build** — the plan, not the plumbing. Lead with the\nsite, not the backend:\n\n- **The plan** — what it is, audience, pages, products (if any), tone. Mark anything you defaulted\n (*\"assumed: warm, minimal look\"*) so the user sees what they skipped.\n- **Your app** *(when Step 2 found a project in the folder)* — one line: keeps its look or gets a new\n one, the routes that must open directly, and whether a database is created.\n- **How it's built** — state the backend as a *consequence* in one plain line, focused on what it\n means for them day to day (\"I'll build this on WordPress, so you edit pages and posts in a normal\n WordPress admin\"), **not** as a choice you're asking them to make. Don't ask \"is WordPress OK?\" or\n offer alternatives unless it was a genuine close call *and* the difference matters to the user.\n\nEnd with *\"Good to go, or anything to change?\"* A yes (or \"go\") → write `brief.md`. A skipped tool\nresult is not a yes (see *When you ask the user* in the top-level `SKILL.md`). Changes → apply them\nand write it; don't loop back into questions. If the user *volunteers* a different backend, use\ntheirs and note it in the brief.\n\n## Step 6 — Write `brief.md` and hand off\n\nWrite `brief.md` from the template below. Fill every section from the conversation, defaulting what\nthe user skipped and tagging it `(assumed)`; leave out **Products** when nothing is sold and\n**Existing project** unless Step 2 found a project in the folder. Use the user's own words where you\ncan. Then update `state.json`:\n`\"stage\": \"provision\"`, fresh `updated_at`.\n\nThe **Suggested backend** section is the contract with `03-provision/SKILL.md`, which routes on it.\nIts first line must be exactly one of `**WordPress**`, `**Ecommerce**`, `**WordPress + Ecommerce**`,\n`**WordPress + WooCommerce**` or `**Webapp**`, followed by an em dash and the one-line reason.\n`**WordPress + WooCommerce**` is the WordPress backend with the WooCommerce store turned on — provision\nroutes it to the `wordpress` subfolder with the store enabled (it is **not** a separate backend).\n\n## `brief.md` template\n\n```markdown\n# Brief — <domain>\n\n> Brief agreed with the user on <today>.\n\n## Purpose\n<what the site is, who runs it, what it's for — 2–3 sentences>\n\n## Audience\n<who visits, how they arrive, what device they mostly use>\n\n## Tone\n<voice in a sentence or two>\n\n## Design direction\n<look & feel in a few adjectives; **reference site(s)** the user named (URLs) and what to take from them\n(layout, structure, mood); colours / brand palette; any screenshots. This is where ALL design questions\nland — the frontend step reads this and does not re-ask. Write \"Agent's choice: <short style>\" when the\nuser didn't specify. Record in the user's own words a request to keep their current look (\"Keep the look\nwe have: <site URL | screenshots | brand guide | the app in this folder>\") and any logo idea they describe; the design step acts\non both.>\n\n## Pages\n- **<Page>** — <one-line purpose>\n- …\n\n## Products\n<only when selling: what is sold, rough count, categories, variants, shipping regions, payments>\n\n## Content\n<what the owner will publish over time and how often — or \"A few fixed pages; no ongoing content.\">\n\n## Must-haves\n- …\n\n## Out of scope\n- …\n\n## Assets\n<logo, colours, photos, copy the user provided and where they are; \"None yet\" if none>\n\n## Existing project\n<only when Step 2 found a project in the folder: its path; the framework; the client-side\nroutes that must open directly and on refresh (or \"None\"); Database: none | MySQL; the env var names\nthe app reads — names only, never values>\n\n## Suggested backend\n**<WordPress | Ecommerce | WordPress + Ecommerce | WordPress + WooCommerce | Webapp>** — <one-line reason>.\n<\"Confirmed by the user.\" or \"User chose this over the recommended <X> because <reason>.\">\n\n## Build mode\n**<Bring your own frontend | Agent designs and builds>** — <one line>. <When \"Bring your own frontend\":\nhow they want to drive design/code; the project's path is under **Existing project**. When \"Agent designs and builds\": leave the design + frontend steps to run as\nnormal.> The design step and the frontend step both read this: **Bring your own frontend** → design\ndefers to the user and frontend builds on their project; **Agent designs and builds** → both run as normal.\n\n## Rendering\n**<SSR | SSG>** — <reason>. Rules: **Ecommerce requires SSR** and **WordPress + WooCommerce requires\nSSR** (carts/checkout need per-request rendering — any SSR framework works; Next.js is the default, not\na requirement); **WordPress may be SSG (Astro)** when the site is mostly static and rarely changes,\notherwise **SSR**. Default when unsure: **Next.js SSR**.\n```\n\n## Example — WordPress + Ecommerce\n\n> A ceramicist sells around 30 handmade pieces and writes a monthly studio journal.\n\n```markdown\n## Suggested backend\n**WordPress + Ecommerce** — sells ~30 products with checkout, and the owner publishes a monthly\nstudio journal they want to edit themselves.\nConfirmed by the user.\n```\n"
148
148
  },
149
149
  {
150
150
  "uri": "skill://hostinger-headless/steps/03-provision/SKILL.md",
@@ -162,10 +162,10 @@ export default [
162
162
  },
163
163
  {
164
164
  "uri": "skill://hostinger-headless/steps/03-provision/webapp/SKILL.md",
165
- "digest": "sha256:9b7aa3f3643b20c8a270ab755fa8ce380692faab479d00296d12c06d12a8f65a",
166
- "size": 9334,
165
+ "digest": "sha256:1b4639929e9dd7dd55ec65ea674ee0b881f039efe5eec5505a690b6b5ba29372",
166
+ "size": 9491,
167
167
  "mimeType": "text/markdown",
168
- "text": "---\nname: headless-step-provision-webapp\ndescription: \"Provision a plain Node.js app host for the Hostinger Headless flow: create an empty website (no WordPress, no store engine, no /cms), optionally a MySQL database, then record it in site.json. For any webapp / SPA / app that brings its own code and data.\"\n---\n\n# headless-step-provision-webapp\n\nProvision a bare **Node.js app host** for `<domain>` and prove it is ready. This is the backend for\n**any webapp, SPA or app** that brings its own code (and, if it needs one, its own database) — there is\nno CMS and no store engine here. The site is an **empty website**: just the hosting slot that the\nfrontend step builds into and the deploy step ships to as a Node.js application. **Out:** `site.json`.\n\n> **Not WordPress, not Ecommerce.** Never provision `headless_wordpress` / `headless_ecommerce` for a\n> webapp, and never install WordPress at `/cms`. A webapp owns its own routes, data and rendering.\n\nAll website/database operations below go through the Hostinger connector (`execute`).\n\n> **Fire vs join (background provision).** Steps 1–2 up to `hosting_websites_start-setup` are the\n> **fire** half: once the setup is queued, record `provision.in_progress` and return to do prepare work\n> (see the router `../SKILL.md`). The readiness **poll** at the end of Step 2, plus Steps 3–5, are the\n> **join** half — run them when an instance-barrier step needs the backend (or immediately, if the\n> agent can't interleave). An already-`completed` site detected in Step 1 means provision is done:\n> record `provision.completed` and adopt it.\n\n## Step 1 — Detect before you create (never provision twice)\n\n1. `hosting_websites_list { \"domain\": \"<domain>\" }` — substring filter, so **exact-match** the row's\n `domain`. If a row exists, note `username`, `order_id`, `website_type`.\n2. `hosting_websites_list-setups { \"domain\": \"<domain>\" }` — exact-match the row:\n - `completed` → already provisioned → skip to Step 3 (adopt it).\n - `running` → skip to Step 2 (poll).\n - `failed` → tell the user and point them at hPanel; do **not** call start-setup again on your own.\n3. If the site exists but is a different kind (`website_type` is `headless`, or a WordPress/store site),\n stop and tell the user — this domain already hosts something else.\n\n## Step 2 — Create (only when nothing exists)\n\n1. Pick the plan: `hosting_orders_list { \"statuses\": [\"active\"] }`. One active order → use it; several\n → ask the user which.\n2. Start the setup as an **empty website** — **omit `type`** (no WordPress, no store):\n\n ```json\n { \"operation\": \"hosting_websites_start-setup\",\n \"params\": { \"order_id\": <order_id>, \"domain\": \"<domain>\" } }\n ```\n\n `domain` must be a **customer-owned** domain. For a temporary `*.hostingersite.com` subdomain,\n **omit `domain`** — the setup generates one itself and returns it in the response. Don't call a\n separate subdomain op to make one first: there's no need in this flow. (The API would also accept a\n subdomain you generated via `hosting_domains_generate-free-subdomain`, valid for 24 h from the client\n that made it — this flow just doesn't use that.) The response then carries the generated domain: if it\n differs from the workspace directory name, move `~/.hostinger/<old>/` to `~/.hostinger/<new>/`\n (if `<new>/` already exists, stop and ask instead of moving into it),\n update the domain in `brief.md`, and tell the user the new domain.\n3. Poll `hosting_websites_list-setups { \"domain\": \"<domain>\" }` every ~15 s until `completed`\n (typically under a minute; allow a few minutes). `failed` → stop, point at hPanel.\n\n## Step 3 — Database (only if the app needs one)\n\nSkip this step entirely for an app with no database (static SPA, an app that talks to an external API).\nThis is **join-half** work — it needs the live website, so run it once `provision.status == \"completed\"`.\n\n**Preferred — let the platform create the database and inject the credentials:**\n`hosting_databases_setup-website { username, domain }` creates a MySQL database, **generates the\npassword, writes `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USER` / `DB_PASSWORD` / `DATABASE_URL` into the\nwebsite's environment variables, and restarts the app — in one call.** The password is never returned,\nso there is **nothing to persist and nothing to lose across sessions** — the app reads it from the\nenvironment. (It 422s if any of those env vars already exist; clear them first with\n`hosting_nodejs_replace-environment-variables`. Frameworks that bake vars into the build — Next.js,\n`NEXT_PUBLIC_*` — only pick up the values on the deploy build.)\n\n> **⚠️ A later `hosting_nodejs_replace-environment-variables` wipes these credentials.** That call is a\n> **full replace**, and when you list the current vars first the `DB_PASSWORD` (and `DATABASE_URL`) read\n> back **masked** (`********`) — the generated password is unknown, so you cannot resend it and the DB\n> env is lost. One rule, pick one:\n> - **(a) Set every other app env var first, and call `setup-website` last** — then nothing has to\n> replace the env again.\n> - **(b) If a later replace is unavoidable**, reset the password with\n> `hosting_databases_change-password { username, name, password }` and resend **all** of `DB_HOST`,\n> `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` with the new password in that same\n> full replace.\n\n**Manual alternative** — only when you need a specific name/user or finer control:\n\n1. `hosting_databases_create` with a name, a user, and a password you generate — name and user are\n prefixed with the account username automatically. Read the full, prefixed `name`/`user` back from\n `hosting_databases_list`.\n2. **Which host the app connects to:** the app runs **on the website**, so Node.js connects to\n **`127.0.0.1:3306`** — never `localhost` (it can resolve to IPv6 `::1`, and not every database user\n has that grant), and never the external `host` from `hosting_databases_list` (`srvNNNN.hstgr.io`,\n which has no grant for connections from the server itself).\n3. **Set the env vars now, while you still hold the generated password.** The password is **not persisted\n anywhere** (never in `site.json`), so set `DB_HOST=127.0.0.1`, `DB_PORT=3306`, `DB_NAME`, `DB_USER`,\n `DB_PASSWORD`, plus `DATABASE_URL=mysql://USER:PASSWORD@127.0.0.1:3306/NAME` (URL-encode USER/PASSWORD)\n right here with `hosting_nodejs_replace-environment-variables` (a **full replace** — list current keys\n first; values come back masked; send the complete set with real values). Saving restarts the process;\n build-time vars need a fresh deploy (`hosting_deploy`) afterwards. **Don't defer this to deploy:** on\n a **cross-session resume** the generated password is gone, so you must **reset it** with\n `hosting_databases_change-password { username, name, password }` and set the env vars from the new one.\n4. **Never** put the password in source, a committed `.env`, client-side JavaScript, or a chat reply;\n never print it once stored.\n\n## Step 4 — Verify\n\n| Check | How | Expect |\n|---|---|---|\n| Website exists | `hosting_websites_list` exact row | the row is present, `username` known |\n| Setup finished | `hosting_websites_list-setups` exact row | `status: completed` |\n| Database reachable *(only if created)* | `hosting_databases_list { username }` | the prefixed db + user are listed |\n\n`GET https://<domain>/` returns the Hostinger default page (or 403) until the frontend is deployed —\nexpected, not a failure. There is **no `/cms` and no API** on this domain; the app itself serves every\nroute once it is deployed (Step 8).\n\nIf a check fails: retry once after ~30 s; if it still fails, stop and point the user at hPanel.\n\n## Step 5 — Record `site.json`\n\n```json\n{\n \"backend\": \"webapp\",\n \"website_type\": \"empty\",\n \"order_id\": 1007223428,\n \"username\": \"u…\",\n \"runtime\": \"nodejs\",\n \"database\": {\n \"enabled\": false,\n \"engine\": \"mysql\",\n \"name\": \"u…_appdb\",\n \"user\": \"u…_app\",\n \"host\": \"127.0.0.1\",\n \"port\": 3306\n },\n \"urls\": {\n \"site\": \"https://<domain>\"\n },\n \"provisioned_at\": \"…\",\n \"verified_at\": \"…\"\n}\n```\n\nNo passwords or tokens in `site.json` — only the non-secret connection shape (host/port/name/user). Set\n`\"database\": { \"enabled\": false }` (and drop the rest) when the app needs no database.\n\n## Deploy & content model (what happens next)\n\n- **Content model is skipped for a webapp** — there is no CMS or store to seed; the app owns its own\n data. The content-model router (`../../04-content-model/SKILL.md`) passes straight through to the\n frontend step for this backend.\n- **Deploy** is a standard Node.js application deploy, handled by `../../08-deploy/SKILL.md`: it archives\n the **source** (not the build output; exclude `node_modules/`, `.next/`, `dist/`, build output and\n `.git/`), then deploys with `hosting_deploy` (`type: \"nodejs\"`, upload + server-side `npm install` +\n build, one shot) passing the framework's `appType` + `outputDirectory` inline, and polls\n `hosting_get-deployment` until the build completes. Any supported `appType` works (Next.js, Nuxt,\n Express, NestJS, Fastify, Vite/React/Vue SPA, …). There is no `/cms` to preserve here, so the whole\n root is the app.\n"
168
+ "text": "---\nname: headless-step-provision-webapp\ndescription: \"Provision a plain Node.js app host for the Hostinger Headless flow: create an empty website (no WordPress, no store engine, no /cms), optionally a MySQL database, then record it in site.json. For any webapp / SPA / app that brings its own code and data.\"\n---\n\n# headless-step-provision-webapp\n\nProvision a bare **Node.js app host** for `<domain>` and prove it is ready. This is the backend for\n**any webapp, SPA or app** that brings its own code (and, if it needs one, its own database) — there is\nno CMS and no store engine here. The site is an **empty website**: just the hosting slot that the\nfrontend step builds into and the deploy step ships to as a Node.js application. **Out:** `site.json`.\n\n> **Not WordPress, not Ecommerce.** Never provision `headless_wordpress` / `headless_ecommerce` for a\n> webapp, and never install WordPress at `/cms`. A webapp owns its own routes, data and rendering.\n\nAll website/database operations below go through the Hostinger connector (`execute`).\n\n> **Fire vs join (background provision).** Steps 1–2 up to `hosting_websites_start-setup` are the\n> **fire** half: once the setup is queued, record `provision.in_progress` and return to do prepare work\n> (see the router `../SKILL.md`). The readiness **poll** at the end of Step 2, plus Steps 3–5, are the\n> **join** half — run them when an instance-barrier step needs the backend (or immediately, if the\n> agent can't interleave). An already-`completed` site detected in Step 1 means provision is done:\n> record `provision.completed` and adopt it.\n\n## Step 1 — Detect before you create (never provision twice)\n\n1. `hosting_websites_list { \"domain\": \"<domain>\" }` — substring filter, so **exact-match** the row's\n `domain`. If a row exists, note `username`, `order_id`, `website_type`.\n2. `hosting_websites_list-setups { \"domain\": \"<domain>\" }` — exact-match the row:\n - `completed` → already provisioned → skip to Step 3 (adopt it).\n - `running` → skip to Step 2 (poll).\n - `failed` → tell the user and point them at hPanel; do **not** call start-setup again on your own.\n3. If the site exists but is a different kind (`website_type` is `headless`, or a WordPress/store site),\n stop and tell the user — this domain already hosts something else.\n\n## Step 2 — Create (only when nothing exists)\n\n1. Pick the plan: `hosting_orders_list { \"statuses\": [\"active\"] }`. One active order → use it; several\n → ask the user which.\n2. Start the setup as an **empty website** — **omit `type`** (no WordPress, no store):\n\n ```json\n { \"operation\": \"hosting_websites_start-setup\",\n \"params\": { \"order_id\": <order_id>, \"domain\": \"<domain>\" } }\n ```\n\n `domain` must be a **customer-owned** domain. For a temporary `*.hostingersite.com` subdomain,\n **omit `domain`** — the setup generates one itself and returns it in the response. Don't call a\n separate subdomain op to make one first: there's no need in this flow. (The API would also accept a\n subdomain you generated via `hosting_domains_generate-free-subdomain`, valid for 24 h from the client\n that made it — this flow just doesn't use that.) The response then carries the generated domain: if it\n differs from the workspace directory name, move `~/.hostinger/<old>/` to `~/.hostinger/<new>/`\n (if `<new>/` already exists, stop and ask instead of moving into it),\n update the domain in `brief.md`, and tell the user the new domain.\n3. Poll `hosting_websites_list-setups { \"domain\": \"<domain>\" }` every ~15 s until `completed`\n (typically under a minute; allow a few minutes). `failed` → stop, point at hPanel.\n\n## Step 3 — Database (only if the app needs one)\n\nSkip this step entirely for an app with no database (static SPA, an app that talks to an external API).\nThe app needs one when `brief.md` → **Existing project** says **Database: MySQL**, or, for a new app,\nwhen the brief describes data the app stores itself.\nThis is **join-half** work — it needs the live website, so run it once `provision.status == \"completed\"`.\n\n**Preferred — let the platform create the database and inject the credentials:**\n`hosting_databases_setup-website { username, domain }` creates a MySQL database, **generates the\npassword, writes `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USER` / `DB_PASSWORD` / `DATABASE_URL` into the\nwebsite's environment variables, and restarts the app — in one call.** The password is never returned,\nso there is **nothing to persist and nothing to lose across sessions** — the app reads it from the\nenvironment. (It 422s if any of those env vars already exist; clear them first with\n`hosting_nodejs_replace-environment-variables`. Frameworks that bake vars into the build — Next.js,\n`NEXT_PUBLIC_*` — only pick up the values on the deploy build.)\n\n> **⚠️ A later `hosting_nodejs_replace-environment-variables` wipes these credentials.** That call is a\n> **full replace**, and when you list the current vars first the `DB_PASSWORD` (and `DATABASE_URL`) read\n> back **masked** (`********`) — the generated password is unknown, so you cannot resend it and the DB\n> env is lost. One rule, pick one:\n> - **(a) Set every other app env var first, and call `setup-website` last** — then nothing has to\n> replace the env again.\n> - **(b) If a later replace is unavoidable**, reset the password with\n> `hosting_databases_change-password { username, name, password }` and resend **all** of `DB_HOST`,\n> `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` with the new password in that same\n> full replace.\n\n**Manual alternative** — only when you need a specific name/user or finer control:\n\n1. `hosting_databases_create` with a name, a user, and a password you generate — name and user are\n prefixed with the account username automatically. Read the full, prefixed `name`/`user` back from\n `hosting_databases_list`.\n2. **Which host the app connects to:** the app runs **on the website**, so Node.js connects to\n **`127.0.0.1:3306`** — never `localhost` (it can resolve to IPv6 `::1`, and not every database user\n has that grant), and never the external `host` from `hosting_databases_list` (`srvNNNN.hstgr.io`,\n which has no grant for connections from the server itself).\n3. **Set the env vars now, while you still hold the generated password.** The password is **not persisted\n anywhere** (never in `site.json`), so set `DB_HOST=127.0.0.1`, `DB_PORT=3306`, `DB_NAME`, `DB_USER`,\n `DB_PASSWORD`, plus `DATABASE_URL=mysql://USER:PASSWORD@127.0.0.1:3306/NAME` (URL-encode USER/PASSWORD)\n right here with `hosting_nodejs_replace-environment-variables` (a **full replace** — list current keys\n first; values come back masked; send the complete set with real values). Saving restarts the process;\n build-time vars need a fresh deploy (`hosting_deploy`) afterwards. **Don't defer this to deploy:** on\n a **cross-session resume** the generated password is gone, so you must **reset it** with\n `hosting_databases_change-password { username, name, password }` and set the env vars from the new one.\n4. **Never** put the password in source, a committed `.env`, client-side JavaScript, or a chat reply;\n never print it once stored.\n\n## Step 4 — Verify\n\n| Check | How | Expect |\n|---|---|---|\n| Website exists | `hosting_websites_list` exact row | the row is present, `username` known |\n| Setup finished | `hosting_websites_list-setups` exact row | `status: completed` |\n| Database reachable *(only if created)* | `hosting_databases_list { username }` | the prefixed db + user are listed |\n\n`GET https://<domain>/` returns the Hostinger default page (or 403) until the frontend is deployed —\nexpected, not a failure. There is **no `/cms` and no API** on this domain; the app itself serves every\nroute once it is deployed (Step 8).\n\nIf a check fails: retry once after ~30 s; if it still fails, stop and point the user at hPanel.\n\n## Step 5 — Record `site.json`\n\n```json\n{\n \"backend\": \"webapp\",\n \"website_type\": \"empty\",\n \"order_id\": 1007223428,\n \"username\": \"u…\",\n \"runtime\": \"nodejs\",\n \"database\": {\n \"enabled\": false,\n \"engine\": \"mysql\",\n \"name\": \"u…_appdb\",\n \"user\": \"u…_app\",\n \"host\": \"127.0.0.1\",\n \"port\": 3306\n },\n \"urls\": {\n \"site\": \"https://<domain>\"\n },\n \"provisioned_at\": \"…\",\n \"verified_at\": \"…\"\n}\n```\n\nNo passwords or tokens in `site.json` — only the non-secret connection shape (host/port/name/user). Set\n`\"database\": { \"enabled\": false }` (and drop the rest) when the app needs no database.\n\n## Deploy & content model (what happens next)\n\n- **Content model is skipped for a webapp** — there is no CMS or store to seed; the app owns its own\n data. The content-model router (`../../04-content-model/SKILL.md`) passes straight through to the\n frontend step for this backend.\n- **Deploy** is a standard Node.js application deploy, handled by `../../08-deploy/SKILL.md`: it archives\n the **source** (not the build output; exclude `node_modules/`, `.next/`, `dist/`, build output and\n `.git/`), then deploys with `hosting_deploy` (`type: \"nodejs\"`, upload + server-side `npm install` +\n build, one shot) passing the framework's `appType` + `outputDirectory` inline, and polls\n `hosting_get-deployment` until the build completes. Any supported `appType` works (Next.js, Nuxt,\n Express, NestJS, Fastify, Vite/React/Vue SPA, …). There is no `/cms` to preserve here, so the whole\n root is the app.\n"
169
169
  },
170
170
  {
171
171
  "uri": "skill://hostinger-headless/steps/03-provision/wordpress/SKILL.md",