hostinger-api-mcp 3.10.3 → 3.11.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 +17 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/audit-hosting/SKILL.md +1 -1
- package/skills/deploy-to-hosting/SKILL.md +6 -17
- package/skills/headless/steps/05-design/SKILL.md +47 -20
- package/skills/headless/steps/05-design/logo-kit.mjs +236 -7
- package/skills/headless/steps/06-frontend/SKILL.md +6 -0
- package/skills/headless/steps/08-deploy/SKILL.md +6 -1
- package/skills/migrate-to-hosting/SKILL.md +1 -1
- package/skills/troubleshoot-website/SKILL.md +2 -2
- package/src/core/catalog.js +2 -1
- package/src/core/catalog.ts +3 -2
- 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 +24 -24
- package/src/core/skills.ts +24 -24
- 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,23 @@ 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.11.0 — 2026-10-27
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Bundled agent skills: 7 files
|
|
12
|
+
- Request handling and authentication: `src/core/catalog.js`, `src/core/catalog.ts`
|
|
13
|
+
- Server instructions: 4 files
|
|
14
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
15
|
+
|
|
16
|
+
## v3.10.4 — 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Also in this release
|
|
19
|
+
|
|
20
|
+
- Dependencies: `package-lock.json`
|
|
21
|
+
- Bundled agent skills: `skills/headless/steps/05-design/SKILL.md`, `skills/headless/steps/05-design/logo-kit.mjs`, `skills/headless/steps/06-frontend/SKILL.md`
|
|
22
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
23
|
+
|
|
7
24
|
## v3.10.3 — 2026-10-26
|
|
8
25
|
|
|
9
26
|
No changes to the published artifact.
|
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.11.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "3.
|
|
14
|
+
"version": "3.11.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -30,7 +30,7 @@ Tell the user the size (plans, websites, WordPress installs) before the checks.
|
|
|
30
30
|
Per Shared and Cloud website (skip `builder` and `horizons`):
|
|
31
31
|
|
|
32
32
|
- `hosting_ssl_status` — flag `failed` (with `last_error`), `expired`, `not_installed` on a custom domain, `is_https_redirect_enabled: false`, and `expires_at` within 30 days when `is_lifetime` is false. Lifetime certificates renew on their own.
|
|
33
|
-
- Node.js sites: `hosting_get-deployment` (latest build failed? `build.state
|
|
33
|
+
- Node.js sites: `hosting_get-deployment` (latest build failed? `build.state`) and `hosting_nodejs_list-vulnerabilities` with `severities: ["critical", "high"]`.
|
|
34
34
|
|
|
35
35
|
Per hosting account (`username`): `hosting_databases_list` — flag `disk_usage_mb` above 80% of `max_size_mb`.
|
|
36
36
|
|
|
@@ -45,9 +45,9 @@ Archive rules for Agency (name archives `name_YYYYMMDD_HHMMSS.zip`) and for arch
|
|
|
45
45
|
- **Static:** build locally first. `index.html` must be at the archive root, not inside a folder: `cd dist && zip -r ../site_20260101_120000.zip .`
|
|
46
46
|
- **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.
|
|
47
47
|
|
|
48
|
-
`hosting_deploy` and the `agency-hosting_deploy-*` operations read files from this machine, so only the local server has them.
|
|
48
|
+
`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.
|
|
49
49
|
|
|
50
|
-
### Without the local deploy operations (
|
|
50
|
+
### Without the local deploy operations (local files on the hosted server; Agency on either server)
|
|
51
51
|
|
|
52
52
|
1. 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.
|
|
53
53
|
2. Upload each file with TUS, where `DEST` is its path in the website's storage:
|
|
@@ -72,35 +72,24 @@ curl -sS -X PATCH "$URL/$DEST?override=true" -H "X-Auth: $AUTH_KEY" -H "X-Auth-R
|
|
|
72
72
|
|
|
73
73
|
## 3. Node.js builds
|
|
74
74
|
|
|
75
|
-
`hosting_deploy` returns a `buildUuid` at once; the build runs on the server and takes minutes. Track it 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.
|
|
75
|
+
`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.
|
|
76
76
|
|
|
77
77
|
- Failed build: the response already carries `analysis` (fetched once per build; the API allows 5 a minute).
|
|
78
78
|
- 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.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Local files on the hosted server, deployed from an upload (step 2):
|
|
81
81
|
|
|
82
|
-
-
|
|
83
|
-
- Failed build: `hosting_nodejs_analyse-failed-build` once per build (5 calls a minute), then `hosting_nodejs_build-logs` if the analysis is empty.
|
|
84
|
-
- Wrong detection: run `hosting_nodejs_start-build` 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`.
|
|
82
|
+
- 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`.
|
|
85
83
|
- 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`.
|
|
86
84
|
|
|
87
85
|
## 4. Auto-deploy from Git
|
|
88
86
|
|
|
89
|
-
On the local 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:
|
|
87
|
+
On 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:
|
|
90
88
|
- **PHP and static sites:** saves the auto-deployment settings, which deploys the branch immediately and again on every push;
|
|
91
89
|
- **Node.js sites:** starts a build of the branch with the stored build settings; later pushes build the same way. A Git deploy has nothing to detect from, so when none are stored pass `appType` and `nodeVersion` (from `engines` in `package.json`), plus `outputDirectory` and `buildScript` if the app has a build step; they are stored for those pushes.
|
|
92
90
|
|
|
93
91
|
With 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`.
|
|
94
92
|
|
|
95
|
-
On the hosted server, by hand:
|
|
96
|
-
|
|
97
|
-
1. `hosting_git_list-installations`. With no `active` installation (check `status: "pending"` and `"suspended"` too), the user connects GitHub or GitLab once in hPanel, as above.
|
|
98
|
-
2. `hosting_git_list-installation-repositories` with the installation `uuid` gives `owner`, `name` and `default_branch` (10 calls a minute).
|
|
99
|
-
3. `hosting_git_update-auto-deployment-settings` with `installation_uuid`, `owner`, `repository`, `branch` and optional `directory`.
|
|
100
|
-
- PHP and static sites deploy the branch immediately and again on every push.
|
|
101
|
-
- Node.js sites clone nothing on save: start the first build with `hosting_nodejs_start-build`, `source_type: "git"` and the same `source_options`. Later pushes build with the stored settings, so keep `hosting_nodejs_update-build-settings` correct.
|
|
102
|
-
4. Confirm with `hosting_git_auto-deployment-settings`.
|
|
103
|
-
|
|
104
93
|
Agency Plan sites have no Git operations; deploy archives.
|
|
105
94
|
|
|
106
95
|
## 5. Environment variables (Node.js)
|
|
@@ -307,14 +307,14 @@ palette (base and surface colours count).
|
|
|
307
307
|
### Build it with the logo kit
|
|
308
308
|
|
|
309
309
|
The kit (`logo-kit.mjs`, next to this file) does the error-prone parts: it outlines letters into
|
|
310
|
-
vector paths, sets text on a circle, draws the outline-and-shadow treatment, measures,
|
|
311
|
-
the output. You write the composition. Copy the kit into
|
|
312
|
-
and a redesign can re-run it:
|
|
310
|
+
vector paths, sets text on a circle, draws the outline-and-shadow treatment, measures, validates
|
|
311
|
+
the output, checks the sizes and renders a preview. You write the composition. Copy the kit into
|
|
312
|
+
`brand/` so the build is self-contained and a redesign can re-run it:
|
|
313
313
|
|
|
314
314
|
```bash
|
|
315
315
|
mkdir -p ~/.hostinger/<domain>/brand && cd ~/.hostinger/<domain>/brand
|
|
316
316
|
cp <the folder this SKILL.md is in>/logo-kit.mjs .
|
|
317
|
-
npm init -y >/dev/null && npm i opentype.js @fontsource/<display-slug>
|
|
317
|
+
npm init -y >/dev/null && npm i opentype.js @resvg/resvg-js @fontsource/<display-slug>
|
|
318
318
|
```
|
|
319
319
|
|
|
320
320
|
Write `brand/logo-build.mjs`, import from `./logo-kit.mjs`, and always run it from `brand/`
|
|
@@ -329,7 +329,13 @@ Write `brand/logo-build.mjs`, import from `./logo-kit.mjs`, and always run it fr
|
|
|
329
329
|
| `arcText(font, text, { cx, cy, r, size })` | Letters around a circle, for seals |
|
|
330
330
|
| `sticker(d, { fill, ink, strokeWidth, shadow, shadowColor, transform })` | Ink outline + hard offset shadow, for outlined directions. On dark backgrounds give the outline and shadow a palette colour that shows (`shadowColor` defaults to `ink`) |
|
|
331
331
|
| `svgDoc({ width, height, title, body })` | Root SVG with `role="img"` and a `<title>` |
|
|
332
|
-
| `check(svg)` | Throws on `NaN`, live `<text>`, a missing title or viewBox. Run it on every file before writing |
|
|
332
|
+
| `check(svg)` | Throws on `NaN`, live `<text>`, a missing title or viewBox, or an `&` that isn't an XML entity. Run it on every file before writing |
|
|
333
|
+
| `tile(size, fill)` | The rounded tile for the mark and favicon; draw the symbol after it |
|
|
334
|
+
| `widthBudget(letterHeight, height)` | The widest viewBox that still fits 192 px on small screens, for a logo of that height; see below |
|
|
335
|
+
| `checkLogo({ logo, mark, favicon, letterHeight, background })` | Runs `check()` on all three files, checks their sizes and renders `brand/logo-preview.png`; see below |
|
|
336
|
+
|
|
337
|
+
Wrap every file in `svgDoc()`, never a hand-written `<svg>` or `<title>`: it escapes the site name,
|
|
338
|
+
and one bare `&` (as in "Salt & Pepper") makes the browser show nothing.
|
|
333
339
|
|
|
334
340
|
Never call opentype's `path.toPathData()` directly; the kit's serializer exists because opentype.js
|
|
335
341
|
2.0 writes `NaN` into some paths, and browsers silently stop drawing at the first `NaN`.
|
|
@@ -339,27 +345,48 @@ Never call opentype's `path.toPathData()` directly; the kit's serializer exists
|
|
|
339
345
|
| File | What | Must read clearly at |
|
|
340
346
|
|---|---|---|
|
|
341
347
|
| `brand/logo.svg` | The primary, mostly horizontal logo for the header | Its header display heights (desktop and small screens) |
|
|
342
|
-
| `brand/logo-mark.svg` | A square icon from the same idea (initial, monogram or the swapped symbol) on a
|
|
348
|
+
| `brand/logo-mark.svg` | A square icon from the same idea (initial, monogram or the swapped symbol) on a `tile()` in a palette colour | 32 px |
|
|
343
349
|
| `brand/favicon.svg` | The simplest form of the mark: same tile, no shadow, no thin details, outlines thickened if it has any | 16 px, on both light and dark browser tab bars |
|
|
344
350
|
| `brand/logo-build.mjs` | The script that built them, with `package.json`, kept so a redesign can adjust it | — |
|
|
345
351
|
|
|
346
352
|
Add `brand/logo-seal.svg` only if the direction uses a seal as a secondary mark. Delete
|
|
347
353
|
`brand/node_modules` when you're done; a redesign runs `npm i` in `brand/` first.
|
|
348
354
|
|
|
349
|
-
**
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
**Compose within the budget, so the first build passes.** The small-screen logo is at most 192 px
|
|
356
|
+
wide with letters at least 16 px tall, so the whole logo can be only about 10–12 times as wide as
|
|
357
|
+
its smallest letters are tall, whatever its height. On one line that's about ten letters plus a
|
|
358
|
+
symbol; a longer name stacks or tags, and a second word set at half size halves the room. Size the
|
|
359
|
+
viewBox in `logo-build.mjs` against `widthBudget(letterHeight, viewBoxHeight)`, the exact limit at
|
|
360
|
+
the heights the kit picks; with your own `heights`, the FAIL line gives the limit. Draw the mark's
|
|
361
|
+
symbol from simple, recognisable shapes that still read at 32 px; fine natural forms like petals or
|
|
362
|
+
leaves turn to mush.
|
|
363
|
+
|
|
364
|
+
**Check the sizes and look at it after every build.** `letterHeight` is in viewBox units: the
|
|
365
|
+
`xHeight` from `layout()` when the logo is mostly lowercase, `capHeight` when it's mostly capitals;
|
|
366
|
+
for mixed fonts, the smaller word's. End `logo-build.mjs` with:
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
checkLogo({ logo, mark, favicon, letterHeight, background: '<page background colour>' });
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
It picks the header heights for you: `--logo-h-sm` is the smallest multiple of 8 that makes the
|
|
373
|
+
letters 16 px, and `--logo-h` the same but at least 48 px, both at most 80 px, so a stacked lockup
|
|
374
|
+
keeps its words large or sets the mark beside them. Pass `heights: { desktop, small }` only when the
|
|
375
|
+
direction needs a larger header logo. It first prints `--logo-h`, `--logo-h-sm` and the **Measured**
|
|
376
|
+
line for `design.md`, then PASS or FAIL for the logo's letters ≥ 16 px at both
|
|
377
|
+
heights, width ≤ 192 px on small screens, headroom around everything drawn and heights in multiples
|
|
378
|
+
of 8; for the mark and favicon sitting on a `tile()` with nothing clipped and room around
|
|
379
|
+
the symbol; and for no shadow on the favicon. Every FAIL names its fixes and the number to reach. It
|
|
380
|
+
then renders `logo-preview.png` (the logo at both heights on the page background, the mark at 32 px
|
|
381
|
+
and favicon at 16 px on light and dark strips, each also enlarged), and throws if anything failed.
|
|
382
|
+
|
|
383
|
+
Then read `logo-preview.png`; don't search for another renderer or read the SVG source. Rebuild only
|
|
384
|
+
for a FAIL line or something in the preview that reads wrong, such as collisions, a clipped or
|
|
385
|
+
clearly off-centre mark, or a favicon detail that turns to mush, and fix everything you saw in one
|
|
386
|
+
rebuild. Optical nudges of a few pixels and the header height aren't reasons to rebuild. A symbol
|
|
387
|
+
gets one redraw: if it still reads wrong, replace it with a simpler form of the idea, such as the
|
|
388
|
+
initial, in the next build. If the kit says "preview not rendered", rely on the size check and list
|
|
389
|
+
"logo not visually checked" under gaps in the final report.
|
|
363
390
|
|
|
364
391
|
The logo is exempt from text-contrast minimums, but the header link around it needs `alt` = the site
|
|
365
392
|
name and a target of at least 24 px.
|
|
@@ -6,18 +6,27 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Workflow (from ~/.hostinger/<domain>/brand/, so packages and this kit sit next to the build):
|
|
8
8
|
// cp <installed skill>/steps/05-design/logo-kit.mjs .
|
|
9
|
-
// npm init -y && npm i opentype.js @fontsource/<display-family-slug>
|
|
9
|
+
// npm init -y && npm i opentype.js @resvg/resvg-js @fontsource/<display-family-slug>
|
|
10
10
|
// node logo-build.mjs // your script: import { … } from './logo-kit.mjs'
|
|
11
|
+
// // then read logo-preview.png
|
|
11
12
|
// rm -rf node_modules // package.json keeps the deps; a redesign runs `npm i` again
|
|
12
13
|
//
|
|
13
14
|
// Packages resolve from the current working directory, so run the build from brand/.
|
|
14
15
|
|
|
15
16
|
import { createRequire } from 'node:module';
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
17
18
|
import { join } from 'node:path';
|
|
18
19
|
|
|
19
20
|
const requireFromCwd = createRequire(join(process.cwd(), 'logo-build.js'));
|
|
20
21
|
const opentype = requireFromCwd('opentype.js');
|
|
22
|
+
// Optional: without resvg the kit still builds logos, it just can't measure what's drawn or render.
|
|
23
|
+
const Resvg = (() => {
|
|
24
|
+
try {
|
|
25
|
+
return requireFromCwd('@resvg/resvg-js').Resvg;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
21
30
|
|
|
22
31
|
// @fontsource splits each weight into unicode subsets; `latin` alone has no ą č ę ė į š ų ū ž,
|
|
23
32
|
// ő, ł, đ… so load every subset that exists and look each character up across them.
|
|
@@ -62,6 +71,8 @@ export function loadFont(slug, weight = 400, style = 'normal') {
|
|
|
62
71
|
return primary;
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
const VIEWBOX = /viewBox="0 0 ([\d.]+) ([\d.]+)"/;
|
|
75
|
+
|
|
65
76
|
const num = (v) => {
|
|
66
77
|
if (!Number.isFinite(v)) throw new Error(`logo-kit: non-finite coordinate ${v}`);
|
|
67
78
|
return +v.toFixed(2);
|
|
@@ -174,11 +185,16 @@ export function arcText(font, text, { cx, cy, r, size, startDeg = -90, fill = tr
|
|
|
174
185
|
export function sticker(d, { fill, ink = '#000', strokeWidth = 8, shadow = 8, shadowColor = ink, transform = '' }) {
|
|
175
186
|
const t = transform ? ` transform="${transform}"` : '';
|
|
176
187
|
const shade = shadow
|
|
177
|
-
? `<path d="${d}" fill="${shadowColor}" stroke="${shadowColor}" stroke-width="${strokeWidth}" stroke-linejoin="round" transform="translate(${shadow} ${shadow})"/>`
|
|
188
|
+
? `<path data-kit="shadow" d="${d}" fill="${shadowColor}" stroke="${shadowColor}" stroke-width="${strokeWidth}" stroke-linejoin="round" transform="translate(${shadow} ${shadow})"/>`
|
|
178
189
|
: '';
|
|
179
190
|
return `<g${t}>${shade}<path d="${d}" fill="${fill}" stroke="${ink}" stroke-width="${strokeWidth}" stroke-linejoin="round" paint-order="stroke"/></g>`;
|
|
180
191
|
}
|
|
181
192
|
|
|
193
|
+
/** The solid rounded tile behind `logo-mark.svg` and `favicon.svg`; draw the symbol after it. */
|
|
194
|
+
export function tile(size, fill) {
|
|
195
|
+
return `<rect data-kit="tile" width="${num(size)}" height="${num(size)}" rx="${num(size / 4)}" fill="${fill}"/>`;
|
|
196
|
+
}
|
|
197
|
+
|
|
182
198
|
/** Root SVG with an accessible name. Pass a distinct `id` if two logos are inlined on one page. */
|
|
183
199
|
export function svgDoc({ width, height, title, body, id = 'logo-title' }) {
|
|
184
200
|
const esc = String(title).replace(/&/g, '&').replace(/</g, '<');
|
|
@@ -187,15 +203,228 @@ export function svgDoc({ width, height, title, body, id = 'logo-title' }) {
|
|
|
187
203
|
|
|
188
204
|
/**
|
|
189
205
|
* Validate a finished SVG string before writing it. Throws on: NaN/undefined/Infinity in the
|
|
190
|
-
* markup, live <text> (the logo must be outlined), a missing <title>,
|
|
206
|
+
* markup, live <text> (the logo must be outlined), a missing <title>, a missing viewBox, or an "&" that
|
|
207
|
+
* doesn't start an XML entity (a browser shows nothing for an SVG file that isn't well-formed XML).
|
|
191
208
|
* @returns {{ bytes: number }}
|
|
192
209
|
*/
|
|
193
210
|
export function check(svg) {
|
|
211
|
+
const problems = markupProblems(svg);
|
|
212
|
+
if (problems.length) throw new Error(`logo-kit check failed: ${problems.join('; ')}`);
|
|
213
|
+
return { bytes: Buffer.byteLength(svg) };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function markupProblems(svg) {
|
|
194
217
|
const problems = [];
|
|
195
218
|
if (/NaN|undefined|Infinity/.test(svg)) problems.push('contains NaN/undefined/Infinity');
|
|
219
|
+
if (/&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[\dA-Fa-f]+);)/.test(svg)) {
|
|
220
|
+
problems.push('contains a bare "&" or an HTML-only entity such as ; build the file with svgDoc(), which escapes the title');
|
|
221
|
+
}
|
|
196
222
|
if (/<text[\s>]/.test(svg)) problems.push('contains <text>; outline the letters with textPath/glyphPath');
|
|
197
223
|
if (!/<title[^>]*>[^<]+<\/title>/.test(svg)) problems.push('missing <title>');
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
224
|
+
if (!VIEWBOX.test(svg)) problems.push('missing or invalid viewBox');
|
|
225
|
+
return problems;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const MIN_LETTER_PX = 16;
|
|
229
|
+
const MAX_SMALL_WIDTH_PX = 192;
|
|
230
|
+
const MIN_DESKTOP_PX = 48;
|
|
231
|
+
const MAX_HEADER_PX = 80; // automatic heights only: taller header logos crowd the nav, and stacked lockups reached 144
|
|
232
|
+
const HEADROOM = 0.02; // of the viewBox height, on every edge
|
|
233
|
+
const CLIP_SLACK = 0.5; // viewBox units of anti-aliasing a "nothing outside the viewBox" check allows
|
|
234
|
+
|
|
235
|
+
function viewBoxOf(svg) {
|
|
236
|
+
const m = svg.match(VIEWBOX);
|
|
237
|
+
if (!m) throw new Error('logo-kit: missing or invalid viewBox');
|
|
238
|
+
return { width: +m[1], height: +m[2] };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Gap between everything drawn (strokes, shadows, transforms) and each viewBox edge, smallest first. */
|
|
242
|
+
function edgeGaps(svg, vb) {
|
|
243
|
+
const b = new Resvg(svg).getBBox();
|
|
244
|
+
if (!b) return [];
|
|
245
|
+
const gaps = { top: b.y, bottom: vb.height - b.y - b.height, left: b.x, right: vb.width - b.x - b.width };
|
|
246
|
+
return Object.entries(gaps).sort((a, b) => a[1] - b[1]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const px = (v) => +v.toFixed(1);
|
|
250
|
+
const ceil1 = (v) => Math.ceil(v * 10) / 10;
|
|
251
|
+
const ceil8 = (v) => Math.ceil(v / 8) * 8;
|
|
252
|
+
const floor8 = (v) => Math.floor(v / 8) * 8;
|
|
253
|
+
|
|
254
|
+
const minHeaderPx = (letterHeight, height) => ceil8((MIN_LETTER_PX * height) / letterHeight);
|
|
255
|
+
const maxWidthAt = (height, smallPx) => Math.floor((MAX_SMALL_WIDTH_PX * height) / smallPx);
|
|
256
|
+
|
|
257
|
+
function autoHeights(letterHeight, height) {
|
|
258
|
+
const small = Math.min(minHeaderPx(letterHeight, height), MAX_HEADER_PX);
|
|
259
|
+
return { desktop: Math.max(small, MIN_DESKTOP_PX), small };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The widest viewBox a logo of this height can have and still fit 192 px on small screens at the
|
|
264
|
+
* height `checkLogo` picks. Call it while composing, before the first build: it is roughly
|
|
265
|
+
* 10–12 × `letterHeight`, so a long name stacks or tags instead of running on one line.
|
|
266
|
+
* @param {number} letterHeight viewBox units, as passed to `checkLogo`
|
|
267
|
+
* @param {number} height the viewBox height
|
|
268
|
+
*/
|
|
269
|
+
export function widthBudget(letterHeight, height) {
|
|
270
|
+
return maxWidthAt(height, autoHeights(letterHeight, height).small);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Collects PASS/FAIL/SKIP lines; `print` returns how many failed. */
|
|
274
|
+
function sizeReport(label, vb) {
|
|
275
|
+
const lines = [];
|
|
276
|
+
return {
|
|
277
|
+
rule(ok, text, fix) {
|
|
278
|
+
lines.push(ok ? `PASS ${text}` : `FAIL ${text} → ${fix}`);
|
|
279
|
+
},
|
|
280
|
+
skip(text) {
|
|
281
|
+
lines.push(`SKIP ${text}`);
|
|
282
|
+
},
|
|
283
|
+
headroom(svg, of) {
|
|
284
|
+
if (!Resvg) return this.skip('headroom: install @resvg/resvg-js to measure it');
|
|
285
|
+
const [edge, gap] = edgeGaps(svg, vb)[0] ?? [];
|
|
286
|
+
if (edge === undefined) return this.skip(`headroom: nothing drawn on ${of}`);
|
|
287
|
+
const min = HEADROOM * vb.height;
|
|
288
|
+
this.rule(gap >= min, `headroom ${px(gap)} at the ${edge} of ${of} (≥ ${px(min)} viewBox units)`,
|
|
289
|
+
`add ${ceil1(min - gap)} viewBox units at the ${edge}: grow the viewBox on that side or move the drawing in; accents, descenders, tilt, outline and shadow all need room`);
|
|
290
|
+
},
|
|
291
|
+
print() {
|
|
292
|
+
console.log(`${label} (viewBox ${px(vb.width)} × ${px(vb.height)}):`);
|
|
293
|
+
for (const line of lines) console.log(` ${line}`);
|
|
294
|
+
return lines.filter((l) => l.startsWith('FAIL')).length;
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function logoSizes(svg, { letterHeight, heights }) {
|
|
300
|
+
if (!letterHeight || (heights && (!heights.desktop || !heights.small))) {
|
|
301
|
+
throw new TypeError('logo-kit checkLogo: pass letterHeight, and heights: { desktop, small } only to set both');
|
|
302
|
+
}
|
|
303
|
+
const vb = viewBoxOf(svg);
|
|
304
|
+
const report = sizeReport('logo.svg', vb);
|
|
305
|
+
const minHeaderH = minHeaderPx(letterHeight, vb.height);
|
|
306
|
+
const maxSmall = floor8((MAX_SMALL_WIDTH_PX * vb.height) / vb.width);
|
|
307
|
+
const { desktop, small } = heights ?? autoHeights(letterHeight, vb.height);
|
|
308
|
+
const lettersAt = (h) => (letterHeight / vb.height) * h;
|
|
309
|
+
const width = (vb.width / vb.height) * small;
|
|
310
|
+
const canLowerSmall = small > minHeaderH && minHeaderH <= maxSmall;
|
|
311
|
+
const widthFixes = [
|
|
312
|
+
...(canLowerSmall ? [`--logo-h-sm ${minHeaderH === maxSmall ? minHeaderH : `${minHeaderH}–${maxSmall}`} px passes both`] : []),
|
|
313
|
+
`make the viewBox at most ${maxWidthAt(vb.height, small)} units wide (now ${px(vb.width)}) at this height: stack or tag the words, tighten gaps, or show logo-mark.svg with the name beside it on small screens`,
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
report.rule(desktop % 8 === 0 && small % 8 === 0, `heights ${desktop} / ${small} px are multiples of 8`, 'round them to multiples of 8');
|
|
317
|
+
const capped = !heights && minHeaderH > MAX_HEADER_PX;
|
|
318
|
+
const minLetterAtCap = ceil1((MIN_LETTER_PX * vb.height) / MAX_HEADER_PX);
|
|
319
|
+
const maxHeightAtCap = Math.floor((letterHeight * MAX_HEADER_PX) / MIN_LETTER_PX);
|
|
320
|
+
const lettersFixes = (name) => capped
|
|
321
|
+
? [`the header logo stops at ${MAX_HEADER_PX} px: make letterHeight at least ${minLetterAtCap} viewBox units (now ${px(letterHeight)})`,
|
|
322
|
+
`make the viewBox at most ${maxHeightAtCap} units tall (now ${px(vb.height)}): trim empty space or set the mark beside the words instead of above them`]
|
|
323
|
+
: [`raise ${name} to ${minHeaderH} px or more`, 'enlarge the letters inside the viewBox', 'trim empty viewBox space'];
|
|
324
|
+
const letterChecks = desktop === small ? [['--logo-h and --logo-h-sm', small]] : [['--logo-h', desktop], ['--logo-h-sm', small]];
|
|
325
|
+
for (const [name, h] of letterChecks) {
|
|
326
|
+
report.rule(lettersAt(h) >= MIN_LETTER_PX, `letters ${px(lettersAt(h))} px tall at ${name} ${h} px (≥ ${MIN_LETTER_PX})`,
|
|
327
|
+
lettersFixes(name).join(', or '));
|
|
328
|
+
}
|
|
329
|
+
report.rule(width <= MAX_SMALL_WIDTH_PX, `width ${px(width)} px at --logo-h-sm ${small} px (≤ ${MAX_SMALL_WIDTH_PX})`,
|
|
330
|
+
widthFixes.join(', or '));
|
|
331
|
+
report.headroom(svg, 'the viewBox');
|
|
332
|
+
const measured = `- **Measured:** letters ${px(lettersAt(small))} px tall at \`--logo-h-sm\`; width ${px(width)} px at \`--logo-h-sm\``;
|
|
333
|
+
return { report, heights: { desktop, small }, measured };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function tileChecks(svg, { favicon = false } = {}) {
|
|
337
|
+
const vb = viewBoxOf(svg);
|
|
338
|
+
const report = sizeReport(favicon ? 'favicon.svg' : 'logo-mark.svg', vb);
|
|
339
|
+
const tileTag = svg.match(/<rect data-kit="tile"[^>]*\/>/)?.[0];
|
|
340
|
+
report.rule(Boolean(tileTag), 'drawn on a tile()', "draw the tile with the kit's tile(size, fill)");
|
|
341
|
+
if (!Resvg) report.skip('clipping: install @resvg/resvg-js to measure it');
|
|
342
|
+
else {
|
|
343
|
+
const gap = edgeGaps(svg, vb)[0]?.[1] ?? 0;
|
|
344
|
+
report.rule(gap >= -CLIP_SLACK, 'nothing drawn outside the viewBox', 'shrink or move the drawing in; the tile already fills the viewBox');
|
|
345
|
+
}
|
|
346
|
+
if (tileTag) report.headroom(svg.replace(tileTag, ''), 'the tile');
|
|
347
|
+
if (favicon) {
|
|
348
|
+
report.rule(!/data-kit="shadow"|<filter|drop-shadow/.test(svg), 'no shadow', 'pass shadow: 0 to sticker() and draw no other shadow');
|
|
349
|
+
}
|
|
350
|
+
return report;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderPreview({ logo, mark, favicon, heights, background, out }) {
|
|
354
|
+
const render = (svg, h, zoom) => {
|
|
355
|
+
const img = new Resvg(svg, { fitTo: { mode: 'height', value: h } }).render();
|
|
356
|
+
return { w: img.width * zoom, h: img.height * zoom, href: `data:image/png;base64,${img.asPng().toString('base64')}` };
|
|
357
|
+
};
|
|
358
|
+
const tiles = (fill) => ({ fill, items: [render(mark, 32, 1), render(mark, 32, 4), render(favicon, 16, 1), render(favicon, 16, 8)] });
|
|
359
|
+
const bands = [
|
|
360
|
+
{
|
|
361
|
+
label: `logo.svg at --logo-h ${heights.desktop} px and --logo-h-sm ${heights.small} px, then 2×, on the page background`,
|
|
362
|
+
fill: background,
|
|
363
|
+
items: [render(logo, heights.desktop, 1), render(logo, heights.small, 1), render(logo, heights.desktop, 2), render(logo, heights.small, 2)],
|
|
364
|
+
},
|
|
365
|
+
{ label: 'light: logo-mark.svg at 32 px and 4×, favicon.svg at 16 px and 8×', ...tiles('#f1f3f4') },
|
|
366
|
+
{ label: 'dark: the same', ...tiles('#202124') },
|
|
367
|
+
];
|
|
368
|
+
const PAD = 16;
|
|
369
|
+
const LABEL = 28;
|
|
370
|
+
let body = '';
|
|
371
|
+
let y = 0;
|
|
372
|
+
let width = 480;
|
|
373
|
+
for (const { label, fill, items } of bands) {
|
|
374
|
+
const rowH = Math.max(...items.map((i) => i.h)) + PAD * 2;
|
|
375
|
+
body += `<text x="${PAD}" y="${y + 19}" font-family="sans-serif" font-size="13" fill="#555">${label}</text>`;
|
|
376
|
+
y += LABEL;
|
|
377
|
+
body += `<rect y="${y}" width="100%" height="${rowH}" fill="${fill}"/>`;
|
|
378
|
+
let x = PAD;
|
|
379
|
+
for (const i of items) {
|
|
380
|
+
body += `<image href="${i.href}" x="${x}" y="${y + (rowH - i.h) / 2}" width="${i.w}" height="${i.h}" image-rendering="optimizeSpeed"/>`;
|
|
381
|
+
x += i.w + PAD * 2;
|
|
382
|
+
}
|
|
383
|
+
width = Math.max(width, x - PAD);
|
|
384
|
+
y += rowH;
|
|
385
|
+
}
|
|
386
|
+
const sheet = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${y}"><rect width="100%" height="100%" fill="#fff"/>${body}</svg>`;
|
|
387
|
+
writeFileSync(out, new Resvg(sheet).render().asPng());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Check the three finished files and render one PNG to look at instead of the SVG source.
|
|
392
|
+
* Throws first if any file fails `check()`.
|
|
393
|
+
* - logo.svg: letters ≥ 16 px at both header heights, width ≤ 192 px on small screens, 2% headroom
|
|
394
|
+
* around everything drawn, heights in multiples of 8. Without `heights`, `--logo-h-sm` is the
|
|
395
|
+
* smallest multiple of 8 that makes the letters 16 px, and `--logo-h` the same but at least 48 px;
|
|
396
|
+
* both stop at 80 px, so letters too small for 80 px fail.
|
|
397
|
+
* - logo-mark.svg, favicon.svg: drawn on a `tile()`, nothing outside the viewBox, 2% headroom
|
|
398
|
+
* around the symbol; the favicon has no shadow.
|
|
399
|
+
* - logo-preview.png: the logo at both heights on the page background, the mark at 32 px and the
|
|
400
|
+
* favicon at 16 px on a light and a dark strip, each also enlarged with hard pixels.
|
|
401
|
+
* Prints the heights and the Measured line first, then every PASS/FAIL line, each failure with its
|
|
402
|
+
* fixes, renders the preview, then throws if anything failed.
|
|
403
|
+
* @param {object} o
|
|
404
|
+
* @param {number} o.letterHeight viewBox units: `layout().xHeight` (mostly lowercase) or
|
|
405
|
+
* `.capHeight` (capitals) of the smaller word
|
|
406
|
+
* @param {{ desktop: number, small: number }} [o.heights] set both header heights yourself
|
|
407
|
+
* @param {string} [o.background] the page background colour
|
|
408
|
+
* @returns {{ heights: { desktop: number, small: number }, measured: string }} heights for
|
|
409
|
+
* `--logo-h` / `--logo-h-sm`, and the design.md → Logo → **Measured** line
|
|
410
|
+
*/
|
|
411
|
+
export function checkLogo({ logo, mark, favicon, letterHeight, heights, background = '#ffffff', out = 'logo-preview.png' }) {
|
|
412
|
+
// resvg can't measure or render a file check() rejects, so these fail before any size line.
|
|
413
|
+
const broken = Object.entries({ 'logo.svg': logo, 'logo-mark.svg': mark, 'favicon.svg': favicon })
|
|
414
|
+
.flatMap(([name, svg]) => markupProblems(svg).map((p) => `${name}: ${p}`));
|
|
415
|
+
if (broken.length) throw new Error(`logo-kit checkLogo: ${broken.join('; ')}`);
|
|
416
|
+
const sizes = logoSizes(logo, { letterHeight, heights });
|
|
417
|
+
// First, so piping the output through `head` can't cut them off.
|
|
418
|
+
console.log(`--logo-h: ${sizes.heights.desktop}px; --logo-h-sm: ${sizes.heights.small}px`);
|
|
419
|
+
console.log(sizes.measured);
|
|
420
|
+
const reports = [sizes.report, tileChecks(mark), tileChecks(favicon, { favicon: true })];
|
|
421
|
+
const failed = reports.reduce((n, r) => n + r.print(), 0);
|
|
422
|
+
if (Resvg) {
|
|
423
|
+
renderPreview({ logo, mark, favicon, heights: sizes.heights, background, out });
|
|
424
|
+
console.log(`preview: read ${out}`);
|
|
425
|
+
} else {
|
|
426
|
+
console.log('preview not rendered: @resvg/resvg-js is not installed; list "logo not visually checked" under gaps');
|
|
427
|
+
}
|
|
428
|
+
if (failed) throw new Error(`logo-kit checkLogo: ${failed} check(s) failed, see the FAIL lines above`);
|
|
429
|
+
return { heights: sizes.heights, measured: sizes.measured };
|
|
201
430
|
}
|
|
@@ -103,6 +103,12 @@ Read the backend from `site.json`:
|
|
|
103
103
|
`yoast_head_json` into the page `<head>` (title, description, Open Graph, JSON-LD schema) — Yoast is
|
|
104
104
|
installed by default at provision (`site.json → seo.enabled: true`). If `seo.enabled` is `false`
|
|
105
105
|
(Yoast absent), fall back to generating per-page title/description/OG tags from each item's content.
|
|
106
|
+
- **Decode WordPress text.** WordPress returns titles and names HTML-encoded (`Tea & Cake`,
|
|
107
|
+
`don’t`). Add the `entities` package and, in the one data module, run every title, name
|
|
108
|
+
and SEO field you render as plain text (posts, pages, terms, Yoast, Store API products and
|
|
109
|
+
categories) through `decodeHTML` once; strip an excerpt's tags first if you show it as text.
|
|
110
|
+
Leave HTML fields (`content.rendered`, an excerpt shown as HTML) as they are, and never inject a
|
|
111
|
+
decoded field as raw HTML.
|
|
106
112
|
- **Contact form** (`site.json → contact_form.enabled: true`, any WordPress backend) → render the
|
|
107
113
|
contact page's form and submit it **client-side** to Contact Form 7's public feedback endpoint. Put a
|
|
108
114
|
`submitContactForm(data)` helper in the one data module: build a `FormData` with the field names from
|
|
@@ -12,6 +12,11 @@ serves the result. **Out:** `deploy.json`.
|
|
|
12
12
|
This is an **instance-barrier** step: run it only after `provision.status == "completed"` and validate
|
|
13
13
|
(if run) has passed. All operations go through the connector (`execute`).
|
|
14
14
|
|
|
15
|
+
> **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git
|
|
16
|
+
> repositories only and cannot read a local `path`. Run this step through the local
|
|
17
|
+
> `hostinger-api-mcp` server instead (see the connect step:
|
|
18
|
+
> `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).
|
|
19
|
+
|
|
15
20
|
**In:** the project in `~/.hostinger/<domain>/src/`, `site.json` (`username`, `urls`), `state.json`
|
|
16
21
|
(`frontend.framework`, `frontend.rendering`).
|
|
17
22
|
|
|
@@ -188,7 +193,7 @@ Update `state.json` (`stage` → `done`), then continue to Step 9 (done).
|
|
|
188
193
|
|
|
189
194
|
## Note — one deploy path, and never `type: "static"`
|
|
190
195
|
|
|
191
|
-
`hosting_deploy` is the only path: give its `path` either the source **directory** (it archives it for
|
|
196
|
+
`hosting_deploy` on the local server is the only path: give its `path` either the source **directory** (it archives it for
|
|
192
197
|
you) or a local **archive** (`zip`, `tar`, `tar.gz`, `tgz`, `7z`) — there is no separate upload or build
|
|
193
198
|
step to fall back to. Either way pass the explicit `appType`/`outputDirectory`/`nodeVersion`/
|
|
194
199
|
`buildScript`/`packageManager`/`rootDirectory` from the table. Never deploy a headless site with
|
|
@@ -41,7 +41,7 @@ Create the Hostinger website on the domain being migrated, not on a free subdoma
|
|
|
41
41
|
|
|
42
42
|
## 3. Import
|
|
43
43
|
|
|
44
|
-
`hosting_deploy` and `agency-hosting_deploy-php-application` read files from this machine
|
|
44
|
+
`hosting_deploy` with `path` and `agency-hosting_deploy-php-application` read files from this machine, so only the local `hostinger-api-mcp` server has them; the hosted server's `hosting_deploy` deploys Git repositories only. On the hosted server (`mcp.hostinger.com`), upload the files first as described in the `deploy-to-hosting` skill ("Without the local deploy operations") and use the operation named in brackets below.
|
|
45
45
|
|
|
46
46
|
**WordPress, Shared and Cloud:** `hosting_deploy` with `domain`, `path` (the site directory or its archive), `databaseDump` and `type: "wordpress-site"` uploads both, extracts the files and imports the database; large sites take several minutes. Always pass `type`: left to detection, a directory without `wp-config.php` or `wp-content` at its top level is taken for a plain site and the dump is not imported. [Upload `site.zip` and `dump.sql`, then `wordpress_installations_import-website` with `archive_path` and `sql_path`.] Then `wordpress_installations_detect` with `username` and poll `wordpress_installations_list` with `domain` until the install appears with `is_valid: true`.
|
|
47
47
|
|
|
@@ -44,9 +44,9 @@ dig +short NS DOMAIN; dig +short A DOMAIN; dig +short CAA DOMAIN
|
|
|
44
44
|
|
|
45
45
|
### Node.js (`nodejs`)
|
|
46
46
|
|
|
47
|
-
Batch `hosting_get-deployment` (the latest build with its log, failure analysis and stored settings), `hosting_nodejs_runtime-logs` (`period: "1h"`, `levels: ["ERROR", "WARN"]`) and `hosting_nodejs_list-environment-variables` (keys only; values always come back masked).
|
|
47
|
+
Batch `hosting_get-deployment` (the latest build with its log, failure analysis and stored settings), `hosting_nodejs_runtime-logs` (`period: "1h"`, `levels: ["ERROR", "WARN"]`) and `hosting_nodejs_list-environment-variables` (keys only; values always come back masked).
|
|
48
48
|
|
|
49
|
-
- Latest build `failed`: `hosting_get-deployment` already holds the `analysis` (fetched once per build — the API re-runs it on every call and allows 5 a minute). A wrongly detected framework, output directory or missing `entry_file` is the usual cause: compare the returned `buildSettings` with the project and deploy again with corrected `appType`, `outputDirectory`, `buildScript` or `entryFile`; a Git deploy stores them, so pushes build correctly too.
|
|
49
|
+
- Latest build `failed`: `hosting_get-deployment` already holds the `analysis` (fetched once per build — the API re-runs it on every call and allows 5 a minute). A wrongly detected framework, output directory or missing `entry_file` is the usual cause: compare the returned `buildSettings` with the project and deploy again with corrected `appType`, `outputDirectory`, `buildScript` or `entryFile`; a Git deploy stores them, so pushes build correctly too. A site deployed on the hosted server from uploaded files has no `hosting_deploy` to repeat: rebuild through `hosting_nodejs_start-build` with the corrected values and store them with `hosting_nodejs_update-build-settings`.
|
|
50
50
|
- Build `completed` but the app errors: read the runtime logs; entries older than `last_deployed_at` belong to the previous deploy. A variable the code reads but the key list lacks is fixed with the `deploy-to-hosting` skill's environment step. `Access denied for user '…'@'::1'` means the app connects to `localhost` — `DB_HOST` must be `127.0.0.1`.
|
|
51
51
|
- Hung process: `hosting_nodejs_restart-application` (restarts without rebuilding).
|
|
52
52
|
|
package/src/core/catalog.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// multi-execute over a catalog of API operations. Pure functions only; the
|
|
3
3
|
// runtime supplies the catalog and the function that performs a call.
|
|
4
4
|
// Keep in sync with catalog.ts and with api-remote-mcp's PHP implementation.
|
|
5
|
-
// `hidden
|
|
5
|
+
// Both servers honour `hidden`: search skips the entry, execute and multi-execute still run
|
|
6
|
+
// it. Each server has its own list (generators.mcp.hidden and generators.remote-mcp.hidden).
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_SEARCH_LIMIT = 5;
|
|
8
9
|
export const MAX_SEARCH_LIMIT = 20;
|
package/src/core/catalog.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Typed mirror of catalog.js. Keep both files, and api-remote-mcp's PHP
|
|
2
|
-
// implementation, behaviourally identical. `hidden
|
|
3
|
-
//
|
|
2
|
+
// implementation, behaviourally identical. Both servers honour `hidden`: search skips the
|
|
3
|
+
// entry, execute and multi-execute still run it. Each server has its own list
|
|
4
|
+
// (generators.mcp.hidden and generators.remote-mcp.hidden in the API manifest).
|
|
4
5
|
|
|
5
6
|
export const DEFAULT_SEARCH_LIMIT = 5;
|
|
6
7
|
export const MAX_SEARCH_LIMIT = 20;
|
|
@@ -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- hosting_nodejs_start-build ->
|
|
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";
|