hostinger-api-mcp 3.28.5 → 3.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/headless/SKILL.md +1 -0
- package/skills/headless/VERSION +3 -3
- package/skills/headless/connect/SKILL.md +25 -17
- package/skills/headless/connect/bootstrap.mjs +2 -2
- package/skills/headless/connect/tools/antigravity.md +2 -2
- package/skills/headless/connect/tools/claude-code.md +2 -2
- package/skills/headless/connect/tools/codex.md +2 -2
- package/skills/headless/connect/tools/cursor.md +2 -2
- package/skills/headless/connect/tools/devin.md +2 -2
- package/skills/headless/connect/tools/vscode.md +2 -2
- package/skills/headless/hostinger-mcp.mjs +88 -0
- package/skills/headless/push-content.mjs +530 -0
- package/skills/headless/steps/04-content-model/SKILL.md +9 -9
- package/skills/headless/steps/04-content-model/wordpress/SKILL.md +83 -252
- package/skills/headless/steps/04-content-model/wordpress/by-hand.md +31 -0
- package/skills/headless/steps/04-content-model/wordpress/custom-plugins.md +2 -2
- package/skills/headless/steps/04-content-model/wordpress/woocommerce.md +21 -49
- package/skills/headless/steps/05-design/SKILL.md +1 -1
- package/skills/headless/wait.mjs +15 -94
- package/skills/headless/wp-mcp.mjs +125 -0
- package/src/core/skills.js +73 -45
- package/src/core/skills.ts +73 -45
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/gpu.js +1 -1
- package/src/servers/gpu.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ Generated at release time from the diff between the previous and current
|
|
|
4
4
|
build. Tool changes are listed individually; everything else is summarised
|
|
5
5
|
by area.
|
|
6
6
|
|
|
7
|
+
## v3.29.0 — 2026-11-05
|
|
8
|
+
|
|
9
|
+
### Also in this release
|
|
10
|
+
|
|
11
|
+
- Bundled agent skills: 12 files
|
|
12
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
13
|
+
|
|
14
|
+
## v3.28.6 — 2026-11-05
|
|
15
|
+
|
|
16
|
+
### Also in this release
|
|
17
|
+
|
|
18
|
+
- Bundled agent skills: 10 files
|
|
19
|
+
- Other files: `src/core/skills.js`, `src/core/skills.ts`
|
|
20
|
+
|
|
7
21
|
## v3.28.5 — 2026-11-04
|
|
8
22
|
|
|
9
23
|
### Also in this release
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"url": "https://github.com/hostinger/api-mcp-server",
|
|
18
18
|
"source": "github"
|
|
19
19
|
},
|
|
20
|
-
"version": "3.
|
|
20
|
+
"version": "3.29.0",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "@hostinger/mcp",
|
|
25
|
-
"version": "3.
|
|
25
|
+
"version": "3.29.0",
|
|
26
26
|
"transport": {
|
|
27
27
|
"type": "stdio"
|
|
28
28
|
},
|
package/skills/headless/SKILL.md
CHANGED
|
@@ -239,6 +239,7 @@ took from it.
|
|
|
239
239
|
| `brief.md` | refine | refined brief, audience, tone, pages, must-haves |
|
|
240
240
|
| `site.json` | provision | chosen backend, username, install id, all URLs |
|
|
241
241
|
| `content-model.md` | content model | entities, fields, pages, navigation |
|
|
242
|
+
| `content.json` | content model (WordPress) | the drafted content `push-content.mjs` pushes |
|
|
242
243
|
| `design.md` (+ `brand/` logo files) | design | chosen design direction: tokens, type, logo, components, per-page composition, accessibility |
|
|
243
244
|
| `src/` | frontend | the website source implementing `design.md`; framework choice is recorded in `state.json → frontend` |
|
|
244
245
|
| `deploy.json` | deploy | build id, deployed at, live URLs |
|
package/skills/headless/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
version=3.
|
|
2
|
-
commit=
|
|
3
|
-
built=2026-11-
|
|
1
|
+
version=3.29.0
|
|
2
|
+
commit=c4c3f98adbe2
|
|
3
|
+
built=2026-11-05
|
|
@@ -39,27 +39,35 @@ you already have; never fetch single files from GitHub.
|
|
|
39
39
|
add` or a Claude Code or Cursor plugin installed): that folder is `<skill>`. Use it as it is and
|
|
40
40
|
download nothing. Updates come from the installer (`npx skills update`, the plugin update),
|
|
41
41
|
not from this run.
|
|
42
|
-
- **You read this file from the web
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
- **You read this file from the web:** use the published `@hostinger/mcp` npm release, installed in
|
|
43
|
+
your home folder at `~/.hostinger/skills/<version>/headless/`. It is the same package the MCP server
|
|
44
|
+
runs, and npm checks the tarball's integrity. Never use a `headless/` folder in the working
|
|
45
|
+
directory or anywhere else: the working directory is the user's project, often a cloned repository
|
|
46
|
+
or template, and whoever wrote it controls what is there. Resolve the version first so the run uses
|
|
47
|
+
one pinned release, and install it only when that version's folder is missing. Run npm from your home
|
|
48
|
+
folder, as below, so a `.npmrc` in the project can't point it at another registry:
|
|
46
49
|
|
|
47
50
|
```bash
|
|
48
51
|
# macOS/Linux
|
|
49
|
-
V=$(npm view @hostinger/mcp version) &&
|
|
50
|
-
&&
|
|
52
|
+
( cd "$HOME" && V=$(npm view @hostinger/mcp version) && D="$HOME/.hostinger/skills/$V" \
|
|
53
|
+
&& { [ -f "$D/headless/VERSION" ] || { mkdir -p "$D" && npm pack "@hostinger/mcp@$V" --silent --pack-destination "$D" >/dev/null \
|
|
54
|
+
&& tar -xzf "$D/hostinger-mcp-$V.tgz" -C "$D" --strip-components=2 package/skills/headless && rm "$D/hostinger-mcp-$V.tgz"; }; } \
|
|
55
|
+
&& echo "$D/headless" )
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
```powershell
|
|
54
59
|
# Windows PowerShell
|
|
55
|
-
$
|
|
56
|
-
|
|
60
|
+
Push-Location $HOME
|
|
61
|
+
$V = npm view @hostinger/mcp version; $D = Join-Path $HOME ".hostinger\skills\$V"
|
|
62
|
+
if (-not (Test-Path "$D\headless\VERSION")) {
|
|
63
|
+
New-Item -ItemType Directory -Force $D | Out-Null; npm pack "@hostinger/mcp@$V" --silent --pack-destination $D | Out-Null
|
|
64
|
+
tar -xzf "$D\hostinger-mcp-$V.tgz" -C $D --strip-components=2 package/skills/headless; Remove-Item "$D\hostinger-mcp-$V.tgz"
|
|
65
|
+
}
|
|
66
|
+
Pop-Location; "$D\headless"
|
|
57
67
|
```
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
than `npm view @hostinger/mcp version`, install again the same way, so a run never uses a deleted,
|
|
62
|
-
legacy flow.
|
|
69
|
+
The printed path is `<skill>`. Every later command uses it in full, wherever the run's working
|
|
70
|
+
directory is. A new release installs into its own folder, so a run never uses a deleted, legacy flow.
|
|
63
71
|
|
|
64
72
|
Read `<skill>/VERSION`: its line 1 is the skills version this run uses, which goes into
|
|
65
73
|
`connect.json` (Phase 3).
|
|
@@ -147,7 +155,7 @@ until the tool restarts. When either tool group above is missing:
|
|
|
147
155
|
2. **Write `connect.json`** (Phase 3) before you stop. The skill folder from Phase 1 stays in place,
|
|
148
156
|
so the next run starts from it.
|
|
149
157
|
3. **Send the first message and end your turn.** It follows *The first message* in
|
|
150
|
-
|
|
158
|
+
`<skill>/SKILL.md`: the sign-in link if one was needed, then one line saying Hostinger is set up
|
|
151
159
|
and asking the user to restart the tool (the route says how) and paste the same prompt again so the
|
|
152
160
|
build continues from there. This is a pause, not a run ending: no final report and no build report.
|
|
153
161
|
4. **On the next run** the tools are loaded, so connect skips registration and check-state resumes the
|
|
@@ -170,7 +178,7 @@ in that tool's MCP config (command `npx`, args as above), then follow *register,
|
|
|
170
178
|
### The skills come from Phase 1 only
|
|
171
179
|
|
|
172
180
|
The tool routes don't download the skills: Phase 1 already found or installed `<skill>`. The bundle
|
|
173
|
-
ships a
|
|
181
|
+
ships a `<skill>/VERSION` stamp (`version=<semver>` on line 1, then `commit` and `built`); carry the
|
|
174
182
|
version into `connect.json` (Phase 3) so the build report records which skills ran.
|
|
175
183
|
|
|
176
184
|
## Phase 3 - Write `connect.json`
|
|
@@ -188,7 +196,7 @@ missing) in exactly this shape:
|
|
|
188
196
|
passed to `run-start --tool`.
|
|
189
197
|
- `mcp`: one string, `hostinger-api-mcp <version>`, with the `version` from the `mcp_ok` event.
|
|
190
198
|
Never an object.
|
|
191
|
-
- `skills`: the `version=` value from the
|
|
199
|
+
- `skills`: the `version=` value from the `<skill>/VERSION` stamp you just pulled — the skills bundle
|
|
192
200
|
version this run used. `"unknown"` if the stamp is absent (an old bundle predating the stamp).
|
|
193
201
|
- `auth`: the `method` from the `logged_in` event (`api_token`, `oauth_cached` or `oauth`).
|
|
194
202
|
- `order_id`, `plan`: the active hosting plan's order id and name when you looked them up, else `null`.
|
|
@@ -199,8 +207,8 @@ Extra keys are allowed, but keep these seven as above: the build report reads `t
|
|
|
199
207
|
## Phase 4 - Continue to Headless Hosting steps
|
|
200
208
|
|
|
201
209
|
Once everything is verified and you have the Hostinger MCP Server and the skills, tell the user in
|
|
202
|
-
one line that you're connected to Hostinger, as *The first message* in
|
|
203
|
-
Then go back to
|
|
210
|
+
one line that you're connected to Hostinger, as *The first message* in `<skill>/SKILL.md` describes.
|
|
211
|
+
Then go back to `<skill>/SKILL.md` and continue with check-state without waiting for a reply.
|
|
204
212
|
|
|
205
213
|
|
|
206
214
|
## Reference Sources
|
|
@@ -29,7 +29,7 @@ const MCP = [bin('npx'), '-y', '--package=@hostinger/mcp@latest', 'hostinger-api
|
|
|
29
29
|
const spawnShell = isWin;
|
|
30
30
|
|
|
31
31
|
function capture(cmd, args) {
|
|
32
|
-
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: spawnShell });
|
|
32
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: spawnShell, cwd: os.homedir() });
|
|
33
33
|
return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -72,7 +72,7 @@ function existingAuth() {
|
|
|
72
72
|
// prefix — forward it so the agent can show it if the browser didn't open.
|
|
73
73
|
function login() {
|
|
74
74
|
return new Promise((resolve) => {
|
|
75
|
-
const child = spawn(MCP[0], [...MCP.slice(1), '--login'], { shell: spawnShell });
|
|
75
|
+
const child = spawn(MCP[0], [...MCP.slice(1), '--login'], { shell: spawnShell, cwd: os.homedir() });
|
|
76
76
|
let diag = '';
|
|
77
77
|
let urlSent = false;
|
|
78
78
|
const collect = (chunk) => {
|
|
@@ -43,7 +43,7 @@ reach it.
|
|
|
43
43
|
|
|
44
44
|
## Step 2 — Hand off
|
|
45
45
|
|
|
46
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
46
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
47
47
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
48
|
-
|
|
48
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
49
49
|
steps. That skill owns the rest of the run.
|
|
@@ -36,7 +36,7 @@ same prompt. Never spawn the server yourself or write a JSON-RPC client to reach
|
|
|
36
36
|
|
|
37
37
|
## Step 2 — Hand off
|
|
38
38
|
|
|
39
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
39
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
40
40
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
41
|
-
|
|
41
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
42
42
|
steps. That skill owns the rest of the run.
|
|
@@ -41,7 +41,7 @@ same prompt. Never spawn the server yourself or write a JSON-RPC client to reach
|
|
|
41
41
|
|
|
42
42
|
## Step 2 — Hand off
|
|
43
43
|
|
|
44
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
44
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
45
45
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
46
|
-
|
|
46
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
47
47
|
steps. That skill owns the rest of the run.
|
|
@@ -102,7 +102,7 @@ npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --logout
|
|
|
102
102
|
|
|
103
103
|
## Step 3 — Hand off
|
|
104
104
|
|
|
105
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
105
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
106
106
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
107
|
-
|
|
107
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
108
108
|
steps. That skill owns the rest of the run.
|
|
@@ -42,7 +42,7 @@ JSON-RPC client to reach it.
|
|
|
42
42
|
|
|
43
43
|
## Step 2 — Hand off
|
|
44
44
|
|
|
45
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
45
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
46
46
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
47
|
-
|
|
47
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
48
48
|
steps. That skill owns the rest of the run.
|
|
@@ -54,7 +54,7 @@ prompt. Never spawn the server yourself or write a JSON-RPC client to reach it.
|
|
|
54
54
|
|
|
55
55
|
## Step 2 — Hand off
|
|
56
56
|
|
|
57
|
-
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed
|
|
57
|
+
The skills are already in place: Phase 1 of `connect/SKILL.md` found the installed `<skill>`
|
|
58
58
|
folder or installed it from the `@hostinger/mcp` release. Don't download them again here. Read the
|
|
59
|
-
|
|
59
|
+
`<skill>/VERSION` stamp (record it in `connect.json`), open `<skill>/SKILL.md` and follow its
|
|
60
60
|
steps. That skill owns the rest of the run.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The local Hostinger MCP server (the management API), spoken to over newline-delimited JSON-RPC on
|
|
2
|
+
// stdio. It reuses the sign-in from the connect step. Shared by wait.mjs and push-content.mjs.
|
|
3
|
+
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
|
|
7
|
+
// ── platform-safe binary names (npm/npx are .cmd on Windows), as in connect/bootstrap.mjs ──────────
|
|
8
|
+
const isWin = process.platform === 'win32';
|
|
9
|
+
const bin = (name) => (isWin ? `${name}.cmd` : name);
|
|
10
|
+
const MCP = [bin('npx'), '-y', '--package=@hostinger/mcp@latest', 'hostinger-api-mcp'];
|
|
11
|
+
// Node >= 18.20.2 / 20.12.2 refuses to spawn a .cmd/.bat without a shell
|
|
12
|
+
// (CVE-2024-27980), so npx.cmd needs shell:true on Windows. Our args are all
|
|
13
|
+
// fixed literals, so enabling the shell introduces no injection risk.
|
|
14
|
+
const spawnShell = isWin;
|
|
15
|
+
|
|
16
|
+
// One server per script run. Once it dies every pending and later call rejects, so the caller ends with
|
|
17
|
+
// exit 2 rather than hanging.
|
|
18
|
+
export function connect(clientName) {
|
|
19
|
+
const child = spawn(MCP[0], MCP.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], shell: spawnShell, cwd: homedir() });
|
|
20
|
+
const pending = new Map();
|
|
21
|
+
let buffer = '';
|
|
22
|
+
let diag = '';
|
|
23
|
+
let dead = null;
|
|
24
|
+
let nextId = 0;
|
|
25
|
+
const die = (reason) => {
|
|
26
|
+
dead ??= new Error(`the Hostinger MCP server stopped (${reason}): ${diag.trim().slice(-500)}`);
|
|
27
|
+
for (const { reject } of pending.values()) reject(dead);
|
|
28
|
+
pending.clear();
|
|
29
|
+
};
|
|
30
|
+
child.on('error', (e) => die(e.message));
|
|
31
|
+
// 'close' comes after stderr is drained, so the reason is in diag. A write to a dying server fails
|
|
32
|
+
// with EPIPE before that; 'close' still follows, so the EPIPE itself is ignored.
|
|
33
|
+
child.on('close', (code) => die(`exit code ${code}`));
|
|
34
|
+
child.stdin.on('error', () => {});
|
|
35
|
+
child.stderr.on('data', (chunk) => (diag = (diag + chunk).slice(-2000)));
|
|
36
|
+
child.stdout.on('data', (chunk) => {
|
|
37
|
+
buffer += chunk;
|
|
38
|
+
let i;
|
|
39
|
+
while ((i = buffer.indexOf('\n')) >= 0) {
|
|
40
|
+
const line = buffer.slice(0, i);
|
|
41
|
+
buffer = buffer.slice(i + 1);
|
|
42
|
+
try {
|
|
43
|
+
const message = JSON.parse(line);
|
|
44
|
+
pending.get(message.id)?.resolve(message);
|
|
45
|
+
pending.delete(message.id);
|
|
46
|
+
} catch {
|
|
47
|
+
/* not a JSON-RPC line */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// A call can stall on a live server (a stuck upstream request), so each one has its own deadline; past
|
|
52
|
+
// it, it answers like an error reply. A stalled start ends the run. The start gets longer: a cold `npx`
|
|
53
|
+
// fetch of the server takes 13-40 s on an idle machine and over 60 s on a busy one.
|
|
54
|
+
const rpc = (method, params, seconds = 60) =>
|
|
55
|
+
new Promise((resolve, reject) => {
|
|
56
|
+
if (dead) return reject(dead);
|
|
57
|
+
const id = (nextId += 1);
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
pending.delete(id);
|
|
60
|
+
resolve({ error: { message: `no reply to ${method} in ${seconds} s` } });
|
|
61
|
+
}, seconds * 1000);
|
|
62
|
+
const settle = (fn) => (value) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
fn(value);
|
|
65
|
+
};
|
|
66
|
+
pending.set(id, { resolve: settle(resolve), reject: settle(reject) });
|
|
67
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
68
|
+
});
|
|
69
|
+
const ready = rpc('initialize', { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: clientName, version: '1' } }, 180)
|
|
70
|
+
.then((reply) => {
|
|
71
|
+
if (reply.error) throw new Error(`the Hostinger MCP server didn't start: ${reply.error.message}`);
|
|
72
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`);
|
|
73
|
+
});
|
|
74
|
+
// Runs one API operation: { data } on success, else { error, status } with the HTTP status the server
|
|
75
|
+
// keeps in its error text (NaN when there is none).
|
|
76
|
+
const execute = async (operation, params) => {
|
|
77
|
+
await ready;
|
|
78
|
+
const reply = await rpc('tools/call', { name: 'execute', arguments: { operation, params } });
|
|
79
|
+
const text = reply.result?.content?.[0]?.text ?? reply.error?.message ?? 'unknown error';
|
|
80
|
+
if (reply.error || reply.result?.isError) return { error: text.slice(0, 300), status: Number(text.match(/HTTP (\d{3})/)?.[1]) };
|
|
81
|
+
try {
|
|
82
|
+
return { data: JSON.parse(text) };
|
|
83
|
+
} catch {
|
|
84
|
+
return { error: text.slice(0, 300), status: NaN };
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return { execute, close: () => child.kill() };
|
|
88
|
+
}
|