hostinger-api-mcp 3.27.3 → 3.28.0
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 +49 -21
- package/skills/headless/VERSION +3 -3
- package/skills/headless/run-log.mjs +21 -1
- package/skills/headless/steps/01-check-state/SKILL.md +12 -13
- package/skills/headless/steps/03-provision/SKILL.md +11 -10
- package/skills/headless/steps/03-provision/ecommerce/SKILL.md +5 -2
- package/skills/headless/steps/03-provision/webapp/SKILL.md +3 -2
- package/skills/headless/steps/03-provision/wordpress/SKILL.md +6 -7
- package/skills/headless/steps/04-content-model/SKILL.md +39 -21
- package/skills/headless/steps/04-content-model/wordpress/SKILL.md +1 -2
- package/skills/headless/steps/07-validate/SKILL.md +3 -3
- package/skills/headless/steps/08-deploy/SKILL.md +27 -14
- package/skills/headless/steps/09-done/SKILL.md +3 -2
- package/skills/headless/wait.mjs +245 -0
- package/src/core/skills.js +43 -36
- package/src/core/skills.ts +43 -36
- 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/gpu.js +1 -1
- package/src/servers/gpu.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.28.0 — 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Dependencies: `package-lock.json`
|
|
12
|
+
- Bundled agent skills: 14 files
|
|
13
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
14
|
+
|
|
15
|
+
## v3.27.4 — 2026-11-03
|
|
16
|
+
|
|
17
|
+
### Also in this release
|
|
18
|
+
|
|
19
|
+
- Bundled agent skills: `skills/headless/VERSION`
|
|
20
|
+
|
|
7
21
|
## v3.27.3 — 2026-11-03
|
|
8
22
|
|
|
9
23
|
### Also in this release
|
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.28.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "3.
|
|
14
|
+
"version": "3.28.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/skills/headless/SKILL.md
CHANGED
|
@@ -58,7 +58,7 @@ The order is sequencial run and check the whole headless flow:
|
|
|
58
58
|
| — | **connect** — connect the Hostinger connector, auth, get active plan | `connect/SKILL.md` → `connect/tools/<tool>.md` | `connect.json` | no |
|
|
59
59
|
| 1 | **check state** — where does this domain stand? resume / switch / start over | `steps/01-check-state/SKILL.md` | `state.json` | no |
|
|
60
60
|
| 2 | **refine** — discover what the user wants, in conversation | `steps/02-refine/SKILL.md` | `brief.md` | no |
|
|
61
|
-
| 3 | **provision** — choose the backend from the brief, then provision it | `steps/03-provision/<backend>/SKILL.md` | `site.json` | **bg** — fire, then
|
|
61
|
+
| 3 | **provision** — choose the backend from the brief, then provision it | `steps/03-provision/<backend>/SKILL.md` | `site.json` | **bg** — fire, then wait at the join |
|
|
62
62
|
| 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** |
|
|
63
63
|
| 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 |
|
|
64
64
|
| 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** |
|
|
@@ -89,7 +89,7 @@ the user rather than improvising the stage from its description.
|
|
|
89
89
|
The **Instance?** column says whether a stage touches the live backend:
|
|
90
90
|
`no` = pure prepare work that can run during the provision wait;
|
|
91
91
|
`push`/`data`/`yes` = a commit phase that joins the instance barrier;
|
|
92
|
-
`bg` = provision itself
|
|
92
|
+
`bg` = provision itself: Hostinger runs the job while you do the prepare work.
|
|
93
93
|
|
|
94
94
|
**Always end with the final report.** Whenever a run ends, give the user the final report defined
|
|
95
95
|
in `steps/09-done/SKILL.md`: an outcome paragraph, a *Steps completed* table (one row per stage that
|
|
@@ -99,8 +99,8 @@ missing step, a failed provision, or a user pause. Every run also saves a **buil
|
|
|
99
99
|
Waiting for the user's answer to a question is not a run ending: end your turn with the question
|
|
100
100
|
alone, with no final report and no build report.
|
|
101
101
|
|
|
102
|
-
**Log the run as you go.** Wrap every stage (and each phase of a split stage) in
|
|
103
|
-
`run-log.mjs stage
|
|
102
|
+
**Log the run as you go.** Wrap every stage (and each phase of a split stage) in the run log —
|
|
103
|
+
`run-log.mjs next <stage>` at each boundary — and log an event the moment something goes wrong, the user
|
|
104
104
|
corrects you, or a skill is missing or wrong — before you continue. See *Run log and build report*.
|
|
105
105
|
|
|
106
106
|
**Why this order.** connect first, so account/plan failures surface before the user spends time
|
|
@@ -178,17 +178,37 @@ which of these cases applies and records it, then provision takes the matching p
|
|
|
178
178
|
3. **User wants a webapp.** Provision is a plain Node.js app host, not headless — `webapp` ignores
|
|
179
179
|
headless shells entirely and is only reached by experimental actions, never the onboarding flow.
|
|
180
180
|
|
|
181
|
-
Either way the create/install is asynchronous and returns `queued` immediately, then it
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
`wordpress_installations_list`
|
|
186
|
-
single rule that makes the overlap safe:
|
|
181
|
+
Either way the create/install is asynchronous and returns `queued` immediately, then it takes a few
|
|
182
|
+
minutes — **what to wait on depends on the path**: a fresh `hosting_websites_create` is `wait.mjs setup`
|
|
183
|
+
(until `hosting_websites_list-setups` says `completed`), while a shell swap
|
|
184
|
+
(`wordpress_installations_install`) creates no new setup, so it's `wait.mjs cms` (until the `/cms` row of
|
|
185
|
+
`wordpress_installations_list` is valid). Run it at the join, not at the fire: by then the prepare work
|
|
186
|
+
has usually outlasted the job, so it returns at once. The single rule that makes the overlap safe:
|
|
187
187
|
|
|
188
|
-
> **Fire provision
|
|
188
|
+
> **Fire provision (Hostinger runs it asynchronously), then do every stage's prepare work while it settles. The
|
|
189
189
|
> moment a step must write to the live instance, it waits for provision to report ready (via the
|
|
190
190
|
> backend step's readiness poll — see above). That wait is the *instance barrier*.**
|
|
191
191
|
|
|
192
|
+
### Waiting on Hostinger: `wait.mjs`
|
|
193
|
+
|
|
194
|
+
`wait.mjs` (next to this file) waits for a slow Hostinger job in one command, so you don't spend a turn
|
|
195
|
+
on every check:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
node <skill>/wait.mjs setup <domain> # a fresh site's setup → completed
|
|
199
|
+
node <skill>/wait.mjs cms <domain> <username> # a shell swap's WordPress install at /cms → valid
|
|
200
|
+
node <skill>/wait.mjs build <domain> <buildUuid> # the deploy build → completed, then the root serves 200
|
|
201
|
+
node <skill>/wait.mjs live <domain> # the root serves 200
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Run it in the foreground, with a `--timeout` just under your tool's command limit (e.g. `--timeout 9m`
|
|
205
|
+
when commands may run 10 minutes); on `timed_out`, run the same command again: it picks the job up
|
|
206
|
+
where it is. It prints one JSON line and
|
|
207
|
+
exits 0 with `ok: true` and what it waited for; 1 with `ok: false` when the job failed, timed out
|
|
208
|
+
(`timed_out`, `last_status`) or hit an error that waiting won't fix (`error`); 2 when it couldn't start
|
|
209
|
+
the local Hostinger MCP, with `fallback` naming the connector operation to poll yourself, every ~15 s.
|
|
210
|
+
Never write your own `sleep` loops.
|
|
211
|
+
|
|
192
212
|
---
|
|
193
213
|
|
|
194
214
|
## State and artifacts
|
|
@@ -243,14 +263,15 @@ provision runs in the background, its status lives in its own `provision` field
|
|
|
243
263
|
}
|
|
244
264
|
```
|
|
245
265
|
|
|
246
|
-
- `provision` is **absent** until provision fires, `{"status":"in_progress", …}`
|
|
247
|
-
|
|
248
|
-
|
|
266
|
+
- `provision` is **absent** until provision fires, `{"status":"in_progress", …}` from the fire until
|
|
267
|
+
the join's `wait.mjs` returns `ok: true`, then `{"status":"completed", …}`. `domain` is the wait's
|
|
268
|
+
target.
|
|
249
269
|
- Any **instance-barrier** step (content-model push, frontend data, deploy) must read
|
|
250
|
-
`provision.status == "completed"`
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
270
|
+
`provision.status == "completed"` before it runs. The connector is asked twice per run: at
|
|
271
|
+
check-state (or the provision join that sets `completed`), and in deploy's pre-flight, because deploy
|
|
272
|
+
overwrites the site. Steps in between trust `state.json`. A prepare step ignores `provision` entirely.
|
|
273
|
+
- On **resume**, `provision.status` is a hint only: check-state re-polls `hosting_websites_list-setups`
|
|
274
|
+
to learn the truth, then updates the field.
|
|
254
275
|
- Write `state.json` however is easiest, rewriting the whole file included: the run log lives in
|
|
255
276
|
`run-log.json`, which only `run-log.mjs` writes.
|
|
256
277
|
- The **onboarding slug** is how hPanel names the coding tool: `claude` for Claude Code, else
|
|
@@ -269,9 +290,10 @@ in. Write it out in full each time (a shell variable holding the command breaks
|
|
|
269
290
|
```bash
|
|
270
291
|
node <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version> # check-state, first thing
|
|
271
292
|
node <skill>/run-log.mjs <domain> run-update --kind resume # check-state, once the route is agreed (or redesign / iterate)
|
|
272
|
-
node <skill>/run-log.mjs <domain>
|
|
273
|
-
node <skill>/run-log.mjs <domain>
|
|
274
|
-
node <skill>/run-log.mjs <domain> stage-
|
|
293
|
+
node <skill>/run-log.mjs <domain> next frontend # at a stage boundary: ends the open stage as ok, starts this one
|
|
294
|
+
node <skill>/run-log.mjs <domain> next frontend --phase shell # split stages log each phase
|
|
295
|
+
node <skill>/run-log.mjs <domain> stage-end frontend ok_after_retries --note "second framework tried" # any other result, or a note
|
|
296
|
+
node <skill>/run-log.mjs <domain> stage-start frontend # start without ending anything
|
|
275
297
|
node <skill>/run-log.mjs <domain> event issue '{"stage":"content_model","symptom":"…","cause":"…","workaround":"…","severity":"medium","resolved":true}'
|
|
276
298
|
node <skill>/run-log.mjs <domain> report --outcome live # done, or wherever the run stops
|
|
277
299
|
node <skill>/run-log.mjs <domain> report-sent <run> # after the report was sent (see below)
|
|
@@ -280,6 +302,12 @@ node <skill>/run-log.mjs <domain> unsent # check-state:
|
|
|
280
302
|
node <skill>/run-log.mjs <domain> run-resume --reason "about/contact layout fix" # follow-up in the same session
|
|
281
303
|
```
|
|
282
304
|
|
|
305
|
+
**Don't spend a turn on the log.** Put each run-log call in the same shell command as the work next
|
|
306
|
+
to it, joined with `&&` (e.g. `node <skill>/run-log.mjs <domain> next deploy && cd … && zip …`), and use
|
|
307
|
+
`next` at a boundary rather than a `stage-end` plus a `stage-start`. When the stage ending wasn't plain
|
|
308
|
+
`ok` or needs a note, put its `stage-end` first in the same command. `next` refuses when two stages are
|
|
309
|
+
open and names them; end them with `stage-end`.
|
|
310
|
+
|
|
283
311
|
**Follow-ups continue the run.** When the user asks for a change after the final report in the
|
|
284
312
|
same session (a layout fix, a copy tweak, a redeploy), that work belongs to the run it follows:
|
|
285
313
|
call `run-resume`, log the feedback and the stages it re-runs as usual, then `report` again. That
|
package/skills/headless/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
version=3.
|
|
2
|
-
commit=
|
|
3
|
-
built=2026-11-
|
|
1
|
+
version=3.28.0
|
|
2
|
+
commit=0b30a7c65edb
|
|
3
|
+
built=2026-11-04
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// node run-log.mjs <domain> run-resume [--reason "<text>"]
|
|
7
7
|
// node run-log.mjs <domain> stage-start <stage> [--phase <phase>] [--started-ago 6m | --started-at <ISO time>]
|
|
8
8
|
// node run-log.mjs <domain> stage-end <stage> <result> [--phase <phase>] [--note "<text>"] [--started-ago 6m | --started-at <ISO time>]
|
|
9
|
+
// node run-log.mjs <domain> next <stage> [--phase <phase>]
|
|
9
10
|
// node run-log.mjs <domain> stage-fix <stage> [--phase <phase>] (--started-ago 6m | --started-at <ISO time>)
|
|
10
11
|
// node run-log.mjs <domain> event <issue|feedback|skill_gap|good> '<json>'
|
|
11
12
|
// node run-log.mjs <domain> report [--outcome live|stopped|failed|abandoned] [--tokens '<json>']
|
|
@@ -239,6 +240,22 @@ function stageEnd(ws, [stage, result], flags) {
|
|
|
239
240
|
console.log(`${stage}${phase ? `/${phase}` : ''} ended: ${result}`);
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
// One call at a stage boundary: ends the open stage (or phase) as ok and starts <stage>, unless the open
|
|
244
|
+
// one is <stage> itself, so re-running a chained `next X && …` doesn't close X as ok. With nothing open
|
|
245
|
+
// it only starts; with more than one open it can't tell which ends, so it refuses.
|
|
246
|
+
function next(ws, [stage], flags) {
|
|
247
|
+
if (!STAGES.includes(stage)) fail(`unknown stage "${stage}"; one of ${STAGES.join(', ')}`);
|
|
248
|
+
const log = loadLog(ws);
|
|
249
|
+
const run = currentRun(log);
|
|
250
|
+
const open = (log.history ?? []).filter((h) => h.run === run.id && !h.ended_at);
|
|
251
|
+
const label = (h) => `${h.stage}${h.phase ? ` --phase ${h.phase}` : ''}`;
|
|
252
|
+
if (open.length > 1) fail(`${open.map(label).join(' and ')} are open; end each with stage-end <stage> <result>, then call next`);
|
|
253
|
+
const phase = flags.phase ?? null;
|
|
254
|
+
const [current] = open;
|
|
255
|
+
if (current && !(current.stage === stage && current.phase === phase)) stageEnd(ws, [current.stage, 'ok'], { phase: current.phase ?? undefined });
|
|
256
|
+
stageStart(ws, [stage], { phase: flags.phase });
|
|
257
|
+
}
|
|
258
|
+
|
|
242
259
|
function stageFix(ws, [stage], flags) {
|
|
243
260
|
if (!STAGES.includes(stage)) fail(`unknown stage "${stage}"`);
|
|
244
261
|
const log = loadLog(ws);
|
|
@@ -663,6 +680,9 @@ switch (command) {
|
|
|
663
680
|
case 'stage-end':
|
|
664
681
|
stageEnd(ws, rest, flags);
|
|
665
682
|
break;
|
|
683
|
+
case 'next':
|
|
684
|
+
next(ws, rest, flags);
|
|
685
|
+
break;
|
|
666
686
|
case 'stage-fix':
|
|
667
687
|
stageFix(ws, rest, flags);
|
|
668
688
|
break;
|
|
@@ -696,5 +716,5 @@ switch (command) {
|
|
|
696
716
|
reportRejected(ws, rest);
|
|
697
717
|
break;
|
|
698
718
|
default:
|
|
699
|
-
fail('command must be run-start, run-update, run-resume, stage-start, stage-end, stage-fix, event, report, unsent, report-sent or report-rejected');
|
|
719
|
+
fail('command must be run-start, run-update, run-resume, stage-start, stage-end, next, stage-fix, event, report, unsent, report-sent or report-rejected');
|
|
700
720
|
}
|
|
@@ -32,8 +32,9 @@ present only once provision has fired):
|
|
|
32
32
|
"updated_at": "…" }
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`provision.status` is `in_progress` or `completed` — but it is a **hint
|
|
36
|
-
`hosting_websites_list-setups` (Step 1)
|
|
35
|
+
`provision.status` is `in_progress` or `completed` — but it is a **hint** from an earlier session:
|
|
36
|
+
re-verify it against `hosting_websites_list-setups` here (Step 1). See *State and artifacts* in the
|
|
37
|
+
top-level `SKILL.md` for when the connector is asked again.
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
@@ -43,18 +44,14 @@ Before anything else, start a run and the stage (see *Run log and build report*
|
|
|
43
44
|
`SKILL.md`):
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
|
-
node <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>
|
|
47
|
-
node <skill>/run-log.mjs <domain>
|
|
47
|
+
node <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version> && \
|
|
48
|
+
node <skill>/run-log.mjs <domain> next check_state && \
|
|
49
|
+
node <skill>/run-log.mjs <domain> unsent
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
If an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
failed last time):
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
node <skill>/run-log.mjs <domain> unsent
|
|
57
|
-
```
|
|
53
|
+
`unsent` lists the build reports that haven't reached Hostinger yet (that abandoned run, or a send
|
|
54
|
+
that failed last time).
|
|
58
55
|
|
|
59
56
|
Each line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`
|
|
60
57
|
and record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One
|
|
@@ -70,7 +67,8 @@ Keep two things separate, and **never guess from a timestamp.**
|
|
|
70
67
|
`hosting_websites_list-setups { "domain": "<domain>" }` and exact-match the domain row:
|
|
71
68
|
- `status: running` → provisioning is underway **right now** (a background provision from an earlier
|
|
72
69
|
turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and
|
|
73
|
-
either resume prepare work while it finishes or
|
|
70
|
+
either resume prepare work while it finishes or wait for it at the barrier with
|
|
71
|
+
`wait.mjs setup <domain>` (see *Waiting on Hostinger* in the top-level `SKILL.md`).
|
|
74
72
|
- `status: failed` → not running; route to provision to self-heal (Step 3).
|
|
75
73
|
- `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps
|
|
76
74
|
(content push, frontend data, deploy) are now unblocked.
|
|
@@ -200,7 +198,8 @@ When the intent is ambiguous, **ask** — don't guess. A skipped tool result is
|
|
|
200
198
|
|
|
201
199
|
Write `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the
|
|
202
200
|
route isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or
|
|
203
|
-
`redesign` / `iterate`)
|
|
201
|
+
`redesign` / `iterate`) and end the stage with the route as its note, in one shell command:
|
|
202
|
+
`… run-update --kind resume && … stage-end check_state ok --note "<the route>"`.
|
|
204
203
|
Then:
|
|
205
204
|
|
|
206
205
|
- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still
|
|
@@ -10,9 +10,9 @@ matching subfolder, which provisions it.
|
|
|
10
10
|
|
|
11
11
|
**In:** `brief.md`. **Out:** `site.json` (written by the subfolder).
|
|
12
12
|
|
|
13
|
-
**This stage
|
|
14
|
-
**fire** half (detect + create, returns straight away) and a **join** half (
|
|
15
|
-
verify, write `site.json`). Fire it, record `provision.in_progress`, then go do the prepare work of
|
|
13
|
+
**This stage is asynchronous.** Provisioning is mostly a wait on Hostinger, so it splits in two: a
|
|
14
|
+
**fire** half (detect + create, returns straight away) and a **join** half (`wait.mjs` in the
|
|
15
|
+
foreground, verify, write `site.json`). Fire it, record `provision.in_progress`, then go do the prepare work of
|
|
16
16
|
the other stages (content-model prepare, frontend shell) while it settles. Only cross back to
|
|
17
17
|
the join half when an instance-barrier step needs the live backend. See *Background provision & the
|
|
18
18
|
instance barrier* in the top-level `SKILL.md`. An agent that can't interleave simply does fire and
|
|
@@ -98,7 +98,7 @@ So provision has **two entry shapes**, and each subfolder's *detect* step picks
|
|
|
98
98
|
you not to expect an empty shell.
|
|
99
99
|
|
|
100
100
|
This only changes *detect*. Everything after the swap (verify, `site.json`, the WooCommerce tail, the
|
|
101
|
-
|
|
101
|
+
fire/join split) is identical to a fresh provision. A swap that installs WordPress is still
|
|
102
102
|
asynchronous — fire it, record `provision.in_progress`, and join at the barrier just like a fresh create.
|
|
103
103
|
|
|
104
104
|
## Step 2 — Fire (don't block)
|
|
@@ -118,8 +118,8 @@ asynchronous — fire it, record `provision.in_progress`, and join at the barrie
|
|
|
118
118
|
> shell from an already-provisioned backend.
|
|
119
119
|
|
|
120
120
|
Follow the chosen subfolder's `SKILL.md` through **detect + create** (its `hosting_websites_create` call). The
|
|
121
|
-
moment the create is accepted and queued, record it in `state.json` and **return** — do not
|
|
122
|
-
|
|
121
|
+
moment the create is accepted and queued, record it in `state.json` and **return** — do not wait for
|
|
122
|
+
it yet:
|
|
123
123
|
|
|
124
124
|
```json
|
|
125
125
|
{ "stage": "content_model",
|
|
@@ -132,7 +132,8 @@ shell). The subfolder's readiness poll, verification and `site.json` are the **j
|
|
|
132
132
|
## Step 3 — Join (at the instance barrier)
|
|
133
133
|
|
|
134
134
|
When an instance-barrier step is about to run (content-model push, frontend data wiring, deploy) — or
|
|
135
|
-
right away, if the agent can't interleave — finish the subfolder's `SKILL.md`:
|
|
136
|
-
run its verification, and write `site.json` (which
|
|
137
|
-
`provision.status` to `"completed"` in `state.json` with a
|
|
138
|
-
|
|
135
|
+
right away, if the agent can't interleave — finish the subfolder's `SKILL.md`: run its `wait.mjs` (it
|
|
136
|
+
returns at once if the job is already done), run its verification, and write `site.json` (which
|
|
137
|
+
records the chosen `backend`). Then set `provision.status` to `"completed"` in `state.json` with a
|
|
138
|
+
fresh `updated_at`.
|
|
139
|
+
|
|
@@ -48,14 +48,17 @@ All website-lifecycle calls go through the `hosting` connector (`execute`); all
|
|
|
48
48
|
Same lifecycle as `../wordpress/SKILL.md` Steps 1–2, but `type: "headless_ecommerce"`:
|
|
49
49
|
|
|
50
50
|
1. Detect: `hosting_websites_list` / `hosting_websites_list-setups` (exact-match the domain). `completed`
|
|
51
|
-
→ adopt. `running` →
|
|
51
|
+
→ adopt. `running` → record `provision.in_progress` and wait at the join. `failed` → stop, point at hPanel.
|
|
52
|
+
If `website_type` is not `headless`
|
|
52
53
|
(plain WordPress, Node.js, …), stop and tell the user — don't overwrite another kind of site.
|
|
53
54
|
2. Create (only if **no** site exists): `hosting_websites_create { order_id, type: "headless_ecommerce", domain }`
|
|
54
55
|
— **omit `domain`** for a temporary subdomain: the setup generates one itself and returns it (don't
|
|
55
56
|
call a separate subdomain op first — there's no need in this flow). The response carries the
|
|
56
57
|
generated domain; if it differs from the workspace dir, move
|
|
57
58
|
`~/.hostinger/<old>/` → `<new>/` (if `<new>/` already exists, stop and ask instead of moving
|
|
58
|
-
into it) and update `brief.md`.
|
|
59
|
+
into it) and update `brief.md`. Don't wait here. At the join, `node <skill>/wait.mjs setup <domain>`
|
|
60
|
+
returns once the setup is `completed` (see *Waiting on Hostinger* in the top-level `SKILL.md`).
|
|
61
|
+
`website_type` will be `headless`.
|
|
59
62
|
|
|
60
63
|
> **Onboarding handoff — an empty headless shell needs no new website.** If the domain already has an
|
|
61
64
|
> empty headless shell (`website_type: headless`, setup `type: headless_empty`, nothing installed —
|
|
@@ -57,8 +57,9 @@ All website/database operations below go through the Hostinger connector (`execu
|
|
|
57
57
|
`~/.hostinger/<new>/`
|
|
58
58
|
(if `<new>/` already exists, stop and ask instead of moving into it),
|
|
59
59
|
update the domain in `brief.md`, and tell the user the new domain.
|
|
60
|
-
3.
|
|
61
|
-
|
|
60
|
+
3. Don't wait here. At the join, `node <skill>/wait.mjs setup <domain>` (see *Waiting on Hostinger* in
|
|
61
|
+
the top-level `SKILL.md`) returns once the setup is `completed`, usually at once by then. A `failed`
|
|
62
|
+
setup → stop, point at hPanel.
|
|
62
63
|
|
|
63
64
|
## Step 3 — Database (only if the app needs one)
|
|
64
65
|
|
|
@@ -64,8 +64,9 @@ All operations below go through the Hostinger connector (`execute`).
|
|
|
64
64
|
(if `<new>/` already exists, stop and ask instead of moving into it),
|
|
65
65
|
update the domain in `brief.md`, and tell the user the new domain.
|
|
66
66
|
Admin credentials are generated by the platform; never ask the user for a password.
|
|
67
|
-
3.
|
|
68
|
-
|
|
67
|
+
3. Don't wait here. At the join, `node <skill>/wait.mjs setup <domain>` (see *Waiting on Hostinger* in
|
|
68
|
+
the top-level `SKILL.md`) returns once the setup is `completed`, usually at once by then. A `failed`
|
|
69
|
+
setup → stop, point at hPanel.
|
|
69
70
|
|
|
70
71
|
## Step 2b — Swap an empty headless shell to WordPress (only when onboarding already made the shell)
|
|
71
72
|
|
|
@@ -93,11 +94,9 @@ install WordPress straight into its `/cms` directory. This is the onboarding han
|
|
|
93
94
|
- A new database is created for the install unless you pass `database.name` of an existing one; the
|
|
94
95
|
generated database counts against the plan's limit.
|
|
95
96
|
3. The call is **asynchronous** — a success response only means the install job was **queued**. This is
|
|
96
|
-
the *fire*: record `provision.in_progress` and return to prepare work.
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
`wordpress_installations_list` can lag a few seconds behind the job finishing and return `[]` — retry
|
|
100
|
-
briefly before failing.
|
|
97
|
+
the *fire*: record `provision.in_progress` and return to prepare work. At the join,
|
|
98
|
+
`node <skill>/wait.mjs cms <domain> <username>` returns the `/cms` install row (`directory: "cms"`,
|
|
99
|
+
valid) once it exists, usually within 1–2 minutes of the install.
|
|
101
100
|
|
|
102
101
|
## Step 3 — Verify (all must pass)
|
|
103
102
|
|
|
@@ -96,7 +96,7 @@ not an answer, see *When you ask the user* in the top-level `SKILL.md`):
|
|
|
96
96
|
| Option | Tool | Backends | Needs the live instance? |
|
|
97
97
|
|---|---|---|---|
|
|
98
98
|
| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |
|
|
99
|
-
| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API, or web
|
|
99
|
+
| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API, or web fetch of Unsplash search pages | any | no — pure prepare work |
|
|
100
100
|
|
|
101
101
|
> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is
|
|
102
102
|
> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build
|
|
@@ -165,29 +165,47 @@ runs **at the instance barrier** (after `provision.status == "completed"`), usin
|
|
|
165
165
|
This is still prepare work and needs no live backend: you pick the photos and record their URLs here,
|
|
166
166
|
and the push phase uploads them.
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
168
|
+
Source every image in a few batched calls, not a search and a fetch per photo:
|
|
169
|
+
|
|
170
|
+
1. **Find candidates, a page of them per fetch.** Group the image briefs into a few themes (e.g.
|
|
171
|
+
"sourdough bread", "bakery interior", "Lisbon street").
|
|
172
|
+
- With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API, one call per theme:
|
|
173
|
+
`GET https://api.unsplash.com/search/photos?query=<theme>&orientation=landscape&per_page=20` with
|
|
174
|
+
header `Authorization: Client-ID <key>`. Each result's `urls.raw` is the file, `alt_description`
|
|
175
|
+
what it shows and `user.name` the photographer; for each photo you keep, also `GET` its
|
|
176
|
+
`links.download_location` (same header), as the API terms require.
|
|
177
|
+
- Without a key, open the theme's search page `https://unsplash.com/s/photos/<theme-with-dashes>`
|
|
178
|
+
with your **web-fetch tool**, once per theme, and ask it to list every free photo on the page with
|
|
179
|
+
its `images.unsplash.com/photo-<…>` URL quoted exactly (the part before `?`), the photo page
|
|
180
|
+
link, the alt text and the photographer. One fetch returns 10–20 candidates. Skip the
|
|
181
|
+
`plus.unsplash.com/premium_photo-…` results: they are Unsplash+ and not free. Fetch the themes in
|
|
182
|
+
parallel when your tool allows several calls at once. Fall back to a web search limited to
|
|
183
|
+
`unsplash.com` plus a fetch of one photo page (`https://unsplash.com/photos/<…>`) only for a
|
|
184
|
+
subject no search page covers.
|
|
185
|
+
- Every other `unsplash.com` request (curl on any page, `/photos/<id>/download`, `/napi/…`,
|
|
183
186
|
`/oembed`) hits a bot challenge: a `307` to `/.within.website` (Anubis) or a 401. If your fetch
|
|
184
187
|
tool gets the challenge page too, stop: no user-agent spoofing or other workarounds. On a
|
|
185
188
|
WordPress backend where Option A hasn't already failed, offer it; otherwise ask the user for an
|
|
186
189
|
`UNSPLASH_ACCESS_KEY` or record the gap (step 5).
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
2. **Check them all in one command.** Pick a candidate per image brief from the alt text, then in one
|
|
191
|
+
shell command request each web-size URL
|
|
192
|
+
(`https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`, query string replaced) and
|
|
193
|
+
confirm 200 `image/jpeg`, and save a small preview (`?w=500`) of each into one folder, e.g.:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
mkdir -p ~/.hostinger/<domain>/media/previews && cd ~/.hostinger/<domain>/media/previews && \
|
|
197
|
+
for id in photo-<a> photo-<b>; do
|
|
198
|
+
curl -s -o /dev/null -w "$id %{http_code} %{content_type}\n" "https://images.unsplash.com/$id?w=1600&q=80&fm=jpg&fit=max"
|
|
199
|
+
curl -s -o "$id.jpg" "https://images.unsplash.com/$id?w=500&q=60&fm=jpg&fit=max"
|
|
200
|
+
done
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The upload ability needs this direct file URL, not the photo page.
|
|
204
|
+
3. **Look before you keep it.** View the previews, all in one turn where your tool can open several
|
|
205
|
+
images at once. Alt text from a search page describes the photo better than a search snippet, but
|
|
206
|
+
a photo often isn't quite what its title says. Reject anything off-topic and take the next
|
|
207
|
+
candidate from the same list. If nothing fits exactly, take the closest real photo and note the gap
|
|
208
|
+
rather than mislabel it.
|
|
191
209
|
4. **Write alt text from what the photo shows**, not from the article's image brief. For example,
|
|
192
210
|
use "Shopfronts and parked scooters on a Hanoi street" even if the article is about rain.
|
|
193
211
|
5. **Record it** under each article in `content-model.md`:
|
|
@@ -212,7 +230,7 @@ Then update `state.json`: set `stage` to the next prepare stage still to do, fre
|
|
|
212
230
|
|
|
213
231
|
## Push phase — create it the backend's way (needs the instance)
|
|
214
232
|
|
|
215
|
-
Only once `provision.status == "completed"` (
|
|
233
|
+
Only once `provision.status == "completed"` (see *State and artifacts* in the top-level `SKILL.md`),
|
|
216
234
|
route on the brief's **Suggested backend** and follow that subfolder, which maps the helper onto the
|
|
217
235
|
live backend — for WordPress, through the per-site WP MCP's ability tools (the installation JWT
|
|
218
236
|
authenticates that MCP session; `wp/v2` REST is the public read surface, not a write path for the
|
|
@@ -5,8 +5,7 @@ description: "Push phase of Step 4 for a headless WordPress backend: open an aut
|
|
|
5
5
|
|
|
6
6
|
# headless-step-content-model-wordpress
|
|
7
7
|
|
|
8
|
-
**Push phase — needs the live instance.** Run only after `provision.status == "completed"`
|
|
9
|
-
against `hosting_websites_list-setups`). It maps the backend-agnostic `content-model.md` onto real
|
|
8
|
+
**Push phase — needs the live instance.** Run only after `provision.status == "completed"`. It maps the backend-agnostic `content-model.md` onto real
|
|
10
9
|
WordPress objects.
|
|
11
10
|
|
|
12
11
|
**In:** `content-model.md`, `site.json` (both prepared earlier), generated media under
|
|
@@ -16,9 +16,9 @@ description: "Step 7 (validate) of the Hostinger Headless flow. OPTIONAL quick g
|
|
|
16
16
|
|
|
17
17
|
## 1 — Instance status (always, quick)
|
|
18
18
|
|
|
19
|
-
`provision.status == "completed"`
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
`provision.status == "completed"` in `state.json`. Anything else → not ready; return to the instance
|
|
20
|
+
barrier, don't deploy. Deploy's pre-flight asks the connector again (see *State and artifacts* in the
|
|
21
|
+
top-level `SKILL.md`).
|
|
22
22
|
|
|
23
23
|
## 2 — Backend API status (WordPress / WooCommerce / Ecommerce, always, quick)
|
|
24
24
|
|
|
@@ -21,7 +21,7 @@ This is an **instance-barrier** step: run it only after `provision.status == "co
|
|
|
21
21
|
> settings passed exactly as the table below says. Run it from the project, since it links the current directory:
|
|
22
22
|
> `cd ~/.hostinger/<domain>/src && hostinger up --domain <domain> --type nodejs --app-type <appType> --output-directory <dir> --build-script build --node-version 22 --package-manager npm --yes --format json`.
|
|
23
23
|
> It uploads, builds, waits and returns `data.buildUuid` and `data.buildState`; add `--no-wait` to
|
|
24
|
-
> return at once and
|
|
24
|
+
> return at once and wait with `node <skill>/wait.mjs build <domain> <uuid>`. Then continue at
|
|
25
25
|
> "Verify" below. Recent CLI versions see that the site is headless (`data.headless: true`) and
|
|
26
26
|
> deploy only its Node.js frontend, leaving `/cms` alone; still pass the settings above.
|
|
27
27
|
|
|
@@ -74,8 +74,9 @@ framework's correct values** — don't rely on auto-detection (it returns null
|
|
|
74
74
|
|
|
75
75
|
## Step 1 — Pre-flight
|
|
76
76
|
|
|
77
|
-
1. **Instance live.** `provision.status == "completed"`, re-verified with
|
|
78
|
-
`hosting_websites_list-setups { "domain": "<domain>" }
|
|
77
|
+
1. **Instance live (the deploy gate).** `provision.status == "completed"`, re-verified with
|
|
78
|
+
`hosting_websites_list-setups { "domain": "<domain>" }`: deploy overwrites the site root, so it never
|
|
79
|
+
trusts `state.json` alone.
|
|
79
80
|
2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and
|
|
80
81
|
the project builds clean (frontend/validate already did this). If not, go back.
|
|
81
82
|
- **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —
|
|
@@ -128,15 +129,26 @@ website root** (expected) and keeps `/cms` intact. `type` is always `"nodejs"` f
|
|
|
128
129
|
**never `"static"`** (see the gate below). Passing `appType`/`outputDirectory` explicitly is what
|
|
129
130
|
keeps the root from serving 403 (see the rule above).
|
|
130
131
|
|
|
131
|
-
## Step 4 —
|
|
132
|
+
## Step 4 — Wait for the build and the live root
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
Wait for it in one command (see *Waiting on Hostinger* in the top-level `SKILL.md`):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
node <skill>/wait.mjs build <domain> <buildUuid>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It waits for `hosting_get-deployment` to report the build `completed`, then for `https://<domain>/` to
|
|
141
|
+
serve 200. What its result means here:
|
|
142
|
+
|
|
143
|
+
- `ok: true`: the build is done and the root serves 200. Go on to Step 5.
|
|
144
|
+
- `ok: false` with `build.state: failed`: read `log_tail` (the end of the build log) and `analysis`, fix
|
|
145
|
+
locally, re-archive and redeploy (pass `fromLine` to `hosting_get-deployment` to read only new log
|
|
146
|
+
output).
|
|
147
|
+
- `ok: false` with `build settings missing`: the settings didn't take (the root would 403 for good). Fix
|
|
148
|
+
the deploy call and redeploy.
|
|
149
|
+
- `ok: false` with `timed_out`: `last_status` says whether the build was still running or the root
|
|
150
|
+
wasn't serving 200 yet (see Step 5's note on a 403). For an SSR app that built but hangs,
|
|
151
|
+
`hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.
|
|
140
152
|
|
|
141
153
|
## Step 5 — Verify the live site (all must pass)
|
|
142
154
|
|
|
@@ -163,9 +175,10 @@ hangs, `hosting_nodejs_restart-application { username, domain }` recovers the se
|
|
|
163
175
|
> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.
|
|
164
176
|
> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.
|
|
165
177
|
|
|
166
|
-
|
|
167
|
-
`
|
|
168
|
-
|
|
178
|
+
The root check is already done when `wait.mjs build` returned `ok: true`; check the rest in one shell
|
|
179
|
+
command (`curl` each URL). A `completed` build whose root 403s almost always means the build settings
|
|
180
|
+
weren't passed to `hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a
|
|
181
|
+
stale root; re-check after ~30 s.
|
|
169
182
|
|
|
170
183
|
## Step 6 — Record `deploy.json`
|
|
171
184
|
|
|
@@ -160,8 +160,9 @@ from `run-log.json`, events from `events.jsonl`) into `reports/<run>.json` and `
|
|
|
160
160
|
HTML to `report.html` in the workspace:
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
|
|
164
|
-
node <skill>/run-log.mjs <domain>
|
|
163
|
+
# one shell command; one `event good` per thing that went well, if not logged yet
|
|
164
|
+
node <skill>/run-log.mjs <domain> event good '{"text":"<something that went well>"}' && \
|
|
165
|
+
node <skill>/run-log.mjs <domain> stage-end done ok && \
|
|
165
166
|
node <skill>/run-log.mjs <domain> report --outcome <live|stopped|failed> [--tokens '<json>']
|
|
166
167
|
```
|
|
167
168
|
|