hostinger-api-mcp 3.14.1 → 3.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +9 -2
  3. package/gemini-extension.json +1 -1
  4. package/package.json +1 -1
  5. package/server.json +2 -2
  6. package/skills/headless/steps/01-check-state/SKILL.md +3 -3
  7. package/src/core/catalog.js +1 -1
  8. package/src/core/catalog.ts +1 -1
  9. package/src/core/runtime.js +50 -0
  10. package/src/core/runtime.ts +50 -0
  11. package/src/core/skills.js +3 -3
  12. package/src/core/skills.ts +3 -3
  13. package/src/core/tools/all.js +70 -0
  14. package/src/core/tools/all.ts +70 -0
  15. package/src/core/tools/hosting.js +70 -0
  16. package/src/core/tools/hosting.ts +70 -0
  17. package/src/servers/agency-hosting.js +1 -1
  18. package/src/servers/agency-hosting.ts +1 -1
  19. package/src/servers/agent-readiness.js +1 -1
  20. package/src/servers/agent-readiness.ts +1 -1
  21. package/src/servers/all.js +1 -1
  22. package/src/servers/all.ts +1 -1
  23. package/src/servers/billing.js +1 -1
  24. package/src/servers/billing.ts +1 -1
  25. package/src/servers/dns.js +1 -1
  26. package/src/servers/dns.ts +1 -1
  27. package/src/servers/domains.js +1 -1
  28. package/src/servers/domains.ts +1 -1
  29. package/src/servers/ecommerce.js +1 -1
  30. package/src/servers/ecommerce.ts +1 -1
  31. package/src/servers/horizons.js +1 -1
  32. package/src/servers/horizons.ts +1 -1
  33. package/src/servers/hosting.js +1 -1
  34. package/src/servers/hosting.ts +1 -1
  35. package/src/servers/mail.js +1 -1
  36. package/src/servers/mail.ts +1 -1
  37. package/src/servers/reach.js +1 -1
  38. package/src/servers/reach.ts +1 -1
  39. package/src/servers/vps.js +1 -1
  40. package/src/servers/vps.ts +1 -1
  41. package/src/servers/wordpress.js +1 -1
  42. package/src/servers/wordpress.ts +1 -1
  43. package/types.d.ts +21 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ Generated at release time from the diff between the previous and current
4
4
  build. Tool changes are listed individually; everything else is summarised
5
5
  by area.
6
6
 
7
+ ## v3.15.1 — 2026-10-27
8
+
9
+ ### Also in this release
10
+
11
+ - Request handling and authentication: `src/core/catalog.js`, `src/core/catalog.ts`
12
+
13
+ ## v3.15.0 — 2026-10-27
14
+
15
+ ### Operations
16
+
17
+ - Added `hosting_websites_trigger-event` — Trigger website event
18
+
19
+ ### Also in this release
20
+
21
+ - Documentation: `README.md`
22
+ - Bundled agent skills: `skills/headless/steps/01-check-state/SKILL.md`
23
+ - Request handling and authentication: `src/core/runtime.js`, `src/core/runtime.ts`
24
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
25
+ - Build and packaging: `types.d.ts`
26
+
7
27
  ## v3.14.1 — 2026-10-27
8
28
 
9
29
  ### Operations
package/README.md CHANGED
@@ -69,7 +69,7 @@ pnpm update -g @hostinger/mcp
69
69
 
70
70
  This package installs the following MCP server commands:
71
71
 
72
- - `hostinger-api-mcp` — unified server over every operation (417 total)
72
+ - `hostinger-api-mcp` — unified server over every operation (418 total)
73
73
  - `hostinger-agency-hosting-mcp` — 42 operations for agency-hosting
74
74
  - `hostinger-agent-readiness-mcp` — 3 operations for agent-readiness
75
75
  - `hostinger-billing-mcp` — 9 operations for billing
@@ -77,7 +77,7 @@ This package installs the following MCP server commands:
77
77
  - `hostinger-domains-mcp` — 42 operations for domains
78
78
  - `hostinger-ecommerce-mcp` — 29 operations for ecommerce
79
79
  - `hostinger-horizons-mcp` — 6 operations for horizons
80
- - `hostinger-hosting-mcp` — 67 operations for hosting
80
+ - `hostinger-hosting-mcp` — 68 operations for hosting
81
81
  - `hostinger-mail-mcp` — 38 operations for mail
82
82
  - `hostinger-reach-mcp` — 69 operations for reach
83
83
  - `hostinger-vps-mcp` — 67 operations for vps
@@ -1828,6 +1828,13 @@ Check deployment status, build status, build logs and why a build failed (failur
1828
1828
  - **Method**: `custom`
1829
1829
  - **Path**: `custom`
1830
1830
 
1831
+ #### hosting_websites_trigger-event
1832
+
1833
+ Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.
1834
+
1835
+ - **Method**: `custom`
1836
+ - **Path**: `custom`
1837
+
1831
1838
  #### hosting_cache_clear-website
1832
1839
 
1833
1840
  Permanently clears all server-side cache for the website at once. Use it when content was
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.14.1",
3
+ "version": "3.15.1",
4
4
  "description": "Hostinger API — manage domains, DNS, hosting, VPS, billing and more from Gemini CLI.",
5
5
  "mcpServers": {
6
6
  "hostinger": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.14.1",
3
+ "version": "3.15.1",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/hostinger/api-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.14.1",
9
+ "version": "3.15.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@hostinger/mcp",
14
- "version": "3.14.1",
14
+ "version": "3.15.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -12,7 +12,7 @@ account can build. Your job here is to
12
12
  This step owns all resume/position logic — no other stage second-guesses it.
13
13
 
14
14
  Nothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its
15
- only writes to Hostinger are reports: unsent build reports (Step 0) and the connected tool (Step 3).
15
+ only writes to Hostinger are unsent build reports (Step 0) and the tool-connected event (Step 3).
16
16
 
17
17
  ---
18
18
 
@@ -113,11 +113,11 @@ Query the Hostinger connector — the local files are never trusted on their own
113
113
  (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site
114
114
  exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.
115
115
  - **Tell hPanel the tool connected.** As soon as you have the exact row, call
116
- `hosting_headless_report-tool-connected { "username": "<row username>", "domain": "<domain>", "tool": "<slug>" }`
116
+ `hosting_websites_trigger-event { "username": "<row username>", "domain": "<domain>", "event": { "name": "headless_onboarding.tool.connected", "params": { "tool": "<slug>" } } }`
117
117
  once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).
118
118
  This flips hPanel's onboarding card to "connected". Don't ask first and don't mention it. Skip the
119
119
  call when there's no exact row or your tool has no onboarding slug. If
120
- `hosting_headless_report-tool-connected` isn't in your connector, or the call returns an error,
120
+ `hosting_websites_trigger-event` isn't in your connector, or the call returns an error,
121
121
  ignore it: no retry, nothing said to the user, and the run carries on unchanged.
122
122
  - **How far did it get?** If the site exists, check its setup via
123
123
  `hosting_websites_list-setups { "domain": "<domain>" }` (exact-match the row the same way):
@@ -3,7 +3,7 @@
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
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
+ // it. Each server has its own list (generators.mcp.hiddenFile and generators.remote-mcp.hiddenFile).
7
7
 
8
8
  export const DEFAULT_SEARCH_LIMIT = 5;
9
9
  export const MAX_SEARCH_LIMIT = 20;
@@ -1,7 +1,7 @@
1
1
  // Typed mirror of catalog.js. Keep both files, and api-remote-mcp's PHP
2
2
  // implementation, behaviourally identical. Both servers honour `hidden`: search skips the
3
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
+ // (the files generators.mcp.hiddenFile and generators.remote-mcp.hiddenFile point to).
5
5
 
6
6
  export const DEFAULT_SEARCH_LIMIT = 5;
7
7
  export const MAX_SEARCH_LIMIT = 20;
@@ -316,6 +316,8 @@ class MCPServer {
316
316
  return await this.handleListJavascriptDeployments(params);
317
317
  case 'hosting_show-js-deployment-logs':
318
318
  return await this.handleShowJsDeploymentLogs(params);
319
+ case 'hosting_websites_trigger-event':
320
+ return await this.handleHostingTriggerWebsiteEvent(params);
319
321
  case 'agency-hosting_deploy-node-static-website':
320
322
  return await this.handleNodeStaticDeploy(params);
321
323
  case 'agency-hosting_deploy-php-application':
@@ -2782,6 +2784,54 @@ class MCPServer {
2782
2784
  };
2783
2785
  }
2784
2786
 
2787
+ async handleHostingTriggerWebsiteEvent(params) {
2788
+ const { username, domain, event } = params;
2789
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
2790
+ const url = new URL(`api/hosting/v1/accounts/${encodeURIComponent(String(username))}/websites/${encodeURIComponent(String(domain))}/events/trigger`, baseUrl).toString();
2791
+
2792
+ try {
2793
+ const bearerToken = await this.getAuthToken();
2794
+
2795
+ const config = {
2796
+ method: 'post',
2797
+ url,
2798
+ headers: {
2799
+ ...this.headers,
2800
+ 'Authorization': `Bearer ${bearerToken}`,
2801
+ 'Content-Type': 'application/json'
2802
+ },
2803
+ data: { event },
2804
+ timeout: 60000, // 60s
2805
+ validateStatus: function (status) {
2806
+ return status < 500;
2807
+ }
2808
+ };
2809
+
2810
+ const response = await axios(config);
2811
+
2812
+ if (response.status < 200 || response.status >= 300) {
2813
+ throw new ApiExecutionError(response.status, response.data);
2814
+ }
2815
+
2816
+ return response.data;
2817
+
2818
+ } catch (error) {
2819
+ const errorMessage = error instanceof Error ? error.message : String(error);
2820
+ this.log('error', `Failed to trigger website event: ${errorMessage}`);
2821
+
2822
+ if (axios.isAxiosError(error)) {
2823
+ const responseData = error.response?.data;
2824
+ const responseStatus = error.response?.status;
2825
+ this.log('error', 'API Error Details:', {
2826
+ status: responseStatus,
2827
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
2828
+ });
2829
+ }
2830
+
2831
+ throw error;
2832
+ }
2833
+ }
2834
+
2785
2835
  agencyHosting_normalizePath(pathString) {
2786
2836
  return pathString.replace(/\\/g, '/');
2787
2837
  }
@@ -347,6 +347,8 @@ class MCPServer {
347
347
  return await this.handleListJavascriptDeployments(params);
348
348
  case 'hosting_show-js-deployment-logs':
349
349
  return await this.handleShowJsDeploymentLogs(params);
350
+ case 'hosting_websites_trigger-event':
351
+ return await this.handleHostingTriggerWebsiteEvent(params);
350
352
  case 'agency-hosting_deploy-node-static-website':
351
353
  return await this.handleNodeStaticDeploy(params);
352
354
  case 'agency-hosting_deploy-php-application':
@@ -2819,6 +2821,54 @@ class MCPServer {
2819
2821
  };
2820
2822
  }
2821
2823
 
2824
+ private async handleHostingTriggerWebsiteEvent(params: Record<string, unknown>): Promise<unknown> {
2825
+ const { username, domain, event } = params;
2826
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
2827
+ const url = new URL(`api/hosting/v1/accounts/${encodeURIComponent(String(username))}/websites/${encodeURIComponent(String(domain))}/events/trigger`, baseUrl).toString();
2828
+
2829
+ try {
2830
+ const bearerToken = await this.getAuthToken();
2831
+
2832
+ const config: AxiosRequestConfig = {
2833
+ method: 'post',
2834
+ url,
2835
+ headers: {
2836
+ ...this.headers,
2837
+ 'Authorization': `Bearer ${bearerToken}`,
2838
+ 'Content-Type': 'application/json'
2839
+ },
2840
+ data: { event },
2841
+ timeout: 60000, // 60s
2842
+ validateStatus: function (status: number): boolean {
2843
+ return status < 500;
2844
+ }
2845
+ };
2846
+
2847
+ const response = await axios(config);
2848
+
2849
+ if (response.status < 200 || response.status >= 300) {
2850
+ throw new ApiExecutionError(response.status, response.data);
2851
+ }
2852
+
2853
+ return response.data;
2854
+
2855
+ } catch (error) {
2856
+ const errorMessage = error instanceof Error ? error.message : String(error);
2857
+ this.log('error', `Failed to trigger website event: ${errorMessage}`);
2858
+
2859
+ if (axios.isAxiosError(error)) {
2860
+ const responseData = error.response?.data;
2861
+ const responseStatus = error.response?.status;
2862
+ this.log('error', 'API Error Details:', {
2863
+ status: responseStatus,
2864
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
2865
+ });
2866
+ }
2867
+
2868
+ throw error;
2869
+ }
2870
+ }
2871
+
2822
2872
  private agencyHosting_normalizePath(pathString: string): string {
2823
2873
  return pathString.replace(/\\/g, '/');
2824
2874
  }
@@ -134,10 +134,10 @@ export default [
134
134
  },
135
135
  {
136
136
  "uri": "skill://hostinger-headless/steps/01-check-state/SKILL.md",
137
- "digest": "sha256:1c6cd793a02758c65d46fecb8e7d8fe1c50544dcc3bbde23b22786ebd24f754c",
138
- "size": 13173,
137
+ "digest": "sha256:043f25855abf92f43728b6e442bbd8d580fd2a6bb0270cfa7ed95dd41050ee60",
138
+ "size": 13227,
139
139
  "mimeType": "text/markdown",
140
- "text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its\nonly writes to Hostinger are reports: unsent build reports (Step 0) and the connected tool (Step 3).\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **Tell hPanel the tool connected.** As soon as you have the exact row, call\n `hosting_headless_report-tool-connected { \"username\": \"<row username>\", \"domain\": \"<domain>\", \"tool\": \"<slug>\" }`\n once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).\n This flips hPanel's onboarding card to \"connected\". Don't ask first and don't mention it. Skip the\n call when there's no exact row or your tool has no onboarding slug. If\n `hosting_headless_report-tool-connected` isn't in your connector, or the call returns an error,\n ignore it: no retry, nothing said to the user, and the run carries on unchanged.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess. A skipped tool result is ambiguous; see\n*When you ask the user* in the top-level `SKILL.md`.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
140
+ "text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its\nonly writes to Hostinger are unsent build reports (Step 0) and the tool-connected event (Step 3).\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **Tell hPanel the tool connected.** As soon as you have the exact row, call\n `hosting_websites_trigger-event { \"username\": \"<row username>\", \"domain\": \"<domain>\", \"event\": { \"name\": \"headless_onboarding.tool.connected\", \"params\": { \"tool\": \"<slug>\" } } }`\n once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).\n This flips hPanel's onboarding card to \"connected\". Don't ask first and don't mention it. Skip the\n call when there's no exact row or your tool has no onboarding slug. If\n `hosting_websites_trigger-event` isn't in your connector, or the call returns an error,\n ignore it: no retry, nothing said to the user, and the run carries on unchanged.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess. A skipped tool result is ambiguous; see\n*When you ask the user* in the top-level `SKILL.md`.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
141
141
  },
142
142
  {
143
143
  "uri": "skill://hostinger-headless/steps/02-refine/SKILL.md",
@@ -148,10 +148,10 @@ const skills: Skill[] = [
148
148
  },
149
149
  {
150
150
  "uri": "skill://hostinger-headless/steps/01-check-state/SKILL.md",
151
- "digest": "sha256:1c6cd793a02758c65d46fecb8e7d8fe1c50544dcc3bbde23b22786ebd24f754c",
152
- "size": 13173,
151
+ "digest": "sha256:043f25855abf92f43728b6e442bbd8d580fd2a6bb0270cfa7ed95dd41050ee60",
152
+ "size": 13227,
153
153
  "mimeType": "text/markdown",
154
- "text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its\nonly writes to Hostinger are reports: unsent build reports (Step 0) and the connected tool (Step 3).\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **Tell hPanel the tool connected.** As soon as you have the exact row, call\n `hosting_headless_report-tool-connected { \"username\": \"<row username>\", \"domain\": \"<domain>\", \"tool\": \"<slug>\" }`\n once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).\n This flips hPanel's onboarding card to \"connected\". Don't ask first and don't mention it. Skip the\n call when there's no exact row or your tool has no onboarding slug. If\n `hosting_headless_report-tool-connected` isn't in your connector, or the call returns an error,\n ignore it: no retry, nothing said to the user, and the run carries on unchanged.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess. A skipped tool result is ambiguous; see\n*When you ask the user* in the top-level `SKILL.md`.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
154
+ "text": "---\nname: headless-step-check-state\ndescription: \"First build step of the Hostinger Headless flow, run right after connect. Works out where a given domain's build stands — is a run already in progress, does the site already exist, how far did it get — by reconciling the global workspace state under ~/.hostinger/<domain>/ with what the Hostinger connector reports, then guides the user to continue, switch or buy a domain, or start over. Owns all resume/position logic for the flow.\"\n---\n\n# headless-step-check-state\n\nThe first build step of the Hostinger Headless flow (orchestrated by `hostinger.com/headless/skill.md`),\nrun right after connect. Connect has already connected the Hostinger connector and confirmed the\naccount can build. Your job here is to\n**figure out where this domain stands and agree the route with the user** before any new work begins.\nThis step owns all resume/position logic — no other stage second-guesses it.\n\nNothing in this step creates, provisions, deploys, or deletes anything. It reads and guides. Its\nonly writes to Hostinger are unsent build reports (Step 0) and the tool-connected event (Step 3).\n\n---\n\n## Where state lives\n\nEverything for a domain lives under the global site workspace **`~/.hostinger/<domain>/`** — one\ndirectory per domain, under the user's home (`~` = `$HOME`), **not** the current working directory.\nThe directory name **is** the domain, so `state.json` inside does not store it. Because it's global,\nthis domain's build is resumable from any directory.\n\n`state.json` shape (`stage` is the *next* stage to run; `provision` is the background task's status,\npresent only once provision has fired):\n\n```json\n{ \"tool\": \"claude-code\", \"stage\": \"content_model\",\n \"provision\": { \"status\": \"in_progress\", \"domain\": \"<domain>\", \"started_at\": \"…\" },\n \"updated_at\": \"…\" }\n```\n\n`provision.status` is `in_progress` or `completed` — but it is a **hint**: always re-verify it against\n`hosting_websites_list-setups` (Step 1) before trusting it to gate an instance-barrier step.\n\n---\n\n## Step 0 — Start the run log\n\nBefore anything else, start a run and the stage (see *Run log and build report* in the top-level\n`SKILL.md`):\n\n```bash\nnode <skill>/run-log.mjs <domain> run-start --tool <tool> --model <model> --skill <skill version>\nnode <skill>/run-log.mjs <domain> stage-start check_state\n```\n\nIf an earlier run on this domain never wrote its report, `run-start` closes it as `abandoned` first.\n\nThen send any build reports that haven't reached Hostinger yet (that abandoned run, or a send that\nfailed last time):\n\n```bash\nnode <skill>/run-log.mjs <domain> unsent\n```\n\nEach line is `<run> <path>`. For each, send the file through `hosting_headless_submit-build-report`\nand record the result, as described in *Run log and build report* in the top-level `SKILL.md`. One\ntry each. Don't ask first; if any went out, say so in one line (e.g. \"Sent 1 earlier build report to\nHostinger.\"). Don't let a failure hold up this step.\nCheck-state runs once per session. A follow-up after the final report, in the same session, doesn't\ncome back here: it calls `run-resume` (see *Run log and build report* in the top-level `SKILL.md`).\n\n## Step 1 — Is a build actively running right now? (check this before anything else)\n\nKeep two things separate, and **never guess from a timestamp.**\n\n- **A platform build actively in progress** — the only *reliable* \"running now\" signal. Check\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` and exact-match the domain row:\n - `status: running` → provisioning is underway **right now** (a background provision from an earlier\n turn). Don't start a second one — reconcile `state.json`'s `provision` to `in_progress`, and\n either resume prepare work while it finishes or poll it to `completed` at the barrier.\n - `status: failed` → not running; route to provision to self-heal (Step 3).\n - `completed` → provision is done; set `provision.status` to `completed`. Instance-barrier steps\n (content push, frontend data, deploy) are now unblocked.\n - no row → not provisioned yet; carry on.\n\n This check **is** the source of truth for `provision.status`: a stored `in_progress`/`completed` in\n `state.json` is only a hint until this reconciles it.\n\n- **A `.lock`** — `~/.hostinger/<domain>/.lock`. It only tells you *a previous run\n touched this domain*. **Do not judge it by age** — a 2-minute-old lock and a 3-year-old lock get the\n same treatment, and a lock on its own is **not** a reason to interrupt the user. Carry it into Step 5:\n if there's nothing to resume (new build), silently take it over; only if there's real state to resume\n do you also ask *is another Claude window building this domain right now?* (yes → stop; no → take\n over).\n\nOnce you're actively working this domain, **acquire/refresh the lock** (write the current timestamp),\nand **release it on a clean stop** (Step 6). The lock marks an active session, nothing more.\n\n---\n\n## Step 2 — Read the workspace directory\n\nCheck `~/.hostinger/<domain>/` by its exact path (for example `test -d ~/.hostinger/<domain>`), not\nby listing `~/.hostinger/`:\n\n- **Absent** → no history for this domain in the workspace.\n- **Present** → read `state.json` (its `stage`) and whatever artifacts exist (`brief.md`,\n `site.json`, …). Treat these as a hint, not the truth.\n\n---\n\n## Step 3 — Verify against reality (reality wins)\n\nQuery the Hostinger connector — the local files are never trusted on their own:\n\n- **Does the site exist?** `hosting_websites_list { \"domain\": \"<domain>\" }`. ⚠️ This filter is a\n **case-insensitive substring match**, not exact — so `test.com` would also return\n `shop.test.com`. **Compare each returned row's `domain` to your target for an *exact* match**\n (case-insensitive) and ignore the rest. No exact row ⇒ the domain is free. An exact row ⇒ the site\n exists; note its `website_type` (`headless`, `wordpress`, `nodejs`, …) and `username`.\n- **Tell hPanel the tool connected.** As soon as you have the exact row, call\n `hosting_websites_trigger-event { \"username\": \"<row username>\", \"domain\": \"<domain>\", \"event\": { \"name\": \"headless_onboarding.tool.connected\", \"params\": { \"tool\": \"<slug>\" } } }`\n once, where `<slug>` is the onboarding slug (see *State and artifacts* in the top-level `SKILL.md`).\n This flips hPanel's onboarding card to \"connected\". Don't ask first and don't mention it. Skip the\n call when there's no exact row or your tool has no onboarding slug. If\n `hosting_websites_trigger-event` isn't in your connector, or the call returns an error,\n ignore it: no retry, nothing said to the user, and the run carries on unchanged.\n- **How far did it get?** If the site exists, check its setup via\n `hosting_websites_list-setups { \"domain\": \"<domain>\" }` (exact-match the row the same way):\n - `status: completed` → provisioned; then, depending on backend, check whether a build has succeeded.\n - `status: running` → still provisioning (see Step 1).\n - `status: failed` → provisioning stopped before finishing → route to **provision** to self-heal.\n- **Is it an empty headless shell from onboarding?** A `headless` site whose setup is `completed` is\n **not necessarily finished** — onboarding now creates the website *before* the build runs, as an\n **empty headless shell** (`hosting_websites_start-setup { type: \"headless_empty\" }`): `website_type`\n is `headless` but **no backend is installed** (no WordPress at `/cms`, no store). Tell a shell from a\n real backend the same way provision does: within 24 h the setup `type` is `headless_empty`; otherwise\n `wordpress_installations_list { username, domain, ownership: \"all\" }` returns **no** `/cms` row and\n there is no `site.json` recording a backend. An empty shell means **provision has not chosen or\n installed a backend yet** — this is a **new build** that happens to already own its website, not a\n finished or resumable one. Route it to **refine** (choose the backend), then **provision** adopts the\n shell and swaps it to that backend (see the provision router's *Onboarding handoff*). Never create a\n second site, and never skip provision for it.\n\nReconcile with the local state. **When they disagree, reality wins** and the local `stage` is\ncorrected.\n\n---\n\n## Step 4 — Determine the position\n\nConnect always writes `state.json` (with `stage: check_state`) before this step runs, so local\nstate is never truly empty. **\"Local state: none\" here means only connect artifacts exist** —\n`connect.json` + a `state.json` still pointing at `check_state`/`refine`, and **no build artifacts\nyet** (`brief.md`, `site.json`, …).\n\n| Local state | Reality (domain) | Position |\n|---|---|---|\n| none (connect only) | free | **new build** — start from refine |\n| none (connect only) | empty headless shell (onboarding) | **new build on an existing shell** — start from refine; provision adopts the shell and swaps it to the chosen backend. Never create a second site, never skip provision |\n| none (connect only) | site exists with a real backend | **adopt / resume** — never create a second site; route to the first unfinished stage |\n| build artifacts present, incomplete | matches | **resume** at `state.json`'s `stage` |\n| build artifacts present | disagrees | **reality wins** — recompute the stage from what actually exists |\n| build artifacts present, all done | live site exists | **finished** — this is now an *iterate* request, not a build |\n\n---\n\n## Step 5 — Guide the user\n\nThe question to ask depends on whether there is actually **something to resume** — i.e. **build\nartifacts** (`brief.md`, `site.json`, …) or a **live site** for this domain. A stray `.lock` or\nconnect-only files are *not* something to resume.\n\n- **New build** (no build artifacts and either the domain is free **or** it holds only an empty\n headless shell from onboarding) → just proceed to refine. **Don't ask resume vs start over** —\n there's nothing to resume; both would land in the same place. An empty headless shell is not\n \"something to resume\": it has no backend and no content yet, so it's a fresh build that already owns\n its website — provision will adopt and swap it. Mention it in one line (\"Using the headless site\n onboarding already created for this domain.\"). If a leftover `.lock` is lying around, silently take\n it over and continue (optionally mention it in one line).\n- **Something to resume** (build artifacts exist, or a site exists for the domain **with a real backend\n installed** — WordPress at `/cms`, a store, or a deployed app) → tell the user plainly what's there\n and **ask them to choose**, regardless of how old it is (don't guess from timestamps):\n\n- **Resume** — pick up at the computed stage. (If a `.lock` is present in this case, also confirm no\n other window is actively building this domain right now.)\n- **Start over / redo a step** — only safe **before anything is provisioned**. If the site is already\n provisioned or live, you **cannot** re-create it: that is forward-only *iterate* (change content or\n design and redeploy), not a fresh build. Never re-provision or overwrite a live site; if the user\n insists on a clean slate for a live domain, explain the limitation and point them at hPanel.\n- **Switch domain** — they want a different domain. A new domain is a **new workspace**\n (`~/.hostinger/<other-domain>/`, with its own `src/`). Hand off to start that domain's run.\n- **Buy a domain** — they want a custom domain instead of the temporary one. Route to the domain\n purchase / `connect-domain` flow, then continue with the chosen domain.\n\nWhen the intent is ambiguous, **ask** — don't guess. A skipped tool result is ambiguous; see\n*When you ask the user* in the top-level `SKILL.md`.\n\n---\n\n## Step 6 — Record and route\n\nWrite `~/.hostinger/<domain>/state.json` with the agreed next `stage` and a fresh timestamp. If the\nroute isn't a new build, record it with `run-log.mjs <domain> run-update --kind resume` (or\n`redesign` / `iterate`), then end the stage with `stage-end check_state ok --note \"<the route>\"`.\nThen:\n\n- **Handing straight to the next stage in this same session?** Keep the `.lock` (you are still\n actively running) and continue.\n- **Stopping and returning control to the user?** (e.g. the next stage isn't built yet, or you are\n waiting on them.) **Delete the `.lock`** — the session is no longer active, and a leftover lock just\n forces an unnecessary \"is another window running?\" question on the next run. Always release the lock\n on a clean stop; it exists only to mark an actively-running session. A stop is still the end of a\n run: give the final report and save the build report (`09-done`).\n\n---\n\n## Invariants\n\n- **One directory per domain.** The domain is the key; state files never repeat it.\n- **Only this domain's workspace.** Never list `~/.hostinger/` or open another site's workspace (see\n *State and artifacts* in the top-level `SKILL.md`).\n- **Never create a second site** on a domain that already has one.\n- **Never re-provision or overwrite a live site.** Recovery is forward-only.\n- **Reality beats local state**, always.\n- **When ambiguous, ask.**\n"
155
155
  },
156
156
  {
157
157
  "uri": "skill://hostinger-headless/steps/02-refine/SKILL.md",
@@ -559,6 +559,76 @@ export default [
559
559
  "group": "hosting",
560
560
  "hidden": true
561
561
  },
562
+ {
563
+ "name": "hosting_websites_trigger-event",
564
+ "title": "Trigger website event",
565
+ "annotations": {
566
+ "title": "Trigger website event",
567
+ "readOnlyHint": false,
568
+ "destructiveHint": false
569
+ },
570
+ "topic": "hosting",
571
+ "description": "Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.",
572
+ "method": "",
573
+ "path": "",
574
+ "inputSchema": {
575
+ "type": "object",
576
+ "properties": {
577
+ "username": {
578
+ "type": "string",
579
+ "description": "Username of the hosting account that owns the website, as hosting_websites_list returns it"
580
+ },
581
+ "domain": {
582
+ "type": "string",
583
+ "description": "Domain name of the website (e.g., example.com)"
584
+ },
585
+ "event": {
586
+ "type": "object",
587
+ "description": "Event to trigger; `name` decides which `params` it takes",
588
+ "properties": {
589
+ "name": {
590
+ "type": "string",
591
+ "description": "Name of the event. `headless_onboarding.tool.connected`: an AI coding tool started building this website with the Hostinger headless skill, so hPanel shows the tool as connected; send it once per skill run; takes `params.tool`.",
592
+ "enum": [
593
+ "headless_onboarding.tool.connected"
594
+ ]
595
+ },
596
+ "params": {
597
+ "type": "object",
598
+ "description": "Fields of the event",
599
+ "properties": {
600
+ "tool": {
601
+ "type": "string",
602
+ "description": "Lowercase slug of the AI coding tool. Known slugs: `claude` (Claude Code), `cursor` (Cursor), `codex` (Codex), `vscode` (VS Code), `antigravity` (Antigravity) and `devin` (Devin). For any other tool, send its own slug.",
603
+ "pattern": "^[a-z0-9-]{1,32}$"
604
+ }
605
+ },
606
+ "required": [
607
+ "tool"
608
+ ],
609
+ "additionalProperties": false
610
+ }
611
+ },
612
+ "required": [
613
+ "name",
614
+ "params"
615
+ ],
616
+ "additionalProperties": false
617
+ }
618
+ },
619
+ "required": [
620
+ "username",
621
+ "domain",
622
+ "event"
623
+ ]
624
+ },
625
+ "security": [],
626
+ "custom": true,
627
+ "templateFile": "trigger-website-event.template.js",
628
+ "templateFileTS": "trigger-website-event.template.ts",
629
+ "handlerMethod": "handleHostingTriggerWebsiteEvent",
630
+ "group": "hosting"
631
+ },
562
632
  {
563
633
  "name": "agency-hosting_datacenters_list",
564
634
  "title": "List available datacenters",
@@ -575,6 +575,76 @@ const tools: OpenApiTool[] = [
575
575
  "group": "hosting",
576
576
  "hidden": true
577
577
  },
578
+ {
579
+ "name": "hosting_websites_trigger-event",
580
+ "title": "Trigger website event",
581
+ "annotations": {
582
+ "title": "Trigger website event",
583
+ "readOnlyHint": false,
584
+ "destructiveHint": false
585
+ },
586
+ "topic": "hosting",
587
+ "description": "Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.",
588
+ "method": "",
589
+ "path": "",
590
+ "inputSchema": {
591
+ "type": "object",
592
+ "properties": {
593
+ "username": {
594
+ "type": "string",
595
+ "description": "Username of the hosting account that owns the website, as hosting_websites_list returns it"
596
+ },
597
+ "domain": {
598
+ "type": "string",
599
+ "description": "Domain name of the website (e.g., example.com)"
600
+ },
601
+ "event": {
602
+ "type": "object",
603
+ "description": "Event to trigger; `name` decides which `params` it takes",
604
+ "properties": {
605
+ "name": {
606
+ "type": "string",
607
+ "description": "Name of the event. `headless_onboarding.tool.connected`: an AI coding tool started building this website with the Hostinger headless skill, so hPanel shows the tool as connected; send it once per skill run; takes `params.tool`.",
608
+ "enum": [
609
+ "headless_onboarding.tool.connected"
610
+ ]
611
+ },
612
+ "params": {
613
+ "type": "object",
614
+ "description": "Fields of the event",
615
+ "properties": {
616
+ "tool": {
617
+ "type": "string",
618
+ "description": "Lowercase slug of the AI coding tool. Known slugs: `claude` (Claude Code), `cursor` (Cursor), `codex` (Codex), `vscode` (VS Code), `antigravity` (Antigravity) and `devin` (Devin). For any other tool, send its own slug.",
619
+ "pattern": "^[a-z0-9-]{1,32}$"
620
+ }
621
+ },
622
+ "required": [
623
+ "tool"
624
+ ],
625
+ "additionalProperties": false
626
+ }
627
+ },
628
+ "required": [
629
+ "name",
630
+ "params"
631
+ ],
632
+ "additionalProperties": false
633
+ }
634
+ },
635
+ "required": [
636
+ "username",
637
+ "domain",
638
+ "event"
639
+ ]
640
+ },
641
+ "security": [],
642
+ "custom": true,
643
+ "templateFile": "trigger-website-event.template.js",
644
+ "templateFileTS": "trigger-website-event.template.ts",
645
+ "handlerMethod": "handleHostingTriggerWebsiteEvent",
646
+ "group": "hosting"
647
+ },
578
648
  {
579
649
  "name": "agency-hosting_datacenters_list",
580
650
  "title": "List available datacenters",
@@ -479,6 +479,76 @@ export default [
479
479
  "group": "hosting",
480
480
  "hidden": true
481
481
  },
482
+ {
483
+ "name": "hosting_websites_trigger-event",
484
+ "title": "Trigger website event",
485
+ "annotations": {
486
+ "title": "Trigger website event",
487
+ "readOnlyHint": false,
488
+ "destructiveHint": false
489
+ },
490
+ "topic": "hosting",
491
+ "description": "Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.",
492
+ "method": "",
493
+ "path": "",
494
+ "inputSchema": {
495
+ "type": "object",
496
+ "properties": {
497
+ "username": {
498
+ "type": "string",
499
+ "description": "Username of the hosting account that owns the website, as hosting_websites_list returns it"
500
+ },
501
+ "domain": {
502
+ "type": "string",
503
+ "description": "Domain name of the website (e.g., example.com)"
504
+ },
505
+ "event": {
506
+ "type": "object",
507
+ "description": "Event to trigger; `name` decides which `params` it takes",
508
+ "properties": {
509
+ "name": {
510
+ "type": "string",
511
+ "description": "Name of the event. `headless_onboarding.tool.connected`: an AI coding tool started building this website with the Hostinger headless skill, so hPanel shows the tool as connected; send it once per skill run; takes `params.tool`.",
512
+ "enum": [
513
+ "headless_onboarding.tool.connected"
514
+ ]
515
+ },
516
+ "params": {
517
+ "type": "object",
518
+ "description": "Fields of the event",
519
+ "properties": {
520
+ "tool": {
521
+ "type": "string",
522
+ "description": "Lowercase slug of the AI coding tool. Known slugs: `claude` (Claude Code), `cursor` (Cursor), `codex` (Codex), `vscode` (VS Code), `antigravity` (Antigravity) and `devin` (Devin). For any other tool, send its own slug.",
523
+ "pattern": "^[a-z0-9-]{1,32}$"
524
+ }
525
+ },
526
+ "required": [
527
+ "tool"
528
+ ],
529
+ "additionalProperties": false
530
+ }
531
+ },
532
+ "required": [
533
+ "name",
534
+ "params"
535
+ ],
536
+ "additionalProperties": false
537
+ }
538
+ },
539
+ "required": [
540
+ "username",
541
+ "domain",
542
+ "event"
543
+ ]
544
+ },
545
+ "security": [],
546
+ "custom": true,
547
+ "templateFile": "trigger-website-event.template.js",
548
+ "templateFileTS": "trigger-website-event.template.ts",
549
+ "handlerMethod": "handleHostingTriggerWebsiteEvent",
550
+ "group": "hosting"
551
+ },
482
552
  {
483
553
  "name": "hosting_cache_clear-website",
484
554
  "title": "Clear website cache",
@@ -495,6 +495,76 @@ const tools: OpenApiTool[] = [
495
495
  "group": "hosting",
496
496
  "hidden": true
497
497
  },
498
+ {
499
+ "name": "hosting_websites_trigger-event",
500
+ "title": "Trigger website event",
501
+ "annotations": {
502
+ "title": "Trigger website event",
503
+ "readOnlyHint": false,
504
+ "destructiveHint": false
505
+ },
506
+ "topic": "hosting",
507
+ "description": "Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.",
508
+ "method": "",
509
+ "path": "",
510
+ "inputSchema": {
511
+ "type": "object",
512
+ "properties": {
513
+ "username": {
514
+ "type": "string",
515
+ "description": "Username of the hosting account that owns the website, as hosting_websites_list returns it"
516
+ },
517
+ "domain": {
518
+ "type": "string",
519
+ "description": "Domain name of the website (e.g., example.com)"
520
+ },
521
+ "event": {
522
+ "type": "object",
523
+ "description": "Event to trigger; `name` decides which `params` it takes",
524
+ "properties": {
525
+ "name": {
526
+ "type": "string",
527
+ "description": "Name of the event. `headless_onboarding.tool.connected`: an AI coding tool started building this website with the Hostinger headless skill, so hPanel shows the tool as connected; send it once per skill run; takes `params.tool`.",
528
+ "enum": [
529
+ "headless_onboarding.tool.connected"
530
+ ]
531
+ },
532
+ "params": {
533
+ "type": "object",
534
+ "description": "Fields of the event",
535
+ "properties": {
536
+ "tool": {
537
+ "type": "string",
538
+ "description": "Lowercase slug of the AI coding tool. Known slugs: `claude` (Claude Code), `cursor` (Cursor), `codex` (Codex), `vscode` (VS Code), `antigravity` (Antigravity) and `devin` (Devin). For any other tool, send its own slug.",
539
+ "pattern": "^[a-z0-9-]{1,32}$"
540
+ }
541
+ },
542
+ "required": [
543
+ "tool"
544
+ ],
545
+ "additionalProperties": false
546
+ }
547
+ },
548
+ "required": [
549
+ "name",
550
+ "params"
551
+ ],
552
+ "additionalProperties": false
553
+ }
554
+ },
555
+ "required": [
556
+ "username",
557
+ "domain",
558
+ "event"
559
+ ]
560
+ },
561
+ "security": [],
562
+ "custom": true,
563
+ "templateFile": "trigger-website-event.template.js",
564
+ "templateFileTS": "trigger-website-event.template.ts",
565
+ "handlerMethod": "handleHostingTriggerWebsiteEvent",
566
+ "group": "hosting"
567
+ },
498
568
  {
499
569
  "name": "hosting_cache_clear-website",
500
570
  "title": "Clear website cache",
@@ -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.14.1', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.1', 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.14.1', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.15.1', 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.14.1', tools });
6
+ startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.1', 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.14.1', tools });
6
+ startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.15.1', tools });
@@ -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.14.1', tools, instructions, skills });
8
+ startServer({ name: 'hostinger-api-mcp', version: '3.15.1', tools, instructions, skills });
@@ -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.14.1', tools, instructions, skills });
8
+ startServer({ name: 'hostinger-api-mcp', version: '3.15.1', tools, instructions, skills });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/billing.js';
5
5
 
6
- startServer({ name: 'hostinger-billing-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/billing.js';
5
5
 
6
- startServer({ name: 'hostinger-billing-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/dns.js';
5
5
 
6
- startServer({ name: 'hostinger-dns-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/dns.js';
5
5
 
6
- startServer({ name: 'hostinger-dns-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/domains.js';
5
5
 
6
- startServer({ name: 'hostinger-domains-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/domains.js';
5
5
 
6
- startServer({ name: 'hostinger-domains-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/ecommerce.js';
5
5
 
6
- startServer({ name: 'hostinger-ecommerce-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/ecommerce.js';
5
5
 
6
- startServer({ name: 'hostinger-ecommerce-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/horizons.js';
5
5
 
6
- startServer({ name: 'hostinger-horizons-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/horizons.js';
5
5
 
6
- startServer({ name: 'hostinger-horizons-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '3.15.1', tools });
@@ -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.14.1', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '3.15.1', tools, instructions });
@@ -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.14.1', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '3.15.1', tools, instructions });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/mail.js';
5
5
 
6
- startServer({ name: 'hostinger-mail-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/mail.js';
5
5
 
6
- startServer({ name: 'hostinger-mail-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/reach.js';
5
5
 
6
- startServer({ name: 'hostinger-reach-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/reach.js';
5
5
 
6
- startServer({ name: 'hostinger-reach-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/vps.js';
5
5
 
6
- startServer({ name: 'hostinger-vps-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/vps.js';
5
5
 
6
- startServer({ name: 'hostinger-vps-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/wordpress.js';
5
5
 
6
- startServer({ name: 'hostinger-wordpress-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '3.15.1', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/wordpress.js';
5
5
 
6
- startServer({ name: 'hostinger-wordpress-mcp', version: '3.14.1', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '3.15.1', tools });
package/types.d.ts CHANGED
@@ -287,6 +287,27 @@ export interface APITools {
287
287
  response: any; // Response structure will depend on the API
288
288
  };
289
289
 
290
+ /**
291
+ * Tell Hostinger that something happened on this website, so Hostinger services such as hPanel can react to it. `event.name` picks the event and decides which `event.params` it takes. The call changes nothing on the website.
292
+ */
293
+ "hosting_websites_trigger-event": {
294
+ params: {
295
+ /**
296
+ * Username of the hosting account that owns the website, as hosting_websites_list returns it
297
+ */
298
+ username: string;
299
+ /**
300
+ * Domain name of the website (e.g., example.com)
301
+ */
302
+ domain: string;
303
+ /**
304
+ * Event to trigger; `name` decides which `params` it takes
305
+ */
306
+ event: object;
307
+ };
308
+ response: any; // Response structure will depend on the API
309
+ };
310
+
290
311
  /**
291
312
  * Lists the datacenters available for provisioning a new website on the given Agency Plan
292
313
  hosting order.