hostinger-api-mcp 3.15.1 → 3.15.2
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 +7 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/headless/steps/04-content-model/SKILL.md +22 -11
- package/src/core/skills.js +3 -3
- package/src/core/skills.ts +3 -3
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ 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.15.2 — 2026-10-27
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Bundled agent skills: `skills/headless/steps/04-content-model/SKILL.md`
|
|
12
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
13
|
+
|
|
7
14
|
## v3.15.1 — 2026-10-27
|
|
8
15
|
|
|
9
16
|
### 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.15.
|
|
9
|
+
"version": "3.15.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "3.15.
|
|
14
|
+
"version": "3.15.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -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
|
|
99
|
+
| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API, or web search + web fetch | 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
|
|
@@ -105,8 +105,9 @@ not an answer, see *When you ask the user* in the top-level `SKILL.md`):
|
|
|
105
105
|
> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not
|
|
106
106
|
> an optional extra. If you are tempted to skip it "for speed", stop and offer the user the choice instead.
|
|
107
107
|
|
|
108
|
-
If you can generate images yourself (some agents can), that
|
|
109
|
-
|
|
108
|
+
If you can generate images yourself (some agents can), offer that as a third option; only if the user
|
|
109
|
+
picks it, put the files in `media/` (no tool needed). **Record alt text for every image whichever source
|
|
110
|
+
you use.**
|
|
110
111
|
|
|
111
112
|
**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the
|
|
112
113
|
option (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the
|
|
@@ -171,14 +172,21 @@ and the push phase uploads them.
|
|
|
171
172
|
- Without a key, use web search limited to `unsplash.com` (e.g. "unsplash photo Lisbon tram") and
|
|
172
173
|
take the photo id from the result URL: the last segment of `unsplash.com/photos/<slug>-<id>`, or
|
|
173
174
|
the whole segment for `unsplash.com/photos/<id>`.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
2. **Resolve a direct file URL.** With the API, the result's `urls.raw` is the file and `user.name`
|
|
176
|
+
the photographer; for each photo you keep, also `GET` its `links.download_location` (same header),
|
|
177
|
+
as the API terms require. Without a key, open the photo page (`https://unsplash.com/photos/<…>`)
|
|
178
|
+
with your **web-fetch tool** and ask it for the main photo's `images.unsplash.com/photo-<…>` URL,
|
|
179
|
+
quoted exactly, and the photographer's name. Either way, drop the query string and request a web
|
|
180
|
+
size: `https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`. Check that it returns
|
|
181
|
+
200 with `image/jpeg`: the upload ability needs this direct file URL, not the photo page.
|
|
182
|
+
- Every other `unsplash.com` request (curl on the page, `/photos/<id>/download`, `/napi/…`,
|
|
183
|
+
`/oembed`) hits a bot challenge: a `307` to `/.within.website` (Anubis) or a 401. If your fetch
|
|
184
|
+
tool gets the challenge page too, stop: no user-agent spoofing or other workarounds. On a
|
|
185
|
+
WordPress backend where Option A hasn't already failed, offer it; otherwise ask the user for an
|
|
186
|
+
`UNSPLASH_ACCESS_KEY` or record the gap (step 5).
|
|
187
|
+
3. **Look before you keep it.** Download a small preview (`?w=500`) and view it. Search snippets and
|
|
188
|
+
fetch summaries only describe the page, and can hand you a related photo's thumbnail instead of
|
|
189
|
+
the main one; a photo also often isn't quite what its title says. Reject anything off-topic.
|
|
182
190
|
If nothing fits exactly, take the closest real photo and note the gap rather than mislabel it.
|
|
183
191
|
4. **Write alt text from what the photo shows**, not from the article's image brief. For example,
|
|
184
192
|
use "Shopfronts and parked scooters on a Hanoi street" even if the article is about rain.
|
|
@@ -191,6 +199,9 @@ and the push phase uploads them.
|
|
|
191
199
|
Image file: <site>-<subject>.jpg
|
|
192
200
|
```
|
|
193
201
|
|
|
202
|
+
If you can't get a real photo, write `Image gap: <reason>` in place of these lines and tell the
|
|
203
|
+
user. Never substitute drawn or generated images without saying so.
|
|
204
|
+
|
|
194
205
|
Unsplash photos are free to use under the Unsplash License. Still credit the photographer: the push
|
|
195
206
|
phase stores "Photo by <Photographer> on Unsplash" as the image caption.
|
|
196
207
|
|
package/src/core/skills.js
CHANGED
|
@@ -176,10 +176,10 @@ export default [
|
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
"uri": "skill://hostinger-headless/steps/04-content-model/SKILL.md",
|
|
179
|
-
"digest": "sha256:
|
|
180
|
-
"size":
|
|
179
|
+
"digest": "sha256:bbc462e158dc9252c018a38e24b45aaa7072cd3e765bda6ad21cfb30fd725201",
|
|
180
|
+
"size": 14732,
|
|
181
181
|
"mimeType": "text/markdown",
|
|
182
|
-
"text": "---\nname: headless-step-content-model\ndescription: \"Step 4 (content model) of the Hostinger Headless flow. Prepare phase: turns brief.md into a content helper (content-model.md) — fixed Title/Description plus a tailored generation prompt per section (Navigation, Pages, Articles) — and generates the drafts/images, no live backend needed. Push phase: routes to the backend subfolder to create it the backend's way once provision is ready.\"\n---\n\n# headless-step-content-model\n\nTwo phases around the **instance barrier** (see the top-level `SKILL.md`):\n\n- **Prepare — no instance.** Build the content helper from `brief.md` and generate the content it\n calls for. Runs *during* the provision wait; needs only the brief.\n- **Push — needs the instance.** Create that content in the backend. Gated on\n `provision.status == \"completed\"`; routes to `wordpress/` or `ecommerce/`.\n\n**In:** `brief.md`. **Out:** `content-model.md` (+ any generated media under\n`~/.hostinger/<domain>/media/`).\n\n> Don't wait for provisioning — the helper comes from the brief alone.\n\n---\n\n## Prepare phase — write the content helper (no instance)\n\n`content-model.md` is a lightweight helper, not a schema. Fill the **fixed values** from the brief and\nwrite a **generation prompt** for each section — tailored to *this* site, not generic. Then run those\nprompts to produce the drafts (and images), so the push phase is just upload.\n\nWrite `content-model.md` from this template, adapting every prompt to the brief's purpose, audience\nand tone:\n\n```markdown\n# Content — <domain>\n\n> Content helper prepared from brief.md on <today>. Fixed values are set; each prompt is run\n> during the provision wait to generate the content and media. Push to the backend happens later.\n\n## Title\n<site name, from the brief>\n\n## Description\n<one–two line site description / default meta, from the brief>\n\n## Navigation\n> Generate the primary menu from the site's pages in reading order (<list the brief's pages>).\n> Short labels; note which items are static pages vs. listing pages. Add a footer menu if the\n> brief implies one (socials, contact).\n\n## Pages\n> Generate each page the brief lists. For every page: title, slug, a short section outline, the\n> copy for static pages (e.g. About, Contact), and SEO title + description.\n> <bullet the brief's pages with their one-line purpose>\n\n## Articles (optional)\n> Generate <N> articles across the brief's categories (<list them>). For each: title, slug,\n> body in the brief's tone, category, any relationship (e.g. destination), published date, SEO,\n> and a featured image (see *Images* below — Hostinger AI generation or Unsplash stock) with\n> alt text. Omit if the brief has no article-like content.\n\n## Products (optional — required for a commerce brief)\n> Skip for a non-commerce brief. For a commerce brief, generate the catalog content here (prepare\n> work, no instance): for each product — name, slug, SKU, short + long description in the brief's\n> tone, price, the collection it belongs to, variants if any, and a product image (see *Images* —\n> Hostinger AI generation or Unsplash stock) with alt text. Group products into the collections\n> the brief implies, and generate any **coupons** the brief calls for (code, type, amount). The\n> `ecommerce` push phase creates these on the store's sales channel; a **WordPress + WooCommerce** brief\n> seeds them into WooCommerce instead (see `wordpress/SKILL.md` + `wordpress/woocommerce.md`) — there\n> the **SKU is the idempotency key** and coupons + product categories are part of the catalog.\n```\n\nGuidance:\n\n- **Fixed vs. prompt.** Title and Description are concrete values. Navigation, Pages and Articles are\n prompts because their volume/wording is generated — keep them as prompts so the push phase (or a\n rerun) can regenerate consistently.\n- **Run the prompts now.** Generate the actual page copy and article drafts during the wait — this is\n the slowest work and the whole reason it runs in parallel. Images depend on the chosen source (see\n *Images* below): Unsplash photos and your own generated files can be produced now; Hostinger AI\n images only generate once the instance exists, so write their brief now and generate at the barrier.\n Put any generated/own media in `~/.hostinger/<domain>/media/` and reference it by filename. Record\n alt text for every image.\n- **Optional sections.** Drop **Articles** if the brief has no article-like content. Drop **Products**\n for a non-commerce brief; for a commerce brief, generate the product catalog here (names, prices,\n descriptions, collections, images) — that's prepare work, and the `ecommerce` push phase then creates\n it on the store.\n- **Don't block on design.** The design step (5) owns the look, and it runs in parallel during the\n provision wait, so never wait for it here. If `design.md` already exists when you source images,\n follow its **Imagery** section; otherwise generate neutral, on-topic imagery from the brief.\n\n### Images — generate with Hostinger AI, or source stock from Unsplash\n\nEvery article (and every product) needs an image. There are two sources, and **you MUST offer the user\nboth and let them choose — never pick silently.** State the default for this backend in one line, name\nthe other option, and proceed on their answer (\"you decide\" = use the default; a skipped tool result is\nnot an answer, see *When you ask the user* in the top-level `SKILL.md`):\n\n| Option | Tool | Backends | Needs the live instance? |\n|---|---|---|---|\n| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |\n| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API / web search | any | no — pure prepare work |\n\n> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is\n> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build\n> is only correct when **the user chose it**, or when **AI generation actually failed** (and then you say\n> so). Taking stock because it is faster, synchronous, or avoids the async poll is a **defect**, not a\n> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not\n> an optional extra. If you are tempted to skip it \"for speed\", stop and offer the user the choice instead.\n\nIf you can generate images yourself (some agents can), that's a third path: put the files in `media/`\nand record alt text — no tool needed. **Record alt text for every image whichever source you use.**\n\n**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the\noption (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the\nphoto now (below); Option A only generates once the instance exists, so its briefs wait for the barrier.\n\n#### Option A — Hostinger AI image generation (WordPress backends)\n\n`wordpress_ai-content_generate-images` generates on-brand images for a live WordPress install, so it\nruns **at the instance barrier** (after `provision.status == \"completed\"`), using the install from\n`site.json` (`username`, and `wordpress.install_id` → the `software` path param). Follow `design.md`'s\n**Imagery** section for look and palette. It is **asynchronous**:\n\n1. **Start** one generation per image:\n\n ```json\n { \"operation\": \"wordpress_ai-content_generate-images\",\n \"params\": {\n \"username\": \"<site.json username>\",\n \"software\": \"<site.json wordpress.install_id>\",\n \"prompt\": \"<what the image shows; for a logo, the wordmark + mark to render>\",\n \"image_type\": \"hero|background|inline|gallery|product|logo\",\n \"num_of_images\": 1,\n \"style\": \"<photographic style tokens from design.md; ignored for logo>\",\n \"color_guidance\": \"<logo only — colour direction, not rendered as text>\" } }\n ```\n\n `prompt` (3–2500 chars) and `image_type` are required; `width`/`height` (128–2048) default per type\n and are ignored for `logo`. Returns `202` with a `generation_id` and `status: in-progress`.\n **Only one generation runs per install at a time** — a `409` means one is already running (and it\n returns no id), so wait for it to finish before starting the next. The endpoint allows 10\n requests/minute.\n\n2. **Poll** `wordpress_ai-content_generation-result` with `{ username, software, generation_id }`\n every few seconds until `status` is `completed` (or `failed`). A completed result carries\n `result.images[]` — each with a **temporary** `url`, plus `width`, `height`, `content_type` — and\n `result.expires_at`.\n\n3. **Use the images before they expire.** The URLs are short-lived, so in the push phase hand each\n `url` straight to WordPress with `media-upload-from-url`, which downloads and stores WP's own copy\n (see the wordpress push `SKILL.md`). Don't persist the temporary URL as the live image. There is no\n photographer to credit — leave `caption` empty or a short on-brand one. Record under the\n article/product in `content-model.md`:\n\n ```markdown\n Image: Hostinger AI (image_type: <type>, generation_id: <uuid>)\n Image alt: <what the image shows>\n Image file: <site>-<subject>.jpg\n ```\n\n On `failed`, retry once with a tightened prompt; if it fails again, fall back to Option B and note\n the gap. Never block the whole push on a single image.\n\n#### Option B — stock photos from Unsplash (any backend, no instance)\n\nThis is still prepare work and needs no live backend: you pick the photos and record their URLs here,\nand the push phase uploads them.\n\n1. **Find candidates.** Use one photo per article, matching its subject and destination.\n - With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API:\n `GET https://api.unsplash.com/search/photos?query=<subject>&orientation=landscape` with header\n `Authorization: Client-ID <key>`.\n - Without a key, use web search limited to `unsplash.com` (e.g. \"unsplash photo Lisbon tram\") and\n take the photo id from the result URL: the last segment of `unsplash.com/photos/<slug>-<id>`, or\n the whole segment for `unsplash.com/photos/<id>`.\n - Don't call `unsplash.com/napi/…`. It sits behind a bot challenge and returns 401.\n2. **Resolve a direct file URL.** `GET https://unsplash.com/photos/<id>/download?force=true` (don't\n follow the redirect) answers `302` with `Location: https://images.unsplash.com/photo-<…>?…`. The\n `dl=` parameter in it also carries the photographer's name. Drop the query string and request a\n web size: `https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`. The upload ability\n needs this direct file URL, which returns 200 with `image/jpeg`. A photo *page* URL won't work.\n3. **Look before you keep it.** Download a small preview (`?w=500`) and view it. Search snippets\n only show captions, and a photo often isn't quite what its title says. Reject anything off-topic.\n If nothing fits exactly, take the closest real photo and note the gap rather than mislabel it.\n4. **Write alt text from what the photo shows**, not from the article's image brief. For example,\n use \"Shopfronts and parked scooters on a Hanoi street\" even if the article is about rain.\n5. **Record it** under each article in `content-model.md`:\n\n ```markdown\n Image: https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max\n Image credit: <Photographer> — https://unsplash.com/photos/<id> (Unsplash License)\n Image alt: <what the photo shows>\n Image file: <site>-<subject>.jpg\n ```\n\nUnsplash photos are free to use under the Unsplash License. Still credit the photographer: the push\nphase stores \"Photo by <Photographer> on Unsplash\" as the image caption.\n\nThen update `state.json`: set `stage` to the next prepare stage still to do, fresh `updated_at`. Do\n**not** advance past the barrier here.\n\n---\n\n## Push phase — create it the backend's way (needs the instance)\n\nOnly once `provision.status == \"completed\"` (re-verified against `hosting_websites_list-setups`),\nroute on the brief's **Suggested backend** and follow that subfolder, which maps the helper onto the\nlive backend — for WordPress, through the per-site WP MCP's ability tools (the installation JWT\nauthenticates that MCP session; `wp/v2` REST is the public read surface, not a write path for the\ntoken) to create the menu, pages, posts, categories and media:\n\n| Suggested backend | Follow |\n|---|---|\n| WordPress | `wordpress/SKILL.md` |\n| WordPress + WooCommerce | `wordpress/SKILL.md` — pushes WP content **and** seeds the WooCommerce catalog (its WooCommerce section + `wordpress/woocommerce.md`) |\n| Ecommerce | `ecommerce/SKILL.md` |\n| Webapp | — (no push; skip to frontend) |\n\n> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce`, and `webapp`** —\n> where `webapp` is \"supported\" by **skipping this whole stage** (it has no content backend; see the note\n> below). A **WordPress + WooCommerce** brief routes to `wordpress/SKILL.md`, which pushes the WP content\n> **and** seeds the WooCommerce catalog in the same push. PocketBase is not available in this version.\n> Push to whichever single backend provision set up (see the provision router) — don't try to push to two\n> backends here.\n>\n> **Webapp has no content backend.** There is no CMS or store to seed, so a `webapp` brief **skips this\n> whole stage** — both prepare and push. The app owns its own data (its own API or database). Leave\n> `content-model.md` unwritten, mark content-model `skipped` in the run log, and go straight to the\n> frontend step.\n\nAfter the push completes, update `state.json` and continue to the frontend **data** phase.\n"
|
|
182
|
+
"text": "---\nname: headless-step-content-model\ndescription: \"Step 4 (content model) of the Hostinger Headless flow. Prepare phase: turns brief.md into a content helper (content-model.md) — fixed Title/Description plus a tailored generation prompt per section (Navigation, Pages, Articles) — and generates the drafts/images, no live backend needed. Push phase: routes to the backend subfolder to create it the backend's way once provision is ready.\"\n---\n\n# headless-step-content-model\n\nTwo phases around the **instance barrier** (see the top-level `SKILL.md`):\n\n- **Prepare — no instance.** Build the content helper from `brief.md` and generate the content it\n calls for. Runs *during* the provision wait; needs only the brief.\n- **Push — needs the instance.** Create that content in the backend. Gated on\n `provision.status == \"completed\"`; routes to `wordpress/` or `ecommerce/`.\n\n**In:** `brief.md`. **Out:** `content-model.md` (+ any generated media under\n`~/.hostinger/<domain>/media/`).\n\n> Don't wait for provisioning — the helper comes from the brief alone.\n\n---\n\n## Prepare phase — write the content helper (no instance)\n\n`content-model.md` is a lightweight helper, not a schema. Fill the **fixed values** from the brief and\nwrite a **generation prompt** for each section — tailored to *this* site, not generic. Then run those\nprompts to produce the drafts (and images), so the push phase is just upload.\n\nWrite `content-model.md` from this template, adapting every prompt to the brief's purpose, audience\nand tone:\n\n```markdown\n# Content — <domain>\n\n> Content helper prepared from brief.md on <today>. Fixed values are set; each prompt is run\n> during the provision wait to generate the content and media. Push to the backend happens later.\n\n## Title\n<site name, from the brief>\n\n## Description\n<one–two line site description / default meta, from the brief>\n\n## Navigation\n> Generate the primary menu from the site's pages in reading order (<list the brief's pages>).\n> Short labels; note which items are static pages vs. listing pages. Add a footer menu if the\n> brief implies one (socials, contact).\n\n## Pages\n> Generate each page the brief lists. For every page: title, slug, a short section outline, the\n> copy for static pages (e.g. About, Contact), and SEO title + description.\n> <bullet the brief's pages with their one-line purpose>\n\n## Articles (optional)\n> Generate <N> articles across the brief's categories (<list them>). For each: title, slug,\n> body in the brief's tone, category, any relationship (e.g. destination), published date, SEO,\n> and a featured image (see *Images* below — Hostinger AI generation or Unsplash stock) with\n> alt text. Omit if the brief has no article-like content.\n\n## Products (optional — required for a commerce brief)\n> Skip for a non-commerce brief. For a commerce brief, generate the catalog content here (prepare\n> work, no instance): for each product — name, slug, SKU, short + long description in the brief's\n> tone, price, the collection it belongs to, variants if any, and a product image (see *Images* —\n> Hostinger AI generation or Unsplash stock) with alt text. Group products into the collections\n> the brief implies, and generate any **coupons** the brief calls for (code, type, amount). The\n> `ecommerce` push phase creates these on the store's sales channel; a **WordPress + WooCommerce** brief\n> seeds them into WooCommerce instead (see `wordpress/SKILL.md` + `wordpress/woocommerce.md`) — there\n> the **SKU is the idempotency key** and coupons + product categories are part of the catalog.\n```\n\nGuidance:\n\n- **Fixed vs. prompt.** Title and Description are concrete values. Navigation, Pages and Articles are\n prompts because their volume/wording is generated — keep them as prompts so the push phase (or a\n rerun) can regenerate consistently.\n- **Run the prompts now.** Generate the actual page copy and article drafts during the wait — this is\n the slowest work and the whole reason it runs in parallel. Images depend on the chosen source (see\n *Images* below): Unsplash photos and your own generated files can be produced now; Hostinger AI\n images only generate once the instance exists, so write their brief now and generate at the barrier.\n Put any generated/own media in `~/.hostinger/<domain>/media/` and reference it by filename. Record\n alt text for every image.\n- **Optional sections.** Drop **Articles** if the brief has no article-like content. Drop **Products**\n for a non-commerce brief; for a commerce brief, generate the product catalog here (names, prices,\n descriptions, collections, images) — that's prepare work, and the `ecommerce` push phase then creates\n it on the store.\n- **Don't block on design.** The design step (5) owns the look, and it runs in parallel during the\n provision wait, so never wait for it here. If `design.md` already exists when you source images,\n follow its **Imagery** section; otherwise generate neutral, on-topic imagery from the brief.\n\n### Images — generate with Hostinger AI, or source stock from Unsplash\n\nEvery article (and every product) needs an image. There are two sources, and **you MUST offer the user\nboth and let them choose — never pick silently.** State the default for this backend in one line, name\nthe other option, and proceed on their answer (\"you decide\" = use the default; a skipped tool result is\nnot an answer, see *When you ask the user* in the top-level `SKILL.md`):\n\n| Option | Tool | Backends | Needs the live instance? |\n|---|---|---|---|\n| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |\n| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API, or web search + web fetch | any | no — pure prepare work |\n\n> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is\n> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build\n> is only correct when **the user chose it**, or when **AI generation actually failed** (and then you say\n> so). Taking stock because it is faster, synchronous, or avoids the async poll is a **defect**, not a\n> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not\n> an optional extra. If you are tempted to skip it \"for speed\", stop and offer the user the choice instead.\n\nIf you can generate images yourself (some agents can), offer that as a third option; only if the user\npicks it, put the files in `media/` (no tool needed). **Record alt text for every image whichever source\nyou use.**\n\n**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the\noption (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the\nphoto now (below); Option A only generates once the instance exists, so its briefs wait for the barrier.\n\n#### Option A — Hostinger AI image generation (WordPress backends)\n\n`wordpress_ai-content_generate-images` generates on-brand images for a live WordPress install, so it\nruns **at the instance barrier** (after `provision.status == \"completed\"`), using the install from\n`site.json` (`username`, and `wordpress.install_id` → the `software` path param). Follow `design.md`'s\n**Imagery** section for look and palette. It is **asynchronous**:\n\n1. **Start** one generation per image:\n\n ```json\n { \"operation\": \"wordpress_ai-content_generate-images\",\n \"params\": {\n \"username\": \"<site.json username>\",\n \"software\": \"<site.json wordpress.install_id>\",\n \"prompt\": \"<what the image shows; for a logo, the wordmark + mark to render>\",\n \"image_type\": \"hero|background|inline|gallery|product|logo\",\n \"num_of_images\": 1,\n \"style\": \"<photographic style tokens from design.md; ignored for logo>\",\n \"color_guidance\": \"<logo only — colour direction, not rendered as text>\" } }\n ```\n\n `prompt` (3–2500 chars) and `image_type` are required; `width`/`height` (128–2048) default per type\n and are ignored for `logo`. Returns `202` with a `generation_id` and `status: in-progress`.\n **Only one generation runs per install at a time** — a `409` means one is already running (and it\n returns no id), so wait for it to finish before starting the next. The endpoint allows 10\n requests/minute.\n\n2. **Poll** `wordpress_ai-content_generation-result` with `{ username, software, generation_id }`\n every few seconds until `status` is `completed` (or `failed`). A completed result carries\n `result.images[]` — each with a **temporary** `url`, plus `width`, `height`, `content_type` — and\n `result.expires_at`.\n\n3. **Use the images before they expire.** The URLs are short-lived, so in the push phase hand each\n `url` straight to WordPress with `media-upload-from-url`, which downloads and stores WP's own copy\n (see the wordpress push `SKILL.md`). Don't persist the temporary URL as the live image. There is no\n photographer to credit — leave `caption` empty or a short on-brand one. Record under the\n article/product in `content-model.md`:\n\n ```markdown\n Image: Hostinger AI (image_type: <type>, generation_id: <uuid>)\n Image alt: <what the image shows>\n Image file: <site>-<subject>.jpg\n ```\n\n On `failed`, retry once with a tightened prompt; if it fails again, fall back to Option B and note\n the gap. Never block the whole push on a single image.\n\n#### Option B — stock photos from Unsplash (any backend, no instance)\n\nThis is still prepare work and needs no live backend: you pick the photos and record their URLs here,\nand the push phase uploads them.\n\n1. **Find candidates.** Use one photo per article, matching its subject and destination.\n - With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API:\n `GET https://api.unsplash.com/search/photos?query=<subject>&orientation=landscape` with header\n `Authorization: Client-ID <key>`.\n - Without a key, use web search limited to `unsplash.com` (e.g. \"unsplash photo Lisbon tram\") and\n take the photo id from the result URL: the last segment of `unsplash.com/photos/<slug>-<id>`, or\n the whole segment for `unsplash.com/photos/<id>`.\n2. **Resolve a direct file URL.** With the API, the result's `urls.raw` is the file and `user.name`\n the photographer; for each photo you keep, also `GET` its `links.download_location` (same header),\n as the API terms require. Without a key, open the photo page (`https://unsplash.com/photos/<…>`)\n with your **web-fetch tool** and ask it for the main photo's `images.unsplash.com/photo-<…>` URL,\n quoted exactly, and the photographer's name. Either way, drop the query string and request a web\n size: `https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`. Check that it returns\n 200 with `image/jpeg`: the upload ability needs this direct file URL, not the photo page.\n - Every other `unsplash.com` request (curl on the page, `/photos/<id>/download`, `/napi/…`,\n `/oembed`) hits a bot challenge: a `307` to `/.within.website` (Anubis) or a 401. If your fetch\n tool gets the challenge page too, stop: no user-agent spoofing or other workarounds. On a\n WordPress backend where Option A hasn't already failed, offer it; otherwise ask the user for an\n `UNSPLASH_ACCESS_KEY` or record the gap (step 5).\n3. **Look before you keep it.** Download a small preview (`?w=500`) and view it. Search snippets and\n fetch summaries only describe the page, and can hand you a related photo's thumbnail instead of\n the main one; a photo also often isn't quite what its title says. Reject anything off-topic.\n If nothing fits exactly, take the closest real photo and note the gap rather than mislabel it.\n4. **Write alt text from what the photo shows**, not from the article's image brief. For example,\n use \"Shopfronts and parked scooters on a Hanoi street\" even if the article is about rain.\n5. **Record it** under each article in `content-model.md`:\n\n ```markdown\n Image: https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max\n Image credit: <Photographer> — https://unsplash.com/photos/<id> (Unsplash License)\n Image alt: <what the photo shows>\n Image file: <site>-<subject>.jpg\n ```\n\n If you can't get a real photo, write `Image gap: <reason>` in place of these lines and tell the\n user. Never substitute drawn or generated images without saying so.\n\nUnsplash photos are free to use under the Unsplash License. Still credit the photographer: the push\nphase stores \"Photo by <Photographer> on Unsplash\" as the image caption.\n\nThen update `state.json`: set `stage` to the next prepare stage still to do, fresh `updated_at`. Do\n**not** advance past the barrier here.\n\n---\n\n## Push phase — create it the backend's way (needs the instance)\n\nOnly once `provision.status == \"completed\"` (re-verified against `hosting_websites_list-setups`),\nroute on the brief's **Suggested backend** and follow that subfolder, which maps the helper onto the\nlive backend — for WordPress, through the per-site WP MCP's ability tools (the installation JWT\nauthenticates that MCP session; `wp/v2` REST is the public read surface, not a write path for the\ntoken) to create the menu, pages, posts, categories and media:\n\n| Suggested backend | Follow |\n|---|---|\n| WordPress | `wordpress/SKILL.md` |\n| WordPress + WooCommerce | `wordpress/SKILL.md` — pushes WP content **and** seeds the WooCommerce catalog (its WooCommerce section + `wordpress/woocommerce.md`) |\n| Ecommerce | `ecommerce/SKILL.md` |\n| Webapp | — (no push; skip to frontend) |\n\n> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce`, and `webapp`** —\n> where `webapp` is \"supported\" by **skipping this whole stage** (it has no content backend; see the note\n> below). A **WordPress + WooCommerce** brief routes to `wordpress/SKILL.md`, which pushes the WP content\n> **and** seeds the WooCommerce catalog in the same push. PocketBase is not available in this version.\n> Push to whichever single backend provision set up (see the provision router) — don't try to push to two\n> backends here.\n>\n> **Webapp has no content backend.** There is no CMS or store to seed, so a `webapp` brief **skips this\n> whole stage** — both prepare and push. The app owns its own data (its own API or database). Leave\n> `content-model.md` unwritten, mark content-model `skipped` in the run log, and go straight to the\n> frontend step.\n\nAfter the push completes, update `state.json` and continue to the frontend **data** phase.\n"
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"uri": "skill://hostinger-headless/steps/04-content-model/ecommerce/SKILL.md",
|
package/src/core/skills.ts
CHANGED
|
@@ -190,10 +190,10 @@ const skills: Skill[] = [
|
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
"uri": "skill://hostinger-headless/steps/04-content-model/SKILL.md",
|
|
193
|
-
"digest": "sha256:
|
|
194
|
-
"size":
|
|
193
|
+
"digest": "sha256:bbc462e158dc9252c018a38e24b45aaa7072cd3e765bda6ad21cfb30fd725201",
|
|
194
|
+
"size": 14732,
|
|
195
195
|
"mimeType": "text/markdown",
|
|
196
|
-
"text": "---\nname: headless-step-content-model\ndescription: \"Step 4 (content model) of the Hostinger Headless flow. Prepare phase: turns brief.md into a content helper (content-model.md) — fixed Title/Description plus a tailored generation prompt per section (Navigation, Pages, Articles) — and generates the drafts/images, no live backend needed. Push phase: routes to the backend subfolder to create it the backend's way once provision is ready.\"\n---\n\n# headless-step-content-model\n\nTwo phases around the **instance barrier** (see the top-level `SKILL.md`):\n\n- **Prepare — no instance.** Build the content helper from `brief.md` and generate the content it\n calls for. Runs *during* the provision wait; needs only the brief.\n- **Push — needs the instance.** Create that content in the backend. Gated on\n `provision.status == \"completed\"`; routes to `wordpress/` or `ecommerce/`.\n\n**In:** `brief.md`. **Out:** `content-model.md` (+ any generated media under\n`~/.hostinger/<domain>/media/`).\n\n> Don't wait for provisioning — the helper comes from the brief alone.\n\n---\n\n## Prepare phase — write the content helper (no instance)\n\n`content-model.md` is a lightweight helper, not a schema. Fill the **fixed values** from the brief and\nwrite a **generation prompt** for each section — tailored to *this* site, not generic. Then run those\nprompts to produce the drafts (and images), so the push phase is just upload.\n\nWrite `content-model.md` from this template, adapting every prompt to the brief's purpose, audience\nand tone:\n\n```markdown\n# Content — <domain>\n\n> Content helper prepared from brief.md on <today>. Fixed values are set; each prompt is run\n> during the provision wait to generate the content and media. Push to the backend happens later.\n\n## Title\n<site name, from the brief>\n\n## Description\n<one–two line site description / default meta, from the brief>\n\n## Navigation\n> Generate the primary menu from the site's pages in reading order (<list the brief's pages>).\n> Short labels; note which items are static pages vs. listing pages. Add a footer menu if the\n> brief implies one (socials, contact).\n\n## Pages\n> Generate each page the brief lists. For every page: title, slug, a short section outline, the\n> copy for static pages (e.g. About, Contact), and SEO title + description.\n> <bullet the brief's pages with their one-line purpose>\n\n## Articles (optional)\n> Generate <N> articles across the brief's categories (<list them>). For each: title, slug,\n> body in the brief's tone, category, any relationship (e.g. destination), published date, SEO,\n> and a featured image (see *Images* below — Hostinger AI generation or Unsplash stock) with\n> alt text. Omit if the brief has no article-like content.\n\n## Products (optional — required for a commerce brief)\n> Skip for a non-commerce brief. For a commerce brief, generate the catalog content here (prepare\n> work, no instance): for each product — name, slug, SKU, short + long description in the brief's\n> tone, price, the collection it belongs to, variants if any, and a product image (see *Images* —\n> Hostinger AI generation or Unsplash stock) with alt text. Group products into the collections\n> the brief implies, and generate any **coupons** the brief calls for (code, type, amount). The\n> `ecommerce` push phase creates these on the store's sales channel; a **WordPress + WooCommerce** brief\n> seeds them into WooCommerce instead (see `wordpress/SKILL.md` + `wordpress/woocommerce.md`) — there\n> the **SKU is the idempotency key** and coupons + product categories are part of the catalog.\n```\n\nGuidance:\n\n- **Fixed vs. prompt.** Title and Description are concrete values. Navigation, Pages and Articles are\n prompts because their volume/wording is generated — keep them as prompts so the push phase (or a\n rerun) can regenerate consistently.\n- **Run the prompts now.** Generate the actual page copy and article drafts during the wait — this is\n the slowest work and the whole reason it runs in parallel. Images depend on the chosen source (see\n *Images* below): Unsplash photos and your own generated files can be produced now; Hostinger AI\n images only generate once the instance exists, so write their brief now and generate at the barrier.\n Put any generated/own media in `~/.hostinger/<domain>/media/` and reference it by filename. Record\n alt text for every image.\n- **Optional sections.** Drop **Articles** if the brief has no article-like content. Drop **Products**\n for a non-commerce brief; for a commerce brief, generate the product catalog here (names, prices,\n descriptions, collections, images) — that's prepare work, and the `ecommerce` push phase then creates\n it on the store.\n- **Don't block on design.** The design step (5) owns the look, and it runs in parallel during the\n provision wait, so never wait for it here. If `design.md` already exists when you source images,\n follow its **Imagery** section; otherwise generate neutral, on-topic imagery from the brief.\n\n### Images — generate with Hostinger AI, or source stock from Unsplash\n\nEvery article (and every product) needs an image. There are two sources, and **you MUST offer the user\nboth and let them choose — never pick silently.** State the default for this backend in one line, name\nthe other option, and proceed on their answer (\"you decide\" = use the default; a skipped tool result is\nnot an answer, see *When you ask the user* in the top-level `SKILL.md`):\n\n| Option | Tool | Backends | Needs the live instance? |\n|---|---|---|---|\n| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |\n| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API / web search | any | no — pure prepare work |\n\n> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is\n> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build\n> is only correct when **the user chose it**, or when **AI generation actually failed** (and then you say\n> so). Taking stock because it is faster, synchronous, or avoids the async poll is a **defect**, not a\n> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not\n> an optional extra. If you are tempted to skip it \"for speed\", stop and offer the user the choice instead.\n\nIf you can generate images yourself (some agents can), that's a third path: put the files in `media/`\nand record alt text — no tool needed. **Record alt text for every image whichever source you use.**\n\n**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the\noption (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the\nphoto now (below); Option A only generates once the instance exists, so its briefs wait for the barrier.\n\n#### Option A — Hostinger AI image generation (WordPress backends)\n\n`wordpress_ai-content_generate-images` generates on-brand images for a live WordPress install, so it\nruns **at the instance barrier** (after `provision.status == \"completed\"`), using the install from\n`site.json` (`username`, and `wordpress.install_id` → the `software` path param). Follow `design.md`'s\n**Imagery** section for look and palette. It is **asynchronous**:\n\n1. **Start** one generation per image:\n\n ```json\n { \"operation\": \"wordpress_ai-content_generate-images\",\n \"params\": {\n \"username\": \"<site.json username>\",\n \"software\": \"<site.json wordpress.install_id>\",\n \"prompt\": \"<what the image shows; for a logo, the wordmark + mark to render>\",\n \"image_type\": \"hero|background|inline|gallery|product|logo\",\n \"num_of_images\": 1,\n \"style\": \"<photographic style tokens from design.md; ignored for logo>\",\n \"color_guidance\": \"<logo only — colour direction, not rendered as text>\" } }\n ```\n\n `prompt` (3–2500 chars) and `image_type` are required; `width`/`height` (128–2048) default per type\n and are ignored for `logo`. Returns `202` with a `generation_id` and `status: in-progress`.\n **Only one generation runs per install at a time** — a `409` means one is already running (and it\n returns no id), so wait for it to finish before starting the next. The endpoint allows 10\n requests/minute.\n\n2. **Poll** `wordpress_ai-content_generation-result` with `{ username, software, generation_id }`\n every few seconds until `status` is `completed` (or `failed`). A completed result carries\n `result.images[]` — each with a **temporary** `url`, plus `width`, `height`, `content_type` — and\n `result.expires_at`.\n\n3. **Use the images before they expire.** The URLs are short-lived, so in the push phase hand each\n `url` straight to WordPress with `media-upload-from-url`, which downloads and stores WP's own copy\n (see the wordpress push `SKILL.md`). Don't persist the temporary URL as the live image. There is no\n photographer to credit — leave `caption` empty or a short on-brand one. Record under the\n article/product in `content-model.md`:\n\n ```markdown\n Image: Hostinger AI (image_type: <type>, generation_id: <uuid>)\n Image alt: <what the image shows>\n Image file: <site>-<subject>.jpg\n ```\n\n On `failed`, retry once with a tightened prompt; if it fails again, fall back to Option B and note\n the gap. Never block the whole push on a single image.\n\n#### Option B — stock photos from Unsplash (any backend, no instance)\n\nThis is still prepare work and needs no live backend: you pick the photos and record their URLs here,\nand the push phase uploads them.\n\n1. **Find candidates.** Use one photo per article, matching its subject and destination.\n - With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API:\n `GET https://api.unsplash.com/search/photos?query=<subject>&orientation=landscape` with header\n `Authorization: Client-ID <key>`.\n - Without a key, use web search limited to `unsplash.com` (e.g. \"unsplash photo Lisbon tram\") and\n take the photo id from the result URL: the last segment of `unsplash.com/photos/<slug>-<id>`, or\n the whole segment for `unsplash.com/photos/<id>`.\n - Don't call `unsplash.com/napi/…`. It sits behind a bot challenge and returns 401.\n2. **Resolve a direct file URL.** `GET https://unsplash.com/photos/<id>/download?force=true` (don't\n follow the redirect) answers `302` with `Location: https://images.unsplash.com/photo-<…>?…`. The\n `dl=` parameter in it also carries the photographer's name. Drop the query string and request a\n web size: `https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`. The upload ability\n needs this direct file URL, which returns 200 with `image/jpeg`. A photo *page* URL won't work.\n3. **Look before you keep it.** Download a small preview (`?w=500`) and view it. Search snippets\n only show captions, and a photo often isn't quite what its title says. Reject anything off-topic.\n If nothing fits exactly, take the closest real photo and note the gap rather than mislabel it.\n4. **Write alt text from what the photo shows**, not from the article's image brief. For example,\n use \"Shopfronts and parked scooters on a Hanoi street\" even if the article is about rain.\n5. **Record it** under each article in `content-model.md`:\n\n ```markdown\n Image: https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max\n Image credit: <Photographer> — https://unsplash.com/photos/<id> (Unsplash License)\n Image alt: <what the photo shows>\n Image file: <site>-<subject>.jpg\n ```\n\nUnsplash photos are free to use under the Unsplash License. Still credit the photographer: the push\nphase stores \"Photo by <Photographer> on Unsplash\" as the image caption.\n\nThen update `state.json`: set `stage` to the next prepare stage still to do, fresh `updated_at`. Do\n**not** advance past the barrier here.\n\n---\n\n## Push phase — create it the backend's way (needs the instance)\n\nOnly once `provision.status == \"completed\"` (re-verified against `hosting_websites_list-setups`),\nroute on the brief's **Suggested backend** and follow that subfolder, which maps the helper onto the\nlive backend — for WordPress, through the per-site WP MCP's ability tools (the installation JWT\nauthenticates that MCP session; `wp/v2` REST is the public read surface, not a write path for the\ntoken) to create the menu, pages, posts, categories and media:\n\n| Suggested backend | Follow |\n|---|---|\n| WordPress | `wordpress/SKILL.md` |\n| WordPress + WooCommerce | `wordpress/SKILL.md` — pushes WP content **and** seeds the WooCommerce catalog (its WooCommerce section + `wordpress/woocommerce.md`) |\n| Ecommerce | `ecommerce/SKILL.md` |\n| Webapp | — (no push; skip to frontend) |\n\n> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce`, and `webapp`** —\n> where `webapp` is \"supported\" by **skipping this whole stage** (it has no content backend; see the note\n> below). A **WordPress + WooCommerce** brief routes to `wordpress/SKILL.md`, which pushes the WP content\n> **and** seeds the WooCommerce catalog in the same push. PocketBase is not available in this version.\n> Push to whichever single backend provision set up (see the provision router) — don't try to push to two\n> backends here.\n>\n> **Webapp has no content backend.** There is no CMS or store to seed, so a `webapp` brief **skips this\n> whole stage** — both prepare and push. The app owns its own data (its own API or database). Leave\n> `content-model.md` unwritten, mark content-model `skipped` in the run log, and go straight to the\n> frontend step.\n\nAfter the push completes, update `state.json` and continue to the frontend **data** phase.\n"
|
|
196
|
+
"text": "---\nname: headless-step-content-model\ndescription: \"Step 4 (content model) of the Hostinger Headless flow. Prepare phase: turns brief.md into a content helper (content-model.md) — fixed Title/Description plus a tailored generation prompt per section (Navigation, Pages, Articles) — and generates the drafts/images, no live backend needed. Push phase: routes to the backend subfolder to create it the backend's way once provision is ready.\"\n---\n\n# headless-step-content-model\n\nTwo phases around the **instance barrier** (see the top-level `SKILL.md`):\n\n- **Prepare — no instance.** Build the content helper from `brief.md` and generate the content it\n calls for. Runs *during* the provision wait; needs only the brief.\n- **Push — needs the instance.** Create that content in the backend. Gated on\n `provision.status == \"completed\"`; routes to `wordpress/` or `ecommerce/`.\n\n**In:** `brief.md`. **Out:** `content-model.md` (+ any generated media under\n`~/.hostinger/<domain>/media/`).\n\n> Don't wait for provisioning — the helper comes from the brief alone.\n\n---\n\n## Prepare phase — write the content helper (no instance)\n\n`content-model.md` is a lightweight helper, not a schema. Fill the **fixed values** from the brief and\nwrite a **generation prompt** for each section — tailored to *this* site, not generic. Then run those\nprompts to produce the drafts (and images), so the push phase is just upload.\n\nWrite `content-model.md` from this template, adapting every prompt to the brief's purpose, audience\nand tone:\n\n```markdown\n# Content — <domain>\n\n> Content helper prepared from brief.md on <today>. Fixed values are set; each prompt is run\n> during the provision wait to generate the content and media. Push to the backend happens later.\n\n## Title\n<site name, from the brief>\n\n## Description\n<one–two line site description / default meta, from the brief>\n\n## Navigation\n> Generate the primary menu from the site's pages in reading order (<list the brief's pages>).\n> Short labels; note which items are static pages vs. listing pages. Add a footer menu if the\n> brief implies one (socials, contact).\n\n## Pages\n> Generate each page the brief lists. For every page: title, slug, a short section outline, the\n> copy for static pages (e.g. About, Contact), and SEO title + description.\n> <bullet the brief's pages with their one-line purpose>\n\n## Articles (optional)\n> Generate <N> articles across the brief's categories (<list them>). For each: title, slug,\n> body in the brief's tone, category, any relationship (e.g. destination), published date, SEO,\n> and a featured image (see *Images* below — Hostinger AI generation or Unsplash stock) with\n> alt text. Omit if the brief has no article-like content.\n\n## Products (optional — required for a commerce brief)\n> Skip for a non-commerce brief. For a commerce brief, generate the catalog content here (prepare\n> work, no instance): for each product — name, slug, SKU, short + long description in the brief's\n> tone, price, the collection it belongs to, variants if any, and a product image (see *Images* —\n> Hostinger AI generation or Unsplash stock) with alt text. Group products into the collections\n> the brief implies, and generate any **coupons** the brief calls for (code, type, amount). The\n> `ecommerce` push phase creates these on the store's sales channel; a **WordPress + WooCommerce** brief\n> seeds them into WooCommerce instead (see `wordpress/SKILL.md` + `wordpress/woocommerce.md`) — there\n> the **SKU is the idempotency key** and coupons + product categories are part of the catalog.\n```\n\nGuidance:\n\n- **Fixed vs. prompt.** Title and Description are concrete values. Navigation, Pages and Articles are\n prompts because their volume/wording is generated — keep them as prompts so the push phase (or a\n rerun) can regenerate consistently.\n- **Run the prompts now.** Generate the actual page copy and article drafts during the wait — this is\n the slowest work and the whole reason it runs in parallel. Images depend on the chosen source (see\n *Images* below): Unsplash photos and your own generated files can be produced now; Hostinger AI\n images only generate once the instance exists, so write their brief now and generate at the barrier.\n Put any generated/own media in `~/.hostinger/<domain>/media/` and reference it by filename. Record\n alt text for every image.\n- **Optional sections.** Drop **Articles** if the brief has no article-like content. Drop **Products**\n for a non-commerce brief; for a commerce brief, generate the product catalog here (names, prices,\n descriptions, collections, images) — that's prepare work, and the `ecommerce` push phase then creates\n it on the store.\n- **Don't block on design.** The design step (5) owns the look, and it runs in parallel during the\n provision wait, so never wait for it here. If `design.md` already exists when you source images,\n follow its **Imagery** section; otherwise generate neutral, on-topic imagery from the brief.\n\n### Images — generate with Hostinger AI, or source stock from Unsplash\n\nEvery article (and every product) needs an image. There are two sources, and **you MUST offer the user\nboth and let them choose — never pick silently.** State the default for this backend in one line, name\nthe other option, and proceed on their answer (\"you decide\" = use the default; a skipped tool result is\nnot an answer, see *When you ask the user* in the top-level `SKILL.md`):\n\n| Option | Tool | Backends | Needs the live instance? |\n|---|---|---|---|\n| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |\n| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API, or web search + web fetch | any | no — pure prepare work |\n\n> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is\n> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build\n> is only correct when **the user chose it**, or when **AI generation actually failed** (and then you say\n> so). Taking stock because it is faster, synchronous, or avoids the async poll is a **defect**, not a\n> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not\n> an optional extra. If you are tempted to skip it \"for speed\", stop and offer the user the choice instead.\n\nIf you can generate images yourself (some agents can), offer that as a third option; only if the user\npicks it, put the files in `media/` (no tool needed). **Record alt text for every image whichever source\nyou use.**\n\n**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the\noption (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the\nphoto now (below); Option A only generates once the instance exists, so its briefs wait for the barrier.\n\n#### Option A — Hostinger AI image generation (WordPress backends)\n\n`wordpress_ai-content_generate-images` generates on-brand images for a live WordPress install, so it\nruns **at the instance barrier** (after `provision.status == \"completed\"`), using the install from\n`site.json` (`username`, and `wordpress.install_id` → the `software` path param). Follow `design.md`'s\n**Imagery** section for look and palette. It is **asynchronous**:\n\n1. **Start** one generation per image:\n\n ```json\n { \"operation\": \"wordpress_ai-content_generate-images\",\n \"params\": {\n \"username\": \"<site.json username>\",\n \"software\": \"<site.json wordpress.install_id>\",\n \"prompt\": \"<what the image shows; for a logo, the wordmark + mark to render>\",\n \"image_type\": \"hero|background|inline|gallery|product|logo\",\n \"num_of_images\": 1,\n \"style\": \"<photographic style tokens from design.md; ignored for logo>\",\n \"color_guidance\": \"<logo only — colour direction, not rendered as text>\" } }\n ```\n\n `prompt` (3–2500 chars) and `image_type` are required; `width`/`height` (128–2048) default per type\n and are ignored for `logo`. Returns `202` with a `generation_id` and `status: in-progress`.\n **Only one generation runs per install at a time** — a `409` means one is already running (and it\n returns no id), so wait for it to finish before starting the next. The endpoint allows 10\n requests/minute.\n\n2. **Poll** `wordpress_ai-content_generation-result` with `{ username, software, generation_id }`\n every few seconds until `status` is `completed` (or `failed`). A completed result carries\n `result.images[]` — each with a **temporary** `url`, plus `width`, `height`, `content_type` — and\n `result.expires_at`.\n\n3. **Use the images before they expire.** The URLs are short-lived, so in the push phase hand each\n `url` straight to WordPress with `media-upload-from-url`, which downloads and stores WP's own copy\n (see the wordpress push `SKILL.md`). Don't persist the temporary URL as the live image. There is no\n photographer to credit — leave `caption` empty or a short on-brand one. Record under the\n article/product in `content-model.md`:\n\n ```markdown\n Image: Hostinger AI (image_type: <type>, generation_id: <uuid>)\n Image alt: <what the image shows>\n Image file: <site>-<subject>.jpg\n ```\n\n On `failed`, retry once with a tightened prompt; if it fails again, fall back to Option B and note\n the gap. Never block the whole push on a single image.\n\n#### Option B — stock photos from Unsplash (any backend, no instance)\n\nThis is still prepare work and needs no live backend: you pick the photos and record their URLs here,\nand the push phase uploads them.\n\n1. **Find candidates.** Use one photo per article, matching its subject and destination.\n - With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API:\n `GET https://api.unsplash.com/search/photos?query=<subject>&orientation=landscape` with header\n `Authorization: Client-ID <key>`.\n - Without a key, use web search limited to `unsplash.com` (e.g. \"unsplash photo Lisbon tram\") and\n take the photo id from the result URL: the last segment of `unsplash.com/photos/<slug>-<id>`, or\n the whole segment for `unsplash.com/photos/<id>`.\n2. **Resolve a direct file URL.** With the API, the result's `urls.raw` is the file and `user.name`\n the photographer; for each photo you keep, also `GET` its `links.download_location` (same header),\n as the API terms require. Without a key, open the photo page (`https://unsplash.com/photos/<…>`)\n with your **web-fetch tool** and ask it for the main photo's `images.unsplash.com/photo-<…>` URL,\n quoted exactly, and the photographer's name. Either way, drop the query string and request a web\n size: `https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max`. Check that it returns\n 200 with `image/jpeg`: the upload ability needs this direct file URL, not the photo page.\n - Every other `unsplash.com` request (curl on the page, `/photos/<id>/download`, `/napi/…`,\n `/oembed`) hits a bot challenge: a `307` to `/.within.website` (Anubis) or a 401. If your fetch\n tool gets the challenge page too, stop: no user-agent spoofing or other workarounds. On a\n WordPress backend where Option A hasn't already failed, offer it; otherwise ask the user for an\n `UNSPLASH_ACCESS_KEY` or record the gap (step 5).\n3. **Look before you keep it.** Download a small preview (`?w=500`) and view it. Search snippets and\n fetch summaries only describe the page, and can hand you a related photo's thumbnail instead of\n the main one; a photo also often isn't quite what its title says. Reject anything off-topic.\n If nothing fits exactly, take the closest real photo and note the gap rather than mislabel it.\n4. **Write alt text from what the photo shows**, not from the article's image brief. For example,\n use \"Shopfronts and parked scooters on a Hanoi street\" even if the article is about rain.\n5. **Record it** under each article in `content-model.md`:\n\n ```markdown\n Image: https://images.unsplash.com/photo-<…>?w=1600&q=80&fm=jpg&fit=max\n Image credit: <Photographer> — https://unsplash.com/photos/<id> (Unsplash License)\n Image alt: <what the photo shows>\n Image file: <site>-<subject>.jpg\n ```\n\n If you can't get a real photo, write `Image gap: <reason>` in place of these lines and tell the\n user. Never substitute drawn or generated images without saying so.\n\nUnsplash photos are free to use under the Unsplash License. Still credit the photographer: the push\nphase stores \"Photo by <Photographer> on Unsplash\" as the image caption.\n\nThen update `state.json`: set `stage` to the next prepare stage still to do, fresh `updated_at`. Do\n**not** advance past the barrier here.\n\n---\n\n## Push phase — create it the backend's way (needs the instance)\n\nOnly once `provision.status == \"completed\"` (re-verified against `hosting_websites_list-setups`),\nroute on the brief's **Suggested backend** and follow that subfolder, which maps the helper onto the\nlive backend — for WordPress, through the per-site WP MCP's ability tools (the installation JWT\nauthenticates that MCP session; `wp/v2` REST is the public read surface, not a write path for the\ntoken) to create the menu, pages, posts, categories and media:\n\n| Suggested backend | Follow |\n|---|---|\n| WordPress | `wordpress/SKILL.md` |\n| WordPress + WooCommerce | `wordpress/SKILL.md` — pushes WP content **and** seeds the WooCommerce catalog (its WooCommerce section + `wordpress/woocommerce.md`) |\n| Ecommerce | `ecommerce/SKILL.md` |\n| Webapp | — (no push; skip to frontend) |\n\n> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce`, and `webapp`** —\n> where `webapp` is \"supported\" by **skipping this whole stage** (it has no content backend; see the note\n> below). A **WordPress + WooCommerce** brief routes to `wordpress/SKILL.md`, which pushes the WP content\n> **and** seeds the WooCommerce catalog in the same push. PocketBase is not available in this version.\n> Push to whichever single backend provision set up (see the provision router) — don't try to push to two\n> backends here.\n>\n> **Webapp has no content backend.** There is no CMS or store to seed, so a `webapp` brief **skips this\n> whole stage** — both prepare and push. The app owns its own data (its own API or database). Leave\n> `content-model.md` unwritten, mark content-model `skipped` in the run log, and go straight to the\n> frontend step.\n\nAfter the push completes, update `state.json` and continue to the frontend **data** phase.\n"
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
"uri": "skill://hostinger-headless/steps/04-content-model/ecommerce/SKILL.md",
|
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/agency-hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.agency-hosting.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.2', tools, instructions });
|
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/agency-hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.agency-hosting.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.2', tools, instructions });
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
import { startServer } from '../core/runtime.js';
|
|
4
4
|
import tools from '../core/tools/agent-readiness.js';
|
|
5
5
|
|
|
6
|
-
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.2', tools });
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
import { startServer } from '../core/runtime.js';
|
|
4
4
|
import tools from '../core/tools/agent-readiness.js';
|
|
5
5
|
|
|
6
|
-
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.2', tools });
|
package/src/servers/all.js
CHANGED
|
@@ -5,4 +5,4 @@ import tools from '../core/tools/all.js';
|
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
import skills from '../core/skills.js';
|
|
7
7
|
|
|
8
|
-
startServer({ name: 'hostinger-api-mcp', version: '3.15.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.15.2', tools, instructions, skills });
|
package/src/servers/all.ts
CHANGED
|
@@ -5,4 +5,4 @@ import tools from '../core/tools/all.js';
|
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
import skills from '../core/skills.js';
|
|
7
7
|
|
|
8
|
-
startServer({ name: 'hostinger-api-mcp', version: '3.15.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.15.2', tools, instructions, skills });
|
package/src/servers/billing.js
CHANGED
package/src/servers/billing.ts
CHANGED
package/src/servers/dns.js
CHANGED
package/src/servers/dns.ts
CHANGED
package/src/servers/domains.js
CHANGED
package/src/servers/domains.ts
CHANGED
package/src/servers/ecommerce.js
CHANGED
package/src/servers/ecommerce.ts
CHANGED
package/src/servers/horizons.js
CHANGED
package/src/servers/horizons.ts
CHANGED
package/src/servers/hosting.js
CHANGED
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-hosting-mcp', version: '3.15.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.15.2', tools, instructions });
|
package/src/servers/hosting.ts
CHANGED
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-hosting-mcp', version: '3.15.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.15.2', tools, instructions });
|
package/src/servers/mail.js
CHANGED
package/src/servers/mail.ts
CHANGED
package/src/servers/reach.js
CHANGED
package/src/servers/reach.ts
CHANGED
package/src/servers/vps.js
CHANGED
package/src/servers/vps.ts
CHANGED
package/src/servers/wordpress.js
CHANGED
package/src/servers/wordpress.ts
CHANGED