hostinger-api-mcp 3.12.2 → 3.13.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 +8 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/deploy-to-hosting/SKILL.md +15 -1
- package/skills/headless/steps/08-deploy/SKILL.md +7 -0
- package/src/core/instructions.agency-hosting.js +1 -1
- package/src/core/instructions.agency-hosting.ts +1 -1
- package/src/core/instructions.js +1 -1
- package/src/core/instructions.ts +1 -1
- package/src/core/skills.js +6 -6
- package/src/core/skills.ts +6 -6
- 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,14 @@ 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.13.0 — 2026-10-27
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Bundled agent skills: `skills/deploy-to-hosting/SKILL.md`, `skills/headless/steps/08-deploy/SKILL.md`
|
|
12
|
+
- Server instructions: 4 files
|
|
13
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
14
|
+
|
|
7
15
|
## v3.12.2 — 2026-10-27
|
|
8
16
|
|
|
9
17
|
### 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.13.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "3.
|
|
14
|
+
"version": "3.13.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -13,9 +13,23 @@ Ship a project that already exists — on disk or in a Git repository — to a H
|
|
|
13
13
|
- Batch reads with `multi-execute`; values pass between steps as `$steps.<i>.<path>`. Batches chain operations from one server only — the Hostinger Connector runs `hosting`, `wordpress` and `agency-hosting` as separate servers.
|
|
14
14
|
- Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.
|
|
15
15
|
|
|
16
|
+
## 0. If a shell and the Hostinger CLI are available
|
|
17
|
+
|
|
18
|
+
`hostinger up` does steps 1 to 3 in one call and is the shortest path when you can run commands and the `hostinger` CLI is installed (`brew install hostinger/tap/hostinger`), signed in and has `hostinger up`: it finds or creates the website, detects the type, packs the directory (honouring `.gitignore`; skipping `node_modules`, `.git`, `.env` files, credentials and private keys, and listing symlinks it does not follow), uploads, deploys or builds, waits for the build and links the directory in `.hostinger/site.json`, the same file this skill uses. Nothing is asked without a terminal.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
hostinger up <dir> --domain <domain> --yes --format json # existing website; --yes only after the user agreed to replace its content
|
|
22
|
+
hostinger up <dir> --create --format json # new website on the only plan, free subdomain
|
|
23
|
+
hostinger up <dir> --domain <domain> --dry-run # the plan as JSON, changes nothing: show it to the user before a first deploy (overwritesContent: the real run needs --yes)
|
|
24
|
+
hostinger up <dir> --domain <domain> --type nodejs --app-type next --output-directory .next --build-script build --node-version 22 --yes --format json
|
|
25
|
+
hostinger up <dir> --domain <domain> --no-wait --format json # returns buildUuid at once; poll with `hostinger hosting nodejs build <domain> <uuid>`
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run it from the project directory: the link is written in the current directory, with `path` naming `<dir>`. The JSON result has `url`, `data.type`, `data.buildUuid`, `data.buildState`, `data.skippedFiles` and, on failure, `error`. Shared and Cloud plans only; for Agency plans, archives and WordPress plugins or themes, use the operations below. The MCP's `hosting_deploy` does the same work when there is no shell.
|
|
29
|
+
|
|
16
30
|
## 1. Pick the target website
|
|
17
31
|
|
|
18
|
-
- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`).
|
|
32
|
+
- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`). The CLI's `hostinger up` and `hostinger link` write the same file and keep any fields you add while it names the same website (linking another website starts it afresh); its `type` may be `static`, `nodejs` or `php`.
|
|
19
33
|
- Otherwise look the domain up with `hosting_websites_list` (Shared and Cloud; substring match — take the exact entry) and `agency-hosting_websites_list-plan` (Agency; keep `details.uid`).
|
|
20
34
|
- No website yet:
|
|
21
35
|
- Shared and Cloud: take a domain (the user's own, or `hosting_domains_generate-free-subdomain`) and an `order_id` from `hosting_orders_list`, then `hosting_websites_create`. `datacenter_code` is needed only for the first website on a new plan (first entry of `hosting_datacenters_list`). Poll `hosting_websites_list-setups` with `domain` every 10–15 s until `status: completed` — uploads and deploys return 404 or 409 before that.
|
|
@@ -17,6 +17,13 @@ This is an **instance-barrier** step: run it only after `provision.status == "co
|
|
|
17
17
|
> `hostinger-api-mcp` server instead (see the connect step:
|
|
18
18
|
> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).
|
|
19
19
|
|
|
20
|
+
> **With a shell and the Hostinger CLI installed**, the same deploy is one command, with the build
|
|
21
|
+
> settings passed exactly as the table below says. Run it from the project, since it links the current directory:
|
|
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
|
+
> It uploads, builds, waits and returns `data.buildUuid` and `data.buildState`; add `--no-wait` to
|
|
24
|
+
> return at once and poll with `hostinger hosting nodejs build <domain> <uuid>`. Then continue at
|
|
25
|
+
> "Verify" below.
|
|
26
|
+
|
|
20
27
|
**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`
|
|
21
28
|
(`frontend.framework`, `frontend.rendering`).
|
|
22
29
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
2
|
+
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying from a shell\n- The Hostinger CLI deploys a website in one command and links the directory: `hostinger up <dir> --domain <domain> --format json` (`--yes` replaces content, `--create` makes a site, `--dry-run` shows the plan). It never prompts without a terminal.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
2
|
+
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying from a shell\n- The Hostinger CLI deploys a website in one command and links the directory: `hostinger up <dir> --domain <domain> --format json` (`--yes` replaces content, `--create` makes a site, `--dry-run` shows the plan). It never prompts without a terminal.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
3
3
|
export default instructions;
|
package/src/core/instructions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying a website (this server only)\n- hosting_deploy replaces the upload, archive-deploy, build, WordPress import/plugin/theme and Git operations named above. Give it a local directory (it detects static/PHP, Node.js, a WordPress site, plugin or theme and packs it itself), a local archive with `type`, or `git: { repository, branch }`. Do not run those operations by hand.\n- A Node.js deploy returns a buildUuid at once and builds on the server. Poll hosting_get-deployment with the domain, pausing longer each time, until the build state is completed or failed. A failed build comes back with its logs and analysis: fix the cause and deploy again. It replaces the build, log and settings operations.\n- Static, PHP, plugin and theme deploys are complete when hosting_deploy returns. A WordPress site import only starts: its files are extracted and the site is ready a few minutes later.\n- Every deploy overwrites the site's contents: confirm the domain with the user before the first deploy to a site that already has content.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
2
|
+
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying from a shell\n- The Hostinger CLI deploys a website in one command and links the directory: `hostinger up <dir> --domain <domain> --format json` (`--yes` replaces content, `--create` makes a site, `--dry-run` shows the plan). It never prompts without a terminal.\n\nDeploying a website (this server only)\n- hosting_deploy replaces the upload, archive-deploy, build, WordPress import/plugin/theme and Git operations named above. Give it a local directory (it detects static/PHP, Node.js, a WordPress site, plugin or theme and packs it itself), a local archive with `type`, or `git: { repository, branch }`. Do not run those operations by hand.\n- A Node.js deploy returns a buildUuid at once and builds on the server. Poll hosting_get-deployment with the domain, pausing longer each time, until the build state is completed or failed. A failed build comes back with its logs and analysis: fix the cause and deploy again. It replaces the build, log and settings operations.\n- Static, PHP, plugin and theme deploys are complete when hosting_deploy returns. A WordPress site import only starts: its files are extracted and the site is ready a few minutes later.\n- Every deploy overwrites the site's contents: confirm the domain with the user before the first deploy to a site that already has content.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
package/src/core/instructions.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying a website (this server only)\n- hosting_deploy replaces the upload, archive-deploy, build, WordPress import/plugin/theme and Git operations named above. Give it a local directory (it detects static/PHP, Node.js, a WordPress site, plugin or theme and packs it itself), a local archive with `type`, or `git: { repository, branch }`. Do not run those operations by hand.\n- A Node.js deploy returns a buildUuid at once and builds on the server. Poll hosting_get-deployment with the domain, pausing longer each time, until the build state is completed or failed. A failed build comes back with its logs and analysis: fix the cause and deploy again. It replaces the build, log and settings operations.\n- Static, PHP, plugin and theme deploys are complete when hosting_deploy returns. A WordPress site import only starts: its files are extracted and the site is ready a few minutes later.\n- Every deploy overwrites the site's contents: confirm the domain with the user before the first deploy to a site that already has content.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
2
|
+
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list-setups with the domain until status is completed (a few minutes); file, deploy and database calls return 404/409 before that.\n- A Node.js deploy or hosting_nodejs_start-build -> poll hosting_get-deployment with the domain until the build state is completed or failed; a failed build comes back with its logs and failure analysis.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nDeploying from a shell\n- The Hostinger CLI deploys a website in one command and links the directory: `hostinger up <dir> --domain <domain> --format json` (`--yes` replaces content, `--create` makes a site, `--dry-run` shows the plan). It never prompts without a terminal.\n\nDeploying a website (this server only)\n- hosting_deploy replaces the upload, archive-deploy, build, WordPress import/plugin/theme and Git operations named above. Give it a local directory (it detects static/PHP, Node.js, a WordPress site, plugin or theme and packs it itself), a local archive with `type`, or `git: { repository, branch }`. Do not run those operations by hand.\n- A Node.js deploy returns a buildUuid at once and builds on the server. Poll hosting_get-deployment with the domain, pausing longer each time, until the build state is completed or failed. A failed build comes back with its logs and analysis: fix the cause and deploy again. It replaces the build, log and settings operations.\n- Static, PHP, plugin and theme deploys are complete when hosting_deploy returns. A WordPress site import only starts: its files are extracted and the site is ready a few minutes later.\n- Every deploy overwrites the site's contents: confirm the domain with the user before the first deploy to a site that already has content.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
3
3
|
export default instructions;
|
package/src/core/skills.js
CHANGED
|
@@ -41,10 +41,10 @@ export default [
|
|
|
41
41
|
"resources": [
|
|
42
42
|
{
|
|
43
43
|
"uri": "skill://deploy-to-hosting/SKILL.md",
|
|
44
|
-
"digest": "sha256:
|
|
45
|
-
"size":
|
|
44
|
+
"digest": "sha256:88b2961fb48b2441c13e361d471117518b2afb4d0d49d143208e89bdf5971d22",
|
|
45
|
+
"size": 16056,
|
|
46
46
|
"mimeType": "text/markdown",
|
|
47
|
-
"text": "---\nname: deploy-to-hosting\ndescription: \"Deploy an existing project to a website on Hostinger web hosting (Shared, Cloud or Agency plans) and keep it deployed: picks the right deploy for static sites, Node.js apps (Next.js, Nuxt, Express, Vite and similar), PHP apps and WordPress plugins or themes, sets up auto-deploy from GitHub or GitLab, manages Node.js environment variables and MySQL databases, and verifies the live site. Triggers: deploy this project, deploy to Hostinger, push my app live, redeploy, set up auto-deploy, connect my GitHub repo, add environment variables, my app needs a database, deploy my WordPress plugin or theme.\"\n---\n\n# Deploy to hosting\n\nShip a project that already exists — on disk or in a Git repository — to a Hostinger website. Building a new site from a prompt (design, store, blog) is the `hostinger-headless` skill; this one takes code as it is.\n\n## Calling the operations\n\n- Read the `inputSchema` that `search` returns before the first call of each operation. If a name below is rejected as unknown, `search` for what the step does (e.g. \"deploy static\").\n- Batch reads with `multi-execute`; values pass between steps as `$steps.<i>.<path>`. Batches chain operations from one server only — the Hostinger Connector runs `hosting`, `wordpress` and `agency-hosting` as separate servers.\n- Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.\n\n## 1. Pick the target website\n\n- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`).\n- Otherwise look the domain up with `hosting_websites_list` (Shared and Cloud; substring match — take the exact entry) and `agency-hosting_websites_list-plan` (Agency; keep `details.uid`).\n- No website yet:\n - Shared and Cloud: take a domain (the user's own, or `hosting_domains_generate-free-subdomain`) and an `order_id` from `hosting_orders_list`, then `hosting_websites_create`. `datacenter_code` is needed only for the first website on a new plan (first entry of `hosting_datacenters_list`). Poll `hosting_websites_list-setups` with `domain` every 10–15 s until `status: completed` — uploads and deploys return 404 or 409 before that.\n - Agency: `agency-hosting_website-setups_create` with `flavor: \"php-fpm\"` (plus `type: \"node-static\"` for built frontends), `settings.php.version` from `agency-hosting_php_list-versions-for-order` and `datacenter_code` from `agency-hosting_datacenters_list`. Poll `agency-hosting_website-setups_status` until `completed`; it returns the `website_uid`.\n- Node.js apps run on Business and Cloud plans (`hostinger_business_*`, `cloud_*` in `hosting_orders_list`). On other plans, build locally and deploy the output as a static site.\n\n## 2. Choose the deploy\n\n**Shared and Cloud, on the local `hostinger-api-mcp` server:** one operation does every deploy — `hosting_deploy` with `domain` and either `path` (a directory on this machine) or `git: { repository: \"owner/name\", branch }`. Given a directory it detects what the project is, packs it itself (it skips `node_modules`, `.git` and `.env` files, listing the `.env` ones as `skippedFiles` so you can set their values with `hosting_nodejs_replace-environment-variables`; it does not read `.gitignore`, so keep build output out of the directory) and does the upload, build and Git setup. Do not archive the directory or call upload and build operations by hand.\n\n| Project | `hosting_deploy` treats it as |\n| --- | --- |\n| Plain HTML/CSS/JS, or a framework's static build output | `static` — extracts as-is |\n| `package.json` with a build script, a start script or a server dependency (Next.js, Nuxt, Express, Vite…) | `nodejs` — starts a build, returns `buildUuid` |\n| PHP code, no build step | `static` |\n| WordPress plugin folder (a `Plugin Name:` header) or theme folder (`Theme Name:` in `style.css`) | `wordpress-plugin` / `wordpress-theme`; `activate: true` activates a theme |\n| A whole WordPress site from elsewhere | `wordpress-site`, with `databaseDump` — see the `migrate-to-hosting` skill |\n\n- Pass `type` to override detection. It is required for an archive (an archive cannot be inspected) and when a directory matches more than one WordPress type, which `hosting_deploy` rejects as ambiguous.\n- Node.js build settings come from the project; detection covers the framework and the build script. Set `appType`, `outputDirectory`, `buildScript`, `entryFile`, `nodeVersion` (default 22), `rootDirectory` or `packageManager` when it is wrong. A TypeScript or other compiled server app always needs `entryFile` set to the built file (for example `dist/server.js`): detection only guesses a root-level file, and the build fails with \"Entry file not found\". The entry file is relative to the root directory for express, fastify, hono, react-router and other, and to the output directory for nest, nuxt, nitro, astro and svelte-kit (`main.js`, not `dist/main.js`); Next.js ignores it. Per-framework values are in the Hostinger docs under Node.js (Supported Frameworks, Build Settings).\n- Static, PHP, plugin and theme deploys are live when the call returns. A WordPress site import only starts (`status: import_started`) and finishes extracting a few minutes later; do not report it complete or verify it before then. Node.js builds run on the server: see step 3.\n\n**Agency:** the website's type decides (`agency-hosting_websites_get`): sites created as `node-static` take `agency-hosting_deploy-node-static-website`, which runs the build when the project has one; every other site takes `agency-hosting_deploy-php-application`, which extracts the archive as-is. They deploy archives, synchronously: the site is live when the call returns. WordPress plugins and themes on Agency sites are installed from wp-admin.\n\nArchive rules for Agency (name archives `name_YYYYMMDD_HHMMSS.zip`) and for archives you build yourself:\n\n- **Static:** build locally first. `index.html` must be at the archive root, not inside a folder: `cd dist && zip -r ../site_20260101_120000.zip .`\n- **Node.js source:** no `node_modules/`, no build output (`dist/`, `.next/`, `build/`), no `.env*`, nothing matched by `.gitignore`; 50 MB at most. `git archive --format=zip -o app_20260101_120000.zip HEAD` produces exactly the committed files — mention that uncommitted changes are left out.\n\n`hosting_deploy` with `path` and the `agency-hosting_deploy-*` operations read files from this machine, so only the local server has them. The hosted server (`mcp.hostinger.com`) has `hosting_deploy` for Git repositories only (step 4) and `hosting_get-deployment` (step 3); to deploy files from this machine there, upload them yourself as below.\n\n### Without the local deploy operations (local files on the hosted server; Agency on either server)\n\n1. Get upload credentials: `hosting_files_generate-upload-url` (`username`, `domain`); Agency: `agency-hosting_files_generate-upload-url` (`website_uid`). Both return `url`, `auth_key` and `rest_auth_key`, which authenticate the upload instead of the API token.\n2. Upload each file with TUS, where `DEST` is its path in the website's storage:\n\n```bash\nSIZE=$(wc -c < \"$FILE\" | tr -d ' ')\ncurl -sS -X POST \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Upload-Length: $SIZE\" -H \"Upload-Offset: 0\"\ncurl -sS -X PATCH \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Content-Type: application/offset+octet-stream\" -H \"Upload-Offset: 0\" --data-binary \"@$FILE\"\n```\n\n The first call returns `201`, the second `204` with an `Upload-Offset` header equal to the file size. `override=true` makes a retry safe.\n3. Deploy from the uploaded files (`RANDOM8` is any fresh 8-character string, e.g. `$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)`):\n\n| Project | `DEST` | Then |\n| --- | --- | --- |\n| Static or PHP | `site.zip` | `hosting_websites_deploy-static-site-archive` with `archive_path: \"site.zip\"` |\n| Node.js | `app.zip` | `hosting_nodejs_build-settings-from-archive` with `archive_path: \"app.zip\"`, then `hosting_nodejs_start-build` with those settings, `source_type: \"archive\"` and `source_options.archive_path: \"app.zip\"` |\n| WordPress plugin | every file, as `wp-content/plugins/SLUG-RANDOM8/<path in the plugin>` | `wordpress_plugins_deploy` with `slug` and `plugin_path: \"SLUG-RANDOM8\"` |\n| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/<path in the theme>` | `wordpress_themes_deploy` with `slug`, `theme_path: \"SLUG-RANDOM8\"` and optional `is_activated` |\n| Agency, extracted as-is | `.h5g/site.zip` | `agency-hosting_files_import-website-from-archive` with `archive_name: \"site.zip\"` |\n| Agency `node-static` | `.h5g/RANDOM8/app.zip` | `agency-hosting_websites_build-nodejs-assets` with `archive_path: \".h5g/RANDOM8\"` — the directory, not the file |\n\n## 3. Node.js builds\n\n`hosting_deploy` returns a `buildUuid` at once; the build runs on the server and takes minutes. Track it — and a build started with `hosting_nodejs_start-build` after an upload — with `hosting_get-deployment` (`domain`, optionally `buildUuid`; the latest build by default), polling every 10–20 s until `build.state` is `completed` or `failed`. It returns the build, its log and the stored build settings; pass the previous `logs.lines` as `fromLine` to read only new output.\n\n- Failed build: the response already carries `analysis` (fetched once per build; the API allows 5 a minute).\n- Wrong detection (framework, `output_directory`, or an entry file inside the build output, such as `dist/server.js` for a compiled app): deploy again with `appType`, `outputDirectory`, `buildScript` or `entryFile` set. An archive deploy sends its settings with the request and leaves the stored ones alone; a Git deploy also stores these values, so later pushes build with them.\n\nLocal files on the hosted server, deployed from an upload (step 2):\n\n- Wrong detection: run `hosting_nodejs_start-build` again with explicit values, `source_type: \"archive\"` and `source_options.archive_path` set to the archive's file name in the document root (check it is there with `hosting_files_list-website-and-directories`). Store the same values with `hosting_nodejs_update-build-settings`.\n- To review detected settings before any build: upload the archive with `hosting_files_generate-upload-url` (the TUS steps are in its description), then `hosting_nodejs_build-settings-from-archive`.\n\n## 4. Auto-deploy from Git\n\nOn both the local and the hosted server, `hosting_deploy` with `git: { repository: \"owner/name\", branch }` does all of it: it finds the Git installation that can reach the repository and deploys it as the `type` you give. Always pass `type: \"static\"` or `\"nodejs\"` unless the site already runs a Node.js app (or you pass Node.js settings such as `appType`): a new site is not detected as Node.js, and a Node.js repository deployed as static would publish its source. Then:\n- **PHP and static sites:** saves the auto-deployment settings, which deploys the branch immediately and again on every push;\n- **Node.js sites:** starts a build of the branch with the stored build settings; later pushes build the same way. When none are stored, they are detected from the branch's `package.json` and stored for those pushes; pass `appType`, `nodeVersion`, `outputDirectory`, `buildScript` or `entryFile` to override them (a compiled app still needs `entryFile`).\n\nWith no active installation it stops and says so: the user connects GitHub or GitLab once in hPanel under Websites → Manage → Advanced → Git; there is no API for that step. Confirm with `hosting_get-deployment`, which returns the stored `gitAutoDeployment`.\n\nAgency Plan sites have no Git operations; deploy archives.\n\n## 5. Environment variables (Node.js)\n\n`hosting_nodejs_replace-environment-variables` replaces the whole set — anything not sent is deleted.\n\n1. `hosting_nodejs_list-environment-variables` for the current keys. Values come back as `********`; never send those back.\n2. Build the full set: every current key plus the new ones, with real values from the project's `.env` or the user. When the real value of an existing key is unknown, ask — do not drop it.\n3. Send it once; the app restarts. Values baked in at build time (Next.js `NEXT_PUBLIC_*`, Vite `VITE_*`) need a new `hosting_nodejs_start-build`.\n\nKeys use uppercase letters, digits and underscores. Never commit `.env` files or repeat secret values back to the user.\n\n## 6. Database\n\n- **Node.js:** `hosting_databases_setup-website` creates a MySQL database and writes `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` into the environment, then restarts the app. The password is generated and never returned. It fails with 422 when any of those keys exists — remove them first with the step 5 replace.\n- **PHP:** `hosting_databases_create` with `name`, `user`, a strong generated `password` and `website_domain`; read the prefixed full name back from `hosting_databases_list`. The app reads credentials from a config file on the server that stays out of Git, and connects to `localhost` or `127.0.0.1`.\n- **Agency:** `agency-hosting_databases_create-website`; the app connects to `localhost`.\n- The `srvNNNN.hstgr.io` host from `hosting_databases_list` is only for connections from outside Hostinger — never put it in the deployed app.\n\n## 7. Verify and record\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://DOMAIN/\ncurl -s https://DOMAIN/ | grep -o \"TEXT THE PROJECT RENDERS\"\n```\n\nA `200` alone can be a placeholder page; match real copy. A new site may serve a default page briefly — clear it with `hosting_cache_clear-website` and retry before calling the deploy failed.\n\nWrite `.hostinger/site.json` in the project so later runs reuse the target: `{ \"domain\", \"username\" or \"website_uid\", \"type\": \"static\" | \"nodejs\" | \"php\" }`, keeping any fields already there.\n"
|
|
47
|
+
"text": "---\nname: deploy-to-hosting\ndescription: \"Deploy an existing project to a website on Hostinger web hosting (Shared, Cloud or Agency plans) and keep it deployed: picks the right deploy for static sites, Node.js apps (Next.js, Nuxt, Express, Vite and similar), PHP apps and WordPress plugins or themes, sets up auto-deploy from GitHub or GitLab, manages Node.js environment variables and MySQL databases, and verifies the live site. Triggers: deploy this project, deploy to Hostinger, push my app live, redeploy, set up auto-deploy, connect my GitHub repo, add environment variables, my app needs a database, deploy my WordPress plugin or theme.\"\n---\n\n# Deploy to hosting\n\nShip a project that already exists — on disk or in a Git repository — to a Hostinger website. Building a new site from a prompt (design, store, blog) is the `hostinger-headless` skill; this one takes code as it is.\n\n## Calling the operations\n\n- Read the `inputSchema` that `search` returns before the first call of each operation. If a name below is rejected as unknown, `search` for what the step does (e.g. \"deploy static\").\n- Batch reads with `multi-execute`; values pass between steps as `$steps.<i>.<path>`. Batches chain operations from one server only — the Hostinger Connector runs `hosting`, `wordpress` and `agency-hosting` as separate servers.\n- Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.\n\n## 0. If a shell and the Hostinger CLI are available\n\n`hostinger up` does steps 1 to 3 in one call and is the shortest path when you can run commands and the `hostinger` CLI is installed (`brew install hostinger/tap/hostinger`), signed in and has `hostinger up`: it finds or creates the website, detects the type, packs the directory (honouring `.gitignore`; skipping `node_modules`, `.git`, `.env` files, credentials and private keys, and listing symlinks it does not follow), uploads, deploys or builds, waits for the build and links the directory in `.hostinger/site.json`, the same file this skill uses. Nothing is asked without a terminal.\n\n```\nhostinger up <dir> --domain <domain> --yes --format json # existing website; --yes only after the user agreed to replace its content\nhostinger up <dir> --create --format json # new website on the only plan, free subdomain\nhostinger up <dir> --domain <domain> --dry-run # the plan as JSON, changes nothing: show it to the user before a first deploy (overwritesContent: the real run needs --yes)\nhostinger up <dir> --domain <domain> --type nodejs --app-type next --output-directory .next --build-script build --node-version 22 --yes --format json\nhostinger up <dir> --domain <domain> --no-wait --format json # returns buildUuid at once; poll with `hostinger hosting nodejs build <domain> <uuid>`\n```\n\nRun it from the project directory: the link is written in the current directory, with `path` naming `<dir>`. The JSON result has `url`, `data.type`, `data.buildUuid`, `data.buildState`, `data.skippedFiles` and, on failure, `error`. Shared and Cloud plans only; for Agency plans, archives and WordPress plugins or themes, use the operations below. The MCP's `hosting_deploy` does the same work when there is no shell.\n\n## 1. Pick the target website\n\n- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`). The CLI's `hostinger up` and `hostinger link` write the same file and keep any fields you add while it names the same website (linking another website starts it afresh); its `type` may be `static`, `nodejs` or `php`.\n- Otherwise look the domain up with `hosting_websites_list` (Shared and Cloud; substring match — take the exact entry) and `agency-hosting_websites_list-plan` (Agency; keep `details.uid`).\n- No website yet:\n - Shared and Cloud: take a domain (the user's own, or `hosting_domains_generate-free-subdomain`) and an `order_id` from `hosting_orders_list`, then `hosting_websites_create`. `datacenter_code` is needed only for the first website on a new plan (first entry of `hosting_datacenters_list`). Poll `hosting_websites_list-setups` with `domain` every 10–15 s until `status: completed` — uploads and deploys return 404 or 409 before that.\n - Agency: `agency-hosting_website-setups_create` with `flavor: \"php-fpm\"` (plus `type: \"node-static\"` for built frontends), `settings.php.version` from `agency-hosting_php_list-versions-for-order` and `datacenter_code` from `agency-hosting_datacenters_list`. Poll `agency-hosting_website-setups_status` until `completed`; it returns the `website_uid`.\n- Node.js apps run on Business and Cloud plans (`hostinger_business_*`, `cloud_*` in `hosting_orders_list`). On other plans, build locally and deploy the output as a static site.\n\n## 2. Choose the deploy\n\n**Shared and Cloud, on the local `hostinger-api-mcp` server:** one operation does every deploy — `hosting_deploy` with `domain` and either `path` (a directory on this machine) or `git: { repository: \"owner/name\", branch }`. Given a directory it detects what the project is, packs it itself (it skips `node_modules`, `.git` and `.env` files, listing the `.env` ones as `skippedFiles` so you can set their values with `hosting_nodejs_replace-environment-variables`; it does not read `.gitignore`, so keep build output out of the directory) and does the upload, build and Git setup. Do not archive the directory or call upload and build operations by hand.\n\n| Project | `hosting_deploy` treats it as |\n| --- | --- |\n| Plain HTML/CSS/JS, or a framework's static build output | `static` — extracts as-is |\n| `package.json` with a build script, a start script or a server dependency (Next.js, Nuxt, Express, Vite…) | `nodejs` — starts a build, returns `buildUuid` |\n| PHP code, no build step | `static` |\n| WordPress plugin folder (a `Plugin Name:` header) or theme folder (`Theme Name:` in `style.css`) | `wordpress-plugin` / `wordpress-theme`; `activate: true` activates a theme |\n| A whole WordPress site from elsewhere | `wordpress-site`, with `databaseDump` — see the `migrate-to-hosting` skill |\n\n- Pass `type` to override detection. It is required for an archive (an archive cannot be inspected) and when a directory matches more than one WordPress type, which `hosting_deploy` rejects as ambiguous.\n- Node.js build settings come from the project; detection covers the framework and the build script. Set `appType`, `outputDirectory`, `buildScript`, `entryFile`, `nodeVersion` (default 22), `rootDirectory` or `packageManager` when it is wrong. A TypeScript or other compiled server app always needs `entryFile` set to the built file (for example `dist/server.js`): detection only guesses a root-level file, and the build fails with \"Entry file not found\". The entry file is relative to the root directory for express, fastify, hono, react-router and other, and to the output directory for nest, nuxt, nitro, astro and svelte-kit (`main.js`, not `dist/main.js`); Next.js ignores it. Per-framework values are in the Hostinger docs under Node.js (Supported Frameworks, Build Settings).\n- Static, PHP, plugin and theme deploys are live when the call returns. A WordPress site import only starts (`status: import_started`) and finishes extracting a few minutes later; do not report it complete or verify it before then. Node.js builds run on the server: see step 3.\n\n**Agency:** the website's type decides (`agency-hosting_websites_get`): sites created as `node-static` take `agency-hosting_deploy-node-static-website`, which runs the build when the project has one; every other site takes `agency-hosting_deploy-php-application`, which extracts the archive as-is. They deploy archives, synchronously: the site is live when the call returns. WordPress plugins and themes on Agency sites are installed from wp-admin.\n\nArchive rules for Agency (name archives `name_YYYYMMDD_HHMMSS.zip`) and for archives you build yourself:\n\n- **Static:** build locally first. `index.html` must be at the archive root, not inside a folder: `cd dist && zip -r ../site_20260101_120000.zip .`\n- **Node.js source:** no `node_modules/`, no build output (`dist/`, `.next/`, `build/`), no `.env*`, nothing matched by `.gitignore`; 50 MB at most. `git archive --format=zip -o app_20260101_120000.zip HEAD` produces exactly the committed files — mention that uncommitted changes are left out.\n\n`hosting_deploy` with `path` and the `agency-hosting_deploy-*` operations read files from this machine, so only the local server has them. The hosted server (`mcp.hostinger.com`) has `hosting_deploy` for Git repositories only (step 4) and `hosting_get-deployment` (step 3); to deploy files from this machine there, upload them yourself as below.\n\n### Without the local deploy operations (local files on the hosted server; Agency on either server)\n\n1. Get upload credentials: `hosting_files_generate-upload-url` (`username`, `domain`); Agency: `agency-hosting_files_generate-upload-url` (`website_uid`). Both return `url`, `auth_key` and `rest_auth_key`, which authenticate the upload instead of the API token.\n2. Upload each file with TUS, where `DEST` is its path in the website's storage:\n\n```bash\nSIZE=$(wc -c < \"$FILE\" | tr -d ' ')\ncurl -sS -X POST \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Upload-Length: $SIZE\" -H \"Upload-Offset: 0\"\ncurl -sS -X PATCH \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Content-Type: application/offset+octet-stream\" -H \"Upload-Offset: 0\" --data-binary \"@$FILE\"\n```\n\n The first call returns `201`, the second `204` with an `Upload-Offset` header equal to the file size. `override=true` makes a retry safe.\n3. Deploy from the uploaded files (`RANDOM8` is any fresh 8-character string, e.g. `$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)`):\n\n| Project | `DEST` | Then |\n| --- | --- | --- |\n| Static or PHP | `site.zip` | `hosting_websites_deploy-static-site-archive` with `archive_path: \"site.zip\"` |\n| Node.js | `app.zip` | `hosting_nodejs_build-settings-from-archive` with `archive_path: \"app.zip\"`, then `hosting_nodejs_start-build` with those settings, `source_type: \"archive\"` and `source_options.archive_path: \"app.zip\"` |\n| WordPress plugin | every file, as `wp-content/plugins/SLUG-RANDOM8/<path in the plugin>` | `wordpress_plugins_deploy` with `slug` and `plugin_path: \"SLUG-RANDOM8\"` |\n| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/<path in the theme>` | `wordpress_themes_deploy` with `slug`, `theme_path: \"SLUG-RANDOM8\"` and optional `is_activated` |\n| Agency, extracted as-is | `.h5g/site.zip` | `agency-hosting_files_import-website-from-archive` with `archive_name: \"site.zip\"` |\n| Agency `node-static` | `.h5g/RANDOM8/app.zip` | `agency-hosting_websites_build-nodejs-assets` with `archive_path: \".h5g/RANDOM8\"` — the directory, not the file |\n\n## 3. Node.js builds\n\n`hosting_deploy` returns a `buildUuid` at once; the build runs on the server and takes minutes. Track it — and a build started with `hosting_nodejs_start-build` after an upload — with `hosting_get-deployment` (`domain`, optionally `buildUuid`; the latest build by default), polling every 10–20 s until `build.state` is `completed` or `failed`. It returns the build, its log and the stored build settings; pass the previous `logs.lines` as `fromLine` to read only new output.\n\n- Failed build: the response already carries `analysis` (fetched once per build; the API allows 5 a minute).\n- Wrong detection (framework, `output_directory`, or an entry file inside the build output, such as `dist/server.js` for a compiled app): deploy again with `appType`, `outputDirectory`, `buildScript` or `entryFile` set. An archive deploy sends its settings with the request and leaves the stored ones alone; a Git deploy also stores these values, so later pushes build with them.\n\nLocal files on the hosted server, deployed from an upload (step 2):\n\n- Wrong detection: run `hosting_nodejs_start-build` again with explicit values, `source_type: \"archive\"` and `source_options.archive_path` set to the archive's file name in the document root (check it is there with `hosting_files_list-website-and-directories`). Store the same values with `hosting_nodejs_update-build-settings`.\n- To review detected settings before any build: upload the archive with `hosting_files_generate-upload-url` (the TUS steps are in its description), then `hosting_nodejs_build-settings-from-archive`.\n\n## 4. Auto-deploy from Git\n\nOn both the local and the hosted server, `hosting_deploy` with `git: { repository: \"owner/name\", branch }` does all of it: it finds the Git installation that can reach the repository and deploys it as the `type` you give. Always pass `type: \"static\"` or `\"nodejs\"` unless the site already runs a Node.js app (or you pass Node.js settings such as `appType`): a new site is not detected as Node.js, and a Node.js repository deployed as static would publish its source. Then:\n- **PHP and static sites:** saves the auto-deployment settings, which deploys the branch immediately and again on every push;\n- **Node.js sites:** starts a build of the branch with the stored build settings; later pushes build the same way. When none are stored, they are detected from the branch's `package.json` and stored for those pushes; pass `appType`, `nodeVersion`, `outputDirectory`, `buildScript` or `entryFile` to override them (a compiled app still needs `entryFile`).\n\nWith no active installation it stops and says so: the user connects GitHub or GitLab once in hPanel under Websites → Manage → Advanced → Git; there is no API for that step. Confirm with `hosting_get-deployment`, which returns the stored `gitAutoDeployment`.\n\nAgency Plan sites have no Git operations; deploy archives.\n\n## 5. Environment variables (Node.js)\n\n`hosting_nodejs_replace-environment-variables` replaces the whole set — anything not sent is deleted.\n\n1. `hosting_nodejs_list-environment-variables` for the current keys. Values come back as `********`; never send those back.\n2. Build the full set: every current key plus the new ones, with real values from the project's `.env` or the user. When the real value of an existing key is unknown, ask — do not drop it.\n3. Send it once; the app restarts. Values baked in at build time (Next.js `NEXT_PUBLIC_*`, Vite `VITE_*`) need a new `hosting_nodejs_start-build`.\n\nKeys use uppercase letters, digits and underscores. Never commit `.env` files or repeat secret values back to the user.\n\n## 6. Database\n\n- **Node.js:** `hosting_databases_setup-website` creates a MySQL database and writes `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` into the environment, then restarts the app. The password is generated and never returned. It fails with 422 when any of those keys exists — remove them first with the step 5 replace.\n- **PHP:** `hosting_databases_create` with `name`, `user`, a strong generated `password` and `website_domain`; read the prefixed full name back from `hosting_databases_list`. The app reads credentials from a config file on the server that stays out of Git, and connects to `localhost` or `127.0.0.1`.\n- **Agency:** `agency-hosting_databases_create-website`; the app connects to `localhost`.\n- The `srvNNNN.hstgr.io` host from `hosting_databases_list` is only for connections from outside Hostinger — never put it in the deployed app.\n\n## 7. Verify and record\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://DOMAIN/\ncurl -s https://DOMAIN/ | grep -o \"TEXT THE PROJECT RENDERS\"\n```\n\nA `200` alone can be a placeholder page; match real copy. A new site may serve a default page briefly — clear it with `hosting_cache_clear-website` and retry before calling the deploy failed.\n\nWrite `.hostinger/site.json` in the project so later runs reuse the target: `{ \"domain\", \"username\" or \"website_uid\", \"type\": \"static\" | \"nodejs\" | \"php\" }`, keeping any fields already there.\n"
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
50
|
},
|
|
@@ -246,10 +246,10 @@ export default [
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
"uri": "skill://hostinger-headless/steps/08-deploy/SKILL.md",
|
|
249
|
-
"digest": "sha256:
|
|
250
|
-
"size":
|
|
249
|
+
"digest": "sha256:df610d6f851a4f2c8f22bf7ff23159dea5c6e836a139dae47716d2b472a59885",
|
|
250
|
+
"size": 12972,
|
|
251
251
|
"mimeType": "text/markdown",
|
|
252
|
-
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n> **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git\n> repositories only and cannot read a local `path`. Run this step through the local\n> `hostinger-api-mcp` server instead (see the connect step:\n> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)\n> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)\n> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts\n> with a \"glibc too old\" error — and Turbopack **does not fall back** to webpack on its own, so the\n> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script\n> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy\n> call stays `\"build\"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on\n> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).\n> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see\n> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`\n> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other\n> frameworks are unaffected.\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n - **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —\n the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it\n as plain `next build`. Fix it here if it isn't.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` on the local server is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
252
|
+
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n> **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git\n> repositories only and cannot read a local `path`. Run this step through the local\n> `hostinger-api-mcp` server instead (see the connect step:\n> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).\n\n> **With a shell and the Hostinger CLI installed**, the same deploy is one command, with the build\n> settings passed exactly as the table below says. Run it from the project, since it links the current directory:\n> `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`.\n> It uploads, builds, waits and returns `data.buildUuid` and `data.buildState`; add `--no-wait` to\n> return at once and poll with `hostinger hosting nodejs build <domain> <uuid>`. Then continue at\n> \"Verify\" below.\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)\n> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)\n> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts\n> with a \"glibc too old\" error — and Turbopack **does not fall back** to webpack on its own, so the\n> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script\n> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy\n> call stays `\"build\"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on\n> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).\n> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see\n> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`\n> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other\n> frameworks are unaffected.\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n - **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —\n the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it\n as plain `next build`. Fix it here if it isn't.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` on the local server is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
"uri": "skill://hostinger-headless/steps/09-done/SKILL.md",
|
package/src/core/skills.ts
CHANGED
|
@@ -55,10 +55,10 @@ const skills: Skill[] = [
|
|
|
55
55
|
"resources": [
|
|
56
56
|
{
|
|
57
57
|
"uri": "skill://deploy-to-hosting/SKILL.md",
|
|
58
|
-
"digest": "sha256:
|
|
59
|
-
"size":
|
|
58
|
+
"digest": "sha256:88b2961fb48b2441c13e361d471117518b2afb4d0d49d143208e89bdf5971d22",
|
|
59
|
+
"size": 16056,
|
|
60
60
|
"mimeType": "text/markdown",
|
|
61
|
-
"text": "---\nname: deploy-to-hosting\ndescription: \"Deploy an existing project to a website on Hostinger web hosting (Shared, Cloud or Agency plans) and keep it deployed: picks the right deploy for static sites, Node.js apps (Next.js, Nuxt, Express, Vite and similar), PHP apps and WordPress plugins or themes, sets up auto-deploy from GitHub or GitLab, manages Node.js environment variables and MySQL databases, and verifies the live site. Triggers: deploy this project, deploy to Hostinger, push my app live, redeploy, set up auto-deploy, connect my GitHub repo, add environment variables, my app needs a database, deploy my WordPress plugin or theme.\"\n---\n\n# Deploy to hosting\n\nShip a project that already exists — on disk or in a Git repository — to a Hostinger website. Building a new site from a prompt (design, store, blog) is the `hostinger-headless` skill; this one takes code as it is.\n\n## Calling the operations\n\n- Read the `inputSchema` that `search` returns before the first call of each operation. If a name below is rejected as unknown, `search` for what the step does (e.g. \"deploy static\").\n- Batch reads with `multi-execute`; values pass between steps as `$steps.<i>.<path>`. Batches chain operations from one server only — the Hostinger Connector runs `hosting`, `wordpress` and `agency-hosting` as separate servers.\n- Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.\n\n## 1. Pick the target website\n\n- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`).\n- Otherwise look the domain up with `hosting_websites_list` (Shared and Cloud; substring match — take the exact entry) and `agency-hosting_websites_list-plan` (Agency; keep `details.uid`).\n- No website yet:\n - Shared and Cloud: take a domain (the user's own, or `hosting_domains_generate-free-subdomain`) and an `order_id` from `hosting_orders_list`, then `hosting_websites_create`. `datacenter_code` is needed only for the first website on a new plan (first entry of `hosting_datacenters_list`). Poll `hosting_websites_list-setups` with `domain` every 10–15 s until `status: completed` — uploads and deploys return 404 or 409 before that.\n - Agency: `agency-hosting_website-setups_create` with `flavor: \"php-fpm\"` (plus `type: \"node-static\"` for built frontends), `settings.php.version` from `agency-hosting_php_list-versions-for-order` and `datacenter_code` from `agency-hosting_datacenters_list`. Poll `agency-hosting_website-setups_status` until `completed`; it returns the `website_uid`.\n- Node.js apps run on Business and Cloud plans (`hostinger_business_*`, `cloud_*` in `hosting_orders_list`). On other plans, build locally and deploy the output as a static site.\n\n## 2. Choose the deploy\n\n**Shared and Cloud, on the local `hostinger-api-mcp` server:** one operation does every deploy — `hosting_deploy` with `domain` and either `path` (a directory on this machine) or `git: { repository: \"owner/name\", branch }`. Given a directory it detects what the project is, packs it itself (it skips `node_modules`, `.git` and `.env` files, listing the `.env` ones as `skippedFiles` so you can set their values with `hosting_nodejs_replace-environment-variables`; it does not read `.gitignore`, so keep build output out of the directory) and does the upload, build and Git setup. Do not archive the directory or call upload and build operations by hand.\n\n| Project | `hosting_deploy` treats it as |\n| --- | --- |\n| Plain HTML/CSS/JS, or a framework's static build output | `static` — extracts as-is |\n| `package.json` with a build script, a start script or a server dependency (Next.js, Nuxt, Express, Vite…) | `nodejs` — starts a build, returns `buildUuid` |\n| PHP code, no build step | `static` |\n| WordPress plugin folder (a `Plugin Name:` header) or theme folder (`Theme Name:` in `style.css`) | `wordpress-plugin` / `wordpress-theme`; `activate: true` activates a theme |\n| A whole WordPress site from elsewhere | `wordpress-site`, with `databaseDump` — see the `migrate-to-hosting` skill |\n\n- Pass `type` to override detection. It is required for an archive (an archive cannot be inspected) and when a directory matches more than one WordPress type, which `hosting_deploy` rejects as ambiguous.\n- Node.js build settings come from the project; detection covers the framework and the build script. Set `appType`, `outputDirectory`, `buildScript`, `entryFile`, `nodeVersion` (default 22), `rootDirectory` or `packageManager` when it is wrong. A TypeScript or other compiled server app always needs `entryFile` set to the built file (for example `dist/server.js`): detection only guesses a root-level file, and the build fails with \"Entry file not found\". The entry file is relative to the root directory for express, fastify, hono, react-router and other, and to the output directory for nest, nuxt, nitro, astro and svelte-kit (`main.js`, not `dist/main.js`); Next.js ignores it. Per-framework values are in the Hostinger docs under Node.js (Supported Frameworks, Build Settings).\n- Static, PHP, plugin and theme deploys are live when the call returns. A WordPress site import only starts (`status: import_started`) and finishes extracting a few minutes later; do not report it complete or verify it before then. Node.js builds run on the server: see step 3.\n\n**Agency:** the website's type decides (`agency-hosting_websites_get`): sites created as `node-static` take `agency-hosting_deploy-node-static-website`, which runs the build when the project has one; every other site takes `agency-hosting_deploy-php-application`, which extracts the archive as-is. They deploy archives, synchronously: the site is live when the call returns. WordPress plugins and themes on Agency sites are installed from wp-admin.\n\nArchive rules for Agency (name archives `name_YYYYMMDD_HHMMSS.zip`) and for archives you build yourself:\n\n- **Static:** build locally first. `index.html` must be at the archive root, not inside a folder: `cd dist && zip -r ../site_20260101_120000.zip .`\n- **Node.js source:** no `node_modules/`, no build output (`dist/`, `.next/`, `build/`), no `.env*`, nothing matched by `.gitignore`; 50 MB at most. `git archive --format=zip -o app_20260101_120000.zip HEAD` produces exactly the committed files — mention that uncommitted changes are left out.\n\n`hosting_deploy` with `path` and the `agency-hosting_deploy-*` operations read files from this machine, so only the local server has them. The hosted server (`mcp.hostinger.com`) has `hosting_deploy` for Git repositories only (step 4) and `hosting_get-deployment` (step 3); to deploy files from this machine there, upload them yourself as below.\n\n### Without the local deploy operations (local files on the hosted server; Agency on either server)\n\n1. Get upload credentials: `hosting_files_generate-upload-url` (`username`, `domain`); Agency: `agency-hosting_files_generate-upload-url` (`website_uid`). Both return `url`, `auth_key` and `rest_auth_key`, which authenticate the upload instead of the API token.\n2. Upload each file with TUS, where `DEST` is its path in the website's storage:\n\n```bash\nSIZE=$(wc -c < \"$FILE\" | tr -d ' ')\ncurl -sS -X POST \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Upload-Length: $SIZE\" -H \"Upload-Offset: 0\"\ncurl -sS -X PATCH \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Content-Type: application/offset+octet-stream\" -H \"Upload-Offset: 0\" --data-binary \"@$FILE\"\n```\n\n The first call returns `201`, the second `204` with an `Upload-Offset` header equal to the file size. `override=true` makes a retry safe.\n3. Deploy from the uploaded files (`RANDOM8` is any fresh 8-character string, e.g. `$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)`):\n\n| Project | `DEST` | Then |\n| --- | --- | --- |\n| Static or PHP | `site.zip` | `hosting_websites_deploy-static-site-archive` with `archive_path: \"site.zip\"` |\n| Node.js | `app.zip` | `hosting_nodejs_build-settings-from-archive` with `archive_path: \"app.zip\"`, then `hosting_nodejs_start-build` with those settings, `source_type: \"archive\"` and `source_options.archive_path: \"app.zip\"` |\n| WordPress plugin | every file, as `wp-content/plugins/SLUG-RANDOM8/<path in the plugin>` | `wordpress_plugins_deploy` with `slug` and `plugin_path: \"SLUG-RANDOM8\"` |\n| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/<path in the theme>` | `wordpress_themes_deploy` with `slug`, `theme_path: \"SLUG-RANDOM8\"` and optional `is_activated` |\n| Agency, extracted as-is | `.h5g/site.zip` | `agency-hosting_files_import-website-from-archive` with `archive_name: \"site.zip\"` |\n| Agency `node-static` | `.h5g/RANDOM8/app.zip` | `agency-hosting_websites_build-nodejs-assets` with `archive_path: \".h5g/RANDOM8\"` — the directory, not the file |\n\n## 3. Node.js builds\n\n`hosting_deploy` returns a `buildUuid` at once; the build runs on the server and takes minutes. Track it — and a build started with `hosting_nodejs_start-build` after an upload — with `hosting_get-deployment` (`domain`, optionally `buildUuid`; the latest build by default), polling every 10–20 s until `build.state` is `completed` or `failed`. It returns the build, its log and the stored build settings; pass the previous `logs.lines` as `fromLine` to read only new output.\n\n- Failed build: the response already carries `analysis` (fetched once per build; the API allows 5 a minute).\n- Wrong detection (framework, `output_directory`, or an entry file inside the build output, such as `dist/server.js` for a compiled app): deploy again with `appType`, `outputDirectory`, `buildScript` or `entryFile` set. An archive deploy sends its settings with the request and leaves the stored ones alone; a Git deploy also stores these values, so later pushes build with them.\n\nLocal files on the hosted server, deployed from an upload (step 2):\n\n- Wrong detection: run `hosting_nodejs_start-build` again with explicit values, `source_type: \"archive\"` and `source_options.archive_path` set to the archive's file name in the document root (check it is there with `hosting_files_list-website-and-directories`). Store the same values with `hosting_nodejs_update-build-settings`.\n- To review detected settings before any build: upload the archive with `hosting_files_generate-upload-url` (the TUS steps are in its description), then `hosting_nodejs_build-settings-from-archive`.\n\n## 4. Auto-deploy from Git\n\nOn both the local and the hosted server, `hosting_deploy` with `git: { repository: \"owner/name\", branch }` does all of it: it finds the Git installation that can reach the repository and deploys it as the `type` you give. Always pass `type: \"static\"` or `\"nodejs\"` unless the site already runs a Node.js app (or you pass Node.js settings such as `appType`): a new site is not detected as Node.js, and a Node.js repository deployed as static would publish its source. Then:\n- **PHP and static sites:** saves the auto-deployment settings, which deploys the branch immediately and again on every push;\n- **Node.js sites:** starts a build of the branch with the stored build settings; later pushes build the same way. When none are stored, they are detected from the branch's `package.json` and stored for those pushes; pass `appType`, `nodeVersion`, `outputDirectory`, `buildScript` or `entryFile` to override them (a compiled app still needs `entryFile`).\n\nWith no active installation it stops and says so: the user connects GitHub or GitLab once in hPanel under Websites → Manage → Advanced → Git; there is no API for that step. Confirm with `hosting_get-deployment`, which returns the stored `gitAutoDeployment`.\n\nAgency Plan sites have no Git operations; deploy archives.\n\n## 5. Environment variables (Node.js)\n\n`hosting_nodejs_replace-environment-variables` replaces the whole set — anything not sent is deleted.\n\n1. `hosting_nodejs_list-environment-variables` for the current keys. Values come back as `********`; never send those back.\n2. Build the full set: every current key plus the new ones, with real values from the project's `.env` or the user. When the real value of an existing key is unknown, ask — do not drop it.\n3. Send it once; the app restarts. Values baked in at build time (Next.js `NEXT_PUBLIC_*`, Vite `VITE_*`) need a new `hosting_nodejs_start-build`.\n\nKeys use uppercase letters, digits and underscores. Never commit `.env` files or repeat secret values back to the user.\n\n## 6. Database\n\n- **Node.js:** `hosting_databases_setup-website` creates a MySQL database and writes `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` into the environment, then restarts the app. The password is generated and never returned. It fails with 422 when any of those keys exists — remove them first with the step 5 replace.\n- **PHP:** `hosting_databases_create` with `name`, `user`, a strong generated `password` and `website_domain`; read the prefixed full name back from `hosting_databases_list`. The app reads credentials from a config file on the server that stays out of Git, and connects to `localhost` or `127.0.0.1`.\n- **Agency:** `agency-hosting_databases_create-website`; the app connects to `localhost`.\n- The `srvNNNN.hstgr.io` host from `hosting_databases_list` is only for connections from outside Hostinger — never put it in the deployed app.\n\n## 7. Verify and record\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://DOMAIN/\ncurl -s https://DOMAIN/ | grep -o \"TEXT THE PROJECT RENDERS\"\n```\n\nA `200` alone can be a placeholder page; match real copy. A new site may serve a default page briefly — clear it with `hosting_cache_clear-website` and retry before calling the deploy failed.\n\nWrite `.hostinger/site.json` in the project so later runs reuse the target: `{ \"domain\", \"username\" or \"website_uid\", \"type\": \"static\" | \"nodejs\" | \"php\" }`, keeping any fields already there.\n"
|
|
61
|
+
"text": "---\nname: deploy-to-hosting\ndescription: \"Deploy an existing project to a website on Hostinger web hosting (Shared, Cloud or Agency plans) and keep it deployed: picks the right deploy for static sites, Node.js apps (Next.js, Nuxt, Express, Vite and similar), PHP apps and WordPress plugins or themes, sets up auto-deploy from GitHub or GitLab, manages Node.js environment variables and MySQL databases, and verifies the live site. Triggers: deploy this project, deploy to Hostinger, push my app live, redeploy, set up auto-deploy, connect my GitHub repo, add environment variables, my app needs a database, deploy my WordPress plugin or theme.\"\n---\n\n# Deploy to hosting\n\nShip a project that already exists — on disk or in a Git repository — to a Hostinger website. Building a new site from a prompt (design, store, blog) is the `hostinger-headless` skill; this one takes code as it is.\n\n## Calling the operations\n\n- Read the `inputSchema` that `search` returns before the first call of each operation. If a name below is rejected as unknown, `search` for what the step does (e.g. \"deploy static\").\n- Batch reads with `multi-execute`; values pass between steps as `$steps.<i>.<path>`. Batches chain operations from one server only — the Hostinger Connector runs `hosting`, `wordpress` and `agency-hosting` as separate servers.\n- Every deploy overwrites the website's current contents. Confirm the target domain with the user before the first deploy to a site that already has content.\n\n## 0. If a shell and the Hostinger CLI are available\n\n`hostinger up` does steps 1 to 3 in one call and is the shortest path when you can run commands and the `hostinger` CLI is installed (`brew install hostinger/tap/hostinger`), signed in and has `hostinger up`: it finds or creates the website, detects the type, packs the directory (honouring `.gitignore`; skipping `node_modules`, `.git`, `.env` files, credentials and private keys, and listing symlinks it does not follow), uploads, deploys or builds, waits for the build and links the directory in `.hostinger/site.json`, the same file this skill uses. Nothing is asked without a terminal.\n\n```\nhostinger up <dir> --domain <domain> --yes --format json # existing website; --yes only after the user agreed to replace its content\nhostinger up <dir> --create --format json # new website on the only plan, free subdomain\nhostinger up <dir> --domain <domain> --dry-run # the plan as JSON, changes nothing: show it to the user before a first deploy (overwritesContent: the real run needs --yes)\nhostinger up <dir> --domain <domain> --type nodejs --app-type next --output-directory .next --build-script build --node-version 22 --yes --format json\nhostinger up <dir> --domain <domain> --no-wait --format json # returns buildUuid at once; poll with `hostinger hosting nodejs build <domain> <uuid>`\n```\n\nRun it from the project directory: the link is written in the current directory, with `path` naming `<dir>`. The JSON result has `url`, `data.type`, `data.buildUuid`, `data.buildState`, `data.skippedFiles` and, on failure, `error`. Shared and Cloud plans only; for Agency plans, archives and WordPress plugins or themes, use the operations below. The MCP's `hosting_deploy` does the same work when there is no shell.\n\n## 1. Pick the target website\n\n- `.hostinger/site.json` in the project: reuse its `domain` and `username` (or `website_uid`). The CLI's `hostinger up` and `hostinger link` write the same file and keep any fields you add while it names the same website (linking another website starts it afresh); its `type` may be `static`, `nodejs` or `php`.\n- Otherwise look the domain up with `hosting_websites_list` (Shared and Cloud; substring match — take the exact entry) and `agency-hosting_websites_list-plan` (Agency; keep `details.uid`).\n- No website yet:\n - Shared and Cloud: take a domain (the user's own, or `hosting_domains_generate-free-subdomain`) and an `order_id` from `hosting_orders_list`, then `hosting_websites_create`. `datacenter_code` is needed only for the first website on a new plan (first entry of `hosting_datacenters_list`). Poll `hosting_websites_list-setups` with `domain` every 10–15 s until `status: completed` — uploads and deploys return 404 or 409 before that.\n - Agency: `agency-hosting_website-setups_create` with `flavor: \"php-fpm\"` (plus `type: \"node-static\"` for built frontends), `settings.php.version` from `agency-hosting_php_list-versions-for-order` and `datacenter_code` from `agency-hosting_datacenters_list`. Poll `agency-hosting_website-setups_status` until `completed`; it returns the `website_uid`.\n- Node.js apps run on Business and Cloud plans (`hostinger_business_*`, `cloud_*` in `hosting_orders_list`). On other plans, build locally and deploy the output as a static site.\n\n## 2. Choose the deploy\n\n**Shared and Cloud, on the local `hostinger-api-mcp` server:** one operation does every deploy — `hosting_deploy` with `domain` and either `path` (a directory on this machine) or `git: { repository: \"owner/name\", branch }`. Given a directory it detects what the project is, packs it itself (it skips `node_modules`, `.git` and `.env` files, listing the `.env` ones as `skippedFiles` so you can set their values with `hosting_nodejs_replace-environment-variables`; it does not read `.gitignore`, so keep build output out of the directory) and does the upload, build and Git setup. Do not archive the directory or call upload and build operations by hand.\n\n| Project | `hosting_deploy` treats it as |\n| --- | --- |\n| Plain HTML/CSS/JS, or a framework's static build output | `static` — extracts as-is |\n| `package.json` with a build script, a start script or a server dependency (Next.js, Nuxt, Express, Vite…) | `nodejs` — starts a build, returns `buildUuid` |\n| PHP code, no build step | `static` |\n| WordPress plugin folder (a `Plugin Name:` header) or theme folder (`Theme Name:` in `style.css`) | `wordpress-plugin` / `wordpress-theme`; `activate: true` activates a theme |\n| A whole WordPress site from elsewhere | `wordpress-site`, with `databaseDump` — see the `migrate-to-hosting` skill |\n\n- Pass `type` to override detection. It is required for an archive (an archive cannot be inspected) and when a directory matches more than one WordPress type, which `hosting_deploy` rejects as ambiguous.\n- Node.js build settings come from the project; detection covers the framework and the build script. Set `appType`, `outputDirectory`, `buildScript`, `entryFile`, `nodeVersion` (default 22), `rootDirectory` or `packageManager` when it is wrong. A TypeScript or other compiled server app always needs `entryFile` set to the built file (for example `dist/server.js`): detection only guesses a root-level file, and the build fails with \"Entry file not found\". The entry file is relative to the root directory for express, fastify, hono, react-router and other, and to the output directory for nest, nuxt, nitro, astro and svelte-kit (`main.js`, not `dist/main.js`); Next.js ignores it. Per-framework values are in the Hostinger docs under Node.js (Supported Frameworks, Build Settings).\n- Static, PHP, plugin and theme deploys are live when the call returns. A WordPress site import only starts (`status: import_started`) and finishes extracting a few minutes later; do not report it complete or verify it before then. Node.js builds run on the server: see step 3.\n\n**Agency:** the website's type decides (`agency-hosting_websites_get`): sites created as `node-static` take `agency-hosting_deploy-node-static-website`, which runs the build when the project has one; every other site takes `agency-hosting_deploy-php-application`, which extracts the archive as-is. They deploy archives, synchronously: the site is live when the call returns. WordPress plugins and themes on Agency sites are installed from wp-admin.\n\nArchive rules for Agency (name archives `name_YYYYMMDD_HHMMSS.zip`) and for archives you build yourself:\n\n- **Static:** build locally first. `index.html` must be at the archive root, not inside a folder: `cd dist && zip -r ../site_20260101_120000.zip .`\n- **Node.js source:** no `node_modules/`, no build output (`dist/`, `.next/`, `build/`), no `.env*`, nothing matched by `.gitignore`; 50 MB at most. `git archive --format=zip -o app_20260101_120000.zip HEAD` produces exactly the committed files — mention that uncommitted changes are left out.\n\n`hosting_deploy` with `path` and the `agency-hosting_deploy-*` operations read files from this machine, so only the local server has them. The hosted server (`mcp.hostinger.com`) has `hosting_deploy` for Git repositories only (step 4) and `hosting_get-deployment` (step 3); to deploy files from this machine there, upload them yourself as below.\n\n### Without the local deploy operations (local files on the hosted server; Agency on either server)\n\n1. Get upload credentials: `hosting_files_generate-upload-url` (`username`, `domain`); Agency: `agency-hosting_files_generate-upload-url` (`website_uid`). Both return `url`, `auth_key` and `rest_auth_key`, which authenticate the upload instead of the API token.\n2. Upload each file with TUS, where `DEST` is its path in the website's storage:\n\n```bash\nSIZE=$(wc -c < \"$FILE\" | tr -d ' ')\ncurl -sS -X POST \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Upload-Length: $SIZE\" -H \"Upload-Offset: 0\"\ncurl -sS -X PATCH \"$URL/$DEST?override=true\" -H \"X-Auth: $AUTH_KEY\" -H \"X-Auth-Rest: $REST_AUTH_KEY\" -H \"Tus-Resumable: 1.0.0\" -H \"Content-Type: application/offset+octet-stream\" -H \"Upload-Offset: 0\" --data-binary \"@$FILE\"\n```\n\n The first call returns `201`, the second `204` with an `Upload-Offset` header equal to the file size. `override=true` makes a retry safe.\n3. Deploy from the uploaded files (`RANDOM8` is any fresh 8-character string, e.g. `$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)`):\n\n| Project | `DEST` | Then |\n| --- | --- | --- |\n| Static or PHP | `site.zip` | `hosting_websites_deploy-static-site-archive` with `archive_path: \"site.zip\"` |\n| Node.js | `app.zip` | `hosting_nodejs_build-settings-from-archive` with `archive_path: \"app.zip\"`, then `hosting_nodejs_start-build` with those settings, `source_type: \"archive\"` and `source_options.archive_path: \"app.zip\"` |\n| WordPress plugin | every file, as `wp-content/plugins/SLUG-RANDOM8/<path in the plugin>` | `wordpress_plugins_deploy` with `slug` and `plugin_path: \"SLUG-RANDOM8\"` |\n| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/<path in the theme>` | `wordpress_themes_deploy` with `slug`, `theme_path: \"SLUG-RANDOM8\"` and optional `is_activated` |\n| Agency, extracted as-is | `.h5g/site.zip` | `agency-hosting_files_import-website-from-archive` with `archive_name: \"site.zip\"` |\n| Agency `node-static` | `.h5g/RANDOM8/app.zip` | `agency-hosting_websites_build-nodejs-assets` with `archive_path: \".h5g/RANDOM8\"` — the directory, not the file |\n\n## 3. Node.js builds\n\n`hosting_deploy` returns a `buildUuid` at once; the build runs on the server and takes minutes. Track it — and a build started with `hosting_nodejs_start-build` after an upload — with `hosting_get-deployment` (`domain`, optionally `buildUuid`; the latest build by default), polling every 10–20 s until `build.state` is `completed` or `failed`. It returns the build, its log and the stored build settings; pass the previous `logs.lines` as `fromLine` to read only new output.\n\n- Failed build: the response already carries `analysis` (fetched once per build; the API allows 5 a minute).\n- Wrong detection (framework, `output_directory`, or an entry file inside the build output, such as `dist/server.js` for a compiled app): deploy again with `appType`, `outputDirectory`, `buildScript` or `entryFile` set. An archive deploy sends its settings with the request and leaves the stored ones alone; a Git deploy also stores these values, so later pushes build with them.\n\nLocal files on the hosted server, deployed from an upload (step 2):\n\n- Wrong detection: run `hosting_nodejs_start-build` again with explicit values, `source_type: \"archive\"` and `source_options.archive_path` set to the archive's file name in the document root (check it is there with `hosting_files_list-website-and-directories`). Store the same values with `hosting_nodejs_update-build-settings`.\n- To review detected settings before any build: upload the archive with `hosting_files_generate-upload-url` (the TUS steps are in its description), then `hosting_nodejs_build-settings-from-archive`.\n\n## 4. Auto-deploy from Git\n\nOn both the local and the hosted server, `hosting_deploy` with `git: { repository: \"owner/name\", branch }` does all of it: it finds the Git installation that can reach the repository and deploys it as the `type` you give. Always pass `type: \"static\"` or `\"nodejs\"` unless the site already runs a Node.js app (or you pass Node.js settings such as `appType`): a new site is not detected as Node.js, and a Node.js repository deployed as static would publish its source. Then:\n- **PHP and static sites:** saves the auto-deployment settings, which deploys the branch immediately and again on every push;\n- **Node.js sites:** starts a build of the branch with the stored build settings; later pushes build the same way. When none are stored, they are detected from the branch's `package.json` and stored for those pushes; pass `appType`, `nodeVersion`, `outputDirectory`, `buildScript` or `entryFile` to override them (a compiled app still needs `entryFile`).\n\nWith no active installation it stops and says so: the user connects GitHub or GitLab once in hPanel under Websites → Manage → Advanced → Git; there is no API for that step. Confirm with `hosting_get-deployment`, which returns the stored `gitAutoDeployment`.\n\nAgency Plan sites have no Git operations; deploy archives.\n\n## 5. Environment variables (Node.js)\n\n`hosting_nodejs_replace-environment-variables` replaces the whole set — anything not sent is deleted.\n\n1. `hosting_nodejs_list-environment-variables` for the current keys. Values come back as `********`; never send those back.\n2. Build the full set: every current key plus the new ones, with real values from the project's `.env` or the user. When the real value of an existing key is unknown, ask — do not drop it.\n3. Send it once; the app restarts. Values baked in at build time (Next.js `NEXT_PUBLIC_*`, Vite `VITE_*`) need a new `hosting_nodejs_start-build`.\n\nKeys use uppercase letters, digits and underscores. Never commit `.env` files or repeat secret values back to the user.\n\n## 6. Database\n\n- **Node.js:** `hosting_databases_setup-website` creates a MySQL database and writes `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `DATABASE_URL` into the environment, then restarts the app. The password is generated and never returned. It fails with 422 when any of those keys exists — remove them first with the step 5 replace.\n- **PHP:** `hosting_databases_create` with `name`, `user`, a strong generated `password` and `website_domain`; read the prefixed full name back from `hosting_databases_list`. The app reads credentials from a config file on the server that stays out of Git, and connects to `localhost` or `127.0.0.1`.\n- **Agency:** `agency-hosting_databases_create-website`; the app connects to `localhost`.\n- The `srvNNNN.hstgr.io` host from `hosting_databases_list` is only for connections from outside Hostinger — never put it in the deployed app.\n\n## 7. Verify and record\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://DOMAIN/\ncurl -s https://DOMAIN/ | grep -o \"TEXT THE PROJECT RENDERS\"\n```\n\nA `200` alone can be a placeholder page; match real copy. A new site may serve a default page briefly — clear it with `hosting_cache_clear-website` and retry before calling the deploy failed.\n\nWrite `.hostinger/site.json` in the project so later runs reuse the target: `{ \"domain\", \"username\" or \"website_uid\", \"type\": \"static\" | \"nodejs\" | \"php\" }`, keeping any fields already there.\n"
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
},
|
|
@@ -260,10 +260,10 @@ const skills: Skill[] = [
|
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"uri": "skill://hostinger-headless/steps/08-deploy/SKILL.md",
|
|
263
|
-
"digest": "sha256:
|
|
264
|
-
"size":
|
|
263
|
+
"digest": "sha256:df610d6f851a4f2c8f22bf7ff23159dea5c6e836a139dae47716d2b472a59885",
|
|
264
|
+
"size": 12972,
|
|
265
265
|
"mimeType": "text/markdown",
|
|
266
|
-
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n> **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git\n> repositories only and cannot read a local `path`. Run this step through the local\n> `hostinger-api-mcp` server instead (see the connect step:\n> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)\n> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)\n> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts\n> with a \"glibc too old\" error — and Turbopack **does not fall back** to webpack on its own, so the\n> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script\n> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy\n> call stays `\"build\"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on\n> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).\n> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see\n> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`\n> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other\n> frameworks are unaffected.\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n - **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —\n the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it\n as plain `next build`. Fix it here if it isn't.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` on the local server is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
266
|
+
"text": "---\nname: headless-step-deploy\ndescription: \"Step 8 (deploy) of the Hostinger Headless flow. Archives the frontend source, then deploys through the connector's one-shot hosting_deploy (type nodejs — it uploads the source, runs npm install and the build on the server) with the chosen framework's build settings (appType + output dir) passed explicitly, polls to completion with hosting_get-deployment, verifies the live site and that the backend survived, then writes deploy.json.\"\n---\n\n# headless-step-deploy\n\nShip the frontend to the live domain through the Hostinger connector. You hand the connector a zip of\nthe project **source** and it uploads it, runs `npm install` + the build script on the server, and\nserves the result. **Out:** `deploy.json`.\n\nThis is an **instance-barrier** step: run it only after `provision.status == \"completed\"` and validate\n(if run) has passed. All operations go through the connector (`execute`).\n\n> **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git\n> repositories only and cannot read a local `path`. Run this step through the local\n> `hostinger-api-mcp` server instead (see the connect step:\n> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).\n\n> **With a shell and the Hostinger CLI installed**, the same deploy is one command, with the build\n> settings passed exactly as the table below says. Run it from the project, since it links the current directory:\n> `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`.\n> It uploads, builds, waits and returns `data.buildUuid` and `data.buildState`; add `--no-wait` to\n> return at once and poll with `hostinger hosting nodejs build <domain> <uuid>`. Then continue at\n> \"Verify\" below.\n\n**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`\n(`frontend.framework`, `frontend.rendering`).\n\n> ## ⚠️ The one rule that makes this work: pass build settings explicitly\n> If you let `hosting_deploy` **auto-detect** build settings, for a headless site that detection returns\n> **null** (`appType:null`, `outputDirectory:null`, Node 20) — the build \"completes\" but the **root\n> serves 403** (no `appType`/output → the vhost serves a directory with no entry). This is reproducible.\n> **Always pass `appType`, `outputDirectory`, `nodeVersion`, `buildScript`, `packageManager` and\n> `rootDirectory` on the `hosting_deploy` call** with the framework's values; the deploy then resolves\n> them and serves correctly, and `/cms` is preserved.\n\n## Build configurations\n\nMap `frontend.framework` (from `state.json`) to its `appType` + build output directory, and pass those\non the `hosting_deploy` call. `appType` is the framework's value, e.g. `next`, `nuxt`, `astro`,\n`svelte-kit`, `nest`, `express`, `fastify`, `react`, `create-react-app`, `gatsby`, `vite`, `vue`,\n`angular`, `parcel`, `svelte`, `other`.\n\nCommon cases (all `nodeVersion: 22`, `buildScript: \"build\"`, `packageManager: \"npm\"`, `rootDirectory: \".\"`):\n\n| Framework / mode | Local config | `appType` | `outputDirectory` |\n|---|---|---|---|\n| **Next.js — SSR** *(default)* | `output: 'standalone'` | `next` | `.next` |\n| **Nuxt — SSR** | default | `nuxt` | `.output` |\n| **SvelteKit — SSR** | node adapter | `svelte-kit` | `build` |\n| **Astro — static** | `output: 'static'` | `astro` | `dist` |\n| **Vite / React / Vue — static (SPA)** | — | `vite` (or `react`/`vue`) | `dist` |\n| **Express / NestJS / Fastify — server** | — | `express`/`nest`/`fastify` | build output dir |\n\nSSR/server frameworks run a persistent Node process (root sends `x-powered-by` / `cache-control: no-store`);\nstatic frameworks serve their output dir as files. If unsure of a framework's output dir, check the\nframework's own build output, but **always pass `appType`/`outputDirectory` on the deploy call with the\nframework's correct values** — don't rely on auto-detection (it returns null → 403).\n\n> ## ⚠️ Next.js 16+: build with webpack, not Turbopack (recurring deploy failure)\n> Next.js 16 makes **Turbopack** the default bundler for `next build`. Turbopack's native (Rust)\n> compiler needs a newer **glibc** than the Hostinger build server ships, so the server build aborts\n> with a \"glibc too old\" error — and Turbopack **does not fall back** to webpack on its own, so the\n> deploy fails every time. **Fix: build with webpack.** On **Next.js 16+** the project's `build` script\n> in `package.json` must be `next build --webpack` (not plain `next build`); `buildScript` on the deploy\n> call stays `\"build\"` — that's what runs it. The `--webpack` flag exists only on Next.js 16+; on\n> **Next.js 15.x** leave the build script as plain `next build` (it already defaults to webpack).\n> **Confirm this in `package.json` before deploying** (the frontend step scaffolds it this way — see\n> `../06-frontend`). Alternative, only if you must avoid the `--webpack` flag: pin Next.js to `15.x`\n> (keeping plain `next build`), whose `next build` still defaults to webpack. Next.js only — other\n> frameworks are unaffected.\n\n## Step 1 — Pre-flight\n\n1. **Instance live.** `provision.status == \"completed\"`, re-verified with\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }`.\n2. **Build output exists locally** (the framework's output dir — e.g. `.next/`, `dist/`, `.output/`) and\n the project builds clean (frontend/validate already did this). If not, go back.\n - **Next.js 16+:** `package.json`'s `build` script is `next build --webpack`, not plain `next build` —\n the server's glibc can't run Turbopack's native compiler (see the box above). On Next.js 15.x leave it\n as plain `next build`. Fix it here if it isn't.\n3. **Env vars (only if used).** Build-time vars (e.g. `NEXT_PUBLIC_*`) must be set **before** the build\n with `hosting_nodejs_replace-environment-variables` (full replace). Never put secrets in the repo or\n the archive. **Webapp with a database:** that full replace wipes the `DB_*`/`DATABASE_URL` that\n `hosting_databases_setup-website` injected (the generated password reads back masked), so set all app\n vars in the same replace and, if you can't, first reset the password with\n `hosting_databases_change-password` and resend every `DB_*`/`DATABASE_URL` — see\n `../03-provision/webapp/SKILL.md` Step 3.\n\n## Step 2 — Archive the source\n\nZip the project **source** with `package.json` at the archive root. **Include** the app/source,\n`public/`, the framework config (`next.config.mjs` / `astro.config.mjs`), `tsconfig.json`,\n`package.json`, `package-lock.json`, `.nvmrc`. **Exclude** `node_modules/`, the build output\n(`.next/`, `dist/`, `out/`), `.git/`, and any `.htaccess` — the server installs and builds from source.\n\n```bash\ncd ~/.hostinger/<domain>/src\nzip -r -X ../app.zip . -x 'node_modules/*' '.next/*' 'dist/*' 'out/*' '.git/*' '**/.htaccess'\n```\n\n## Step 3 — Deploy (one shot: settings + upload + build)\n\nPass the framework's build settings **inline** on the deploy call — there is no separate settings step.\n`path` is the archive from Step 2 (or the source directory itself; `hosting_deploy` archives a directory\nfor you, skipping `node_modules/`, `.git/` and `.env`, but it does **not** read `.gitignore`, so the\nbuild output must already be absent).\n\n```json\n{ \"operation\": \"hosting_deploy\",\n \"params\": {\n \"domain\": \"<domain>\",\n \"path\": \"/absolute/path/to/.hostinger/<domain>/app.zip\",\n \"type\": \"nodejs\",\n \"appType\": \"<framework appType, e.g. next>\",\n \"outputDirectory\": \"<framework output dir, e.g. .next>\",\n \"nodeVersion\": 22,\n \"buildScript\": \"build\",\n \"packageManager\": \"npm\",\n \"rootDirectory\": \".\" } }\n```\n\n`hosting_deploy` uploads the source and triggers `npm install` + the build **end-to-end** — do not call\nany upload or build operation yourself. It returns a build `buildUuid` at once. This **overwrites the\nwebsite root** (expected) and keeps `/cms` intact. Passing `appType`/`outputDirectory` explicitly is what\nkeeps the root from serving 403 (see the rule above).\n\n## Step 4 — Poll to completion\n\nPoll `hosting_get-deployment { \"domain\": \"<domain>\", \"buildUuid\": \"<buildUuid>\" }` with backoff until\n`build.state` is `completed`. On `failed`, the same response carries the end of the build log and the\nfailure analysis — read it, fix locally, re-archive and redeploy (pass `fromLine`, the line count from\nthe previous call, to read only new log output). `hosting_get-deployment` also returns the build settings\nstored for the site: `appType`/`outputDirectory`/`nodeVersion` should echo what you passed; if they read\n`null`, the settings didn't take — fix the deploy call before continuing. For an SSR app that built but\nhangs, `hosting_nodejs_restart-application { username, domain }` recovers the server without a rebuild.\n\n## Step 5 — Verify the live site (all must pass)\n\n| Check | How | Expect |\n|---|---|---|\n| Root serves | `GET https://<domain>/` | **200** (not 403 / 404) |\n| SSR is live *(server frameworks)* | response headers | `x-powered-by` present, `cache-control: no-store` |\n| Static serves *(static frameworks)* | `GET https://<domain>/` | 200 HTML with real home content |\n| Key routes | a couple of planned routes + one deep route | 200, real content |\n| **Backend survived** *(WordPress / WooCommerce)* | `GET https://<domain>/cms/wp-json/wp/v2/posts?per_page=1` | 200 |\n\n> **Only WordPress (including WordPress + WooCommerce) has a `/cms` on the domain**, so the *Backend\n> survived* check is WordPress-only.\n> - **WordPress + WooCommerce:** also confirm the store survived the deploy —\n> `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` → 200. There is nothing external to\n> link (no sales-channel step — that's Ecommerce only). **Checkout** stays owner-completed: when\n> `site.json → woocommerce.payment_enabled` is `false`, don't test `/checkout`; a catalog that serves 200\n> is a passing, complete deploy.\n> - **Ecommerce:** the backend is the **Hostinger Ecommerce store**, read over the external Storefront API\n> (`api-ecommerce.hostinger.com`) — nothing a deploy touches lives on this domain. There is no `/cms`;\n> confirm the storefront still fetches products as a frontend check if you like. **Only check that\n> checkout can be reached when `site.json → ecommerce.payment_enabled` is `true`** — on a free account\n> (`payment_enabled: false`) there is no checkout, so **skip that check entirely**; a catalog that serves\n> 200 is a passing, complete deploy. Never fail or hold the deploy because checkout is unavailable.\n> - **Webapp:** no content backend at all — the whole root is the app. Skip the check; `/cms` won't resolve.\n\nA `completed` build whose root 403s almost always means the build settings weren't passed to\n`hosting_deploy` (null settings) — pass them and redeploy. Caching can briefly serve a stale root;\nre-check after ~30 s.\n\n## Step 6 — Record `deploy.json`\n\n```json\n{\n \"build_uuid\": \"…\",\n \"stack\": \"nextjs\",\n \"rendering\": \"ssr\",\n \"app_type\": \"next\",\n \"output_directory\": \".next\",\n \"node_version\": 22,\n \"method\": \"hosting_deploy (type: nodejs, settings passed inline)\",\n \"deployed_at\": \"…\",\n \"verified_at\": \"…\",\n \"urls\": { \"live\": \"https://<domain>\", \"cms\": \"https://<domain>/cms\" },\n \"checks\": { \"root\": 200, \"ssr_headers\": true, \"backend_preserved\": true }\n}\n```\n\nNo secrets in `deploy.json`. The `urls.cms` field and the `backend_preserved` check above are\n**WordPress-only** — adjust the record to the backend:\n\n- **WordPress:** keep `urls.cms` and `checks.backend_preserved` as shown.\n- **WordPress + WooCommerce:** keep `urls.cms` + `checks.backend_preserved` (it *is* WordPress), and add\n `urls.store_api` from `site.json`; no external link step.\n- **Ecommerce:** omit `urls.cms`/`backend_preserved`; record the store URLs instead (`urls.storefront_api`,\n `urls.store_admin` from `site.json`) and link the custom sales channel to the live domain —\n `ecommerce_sales-channels_update { store_id, sales_channel_id, url }` (see the ecommerce provision step).\n- **Webapp:** omit `urls.cms` and `backend_preserved` entirely — `urls` is just `{ \"live\": \"https://<domain>\" }`.\n\nUpdate `state.json` (`stage` → `done`), then continue to Step 9 (done).\n\n---\n\n## Note — one deploy path, and never `type: \"static\"`\n\n`hosting_deploy` on the local server is the only path: give its `path` either the source **directory** (it archives it for\nyou) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build\nstep to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/\n`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with\n`type: \"static\"` — it serves the directory as plain files and can overwrite `/cms`.\n"
|
|
267
267
|
},
|
|
268
268
|
{
|
|
269
269
|
"uri": "skill://hostinger-headless/steps/09-done/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.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.13.0', 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.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.13.0', 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.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.13.0', 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.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.13.0', 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.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.13.0', 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.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.13.0', 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.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.13.0', 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.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.13.0', 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