htmlhost-mcp 1.1.0 → 1.2.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/README.md +5 -0
- package/package.json +1 -1
- package/src/api.mjs +10 -3
- package/src/server.mjs +10 -2
package/README.md
CHANGED
|
@@ -97,6 +97,11 @@ title (optional) Title for the site
|
|
|
97
97
|
ttl (optional) Time-to-live: 1d, 7d, 30d, or never
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
A `_redirects` file at the root of a deployed directory sets the site's redirects,
|
|
101
|
+
one per line: `/old-page /new-page`, optionally followed by `301` (default) or `302`.
|
|
102
|
+
It isn't published. Each deploy with the file replaces the redirects it set before;
|
|
103
|
+
redirects added in the dashboard stay. `pull_site` writes the file back.
|
|
104
|
+
|
|
100
105
|
### `deploy_html`
|
|
101
106
|
|
|
102
107
|
Deploy generated HTML directly — no files on disk needed. Perfect for when an AI assistant generates a webpage and wants to make it live immediately.
|
package/package.json
CHANGED
package/src/api.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Reuses the same ~/.htmlhostrc token and API base as the CLI,
|
|
5
5
|
* so `htmlhost login` is the only setup step needed.
|
|
6
6
|
*/
|
|
7
|
-
import { readFileSync, writeFileSync, statSync, readdirSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { readFileSync, writeFileSync, statSync, readdirSync, mkdirSync, existsSync } from "node:fs";
|
|
8
8
|
import { join, basename, dirname, resolve } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
@@ -132,6 +132,8 @@ const DEFAULT_IGNORE = [
|
|
|
132
132
|
".gitignore", ".npmrc", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
133
133
|
"AGENTS.md", "CLAUDE.md", "GEMINI.md", ".claude", ".cursor", ".cursorrules",
|
|
134
134
|
".windsurfrules", ".github",
|
|
135
|
+
// Redirect rules: sent with the deploy and applied by htmlhost, never published as a file.
|
|
136
|
+
"_redirects",
|
|
135
137
|
];
|
|
136
138
|
|
|
137
139
|
function shouldIgnore(name) {
|
|
@@ -184,6 +186,8 @@ export async function deployDirectory(dirPath, { slug = null, title = null, ttl
|
|
|
184
186
|
|
|
185
187
|
const htmlFiles = allFiles.filter((f) => f.relativePath.endsWith(".html"));
|
|
186
188
|
const assetFiles = allFiles.filter((f) => !f.relativePath.endsWith(".html"));
|
|
189
|
+
// Decided before upload-url may hand back a generated slug for the new site.
|
|
190
|
+
const isNewSite = !slug;
|
|
187
191
|
|
|
188
192
|
// Build page payloads
|
|
189
193
|
const pagePayloads = [];
|
|
@@ -232,12 +236,15 @@ export async function deployDirectory(dirPath, { slug = null, title = null, ttl
|
|
|
232
236
|
}
|
|
233
237
|
}
|
|
234
238
|
|
|
235
|
-
// Final deploy
|
|
239
|
+
// Final deploy. A root `_redirects` file (Netlify style) replaces the site's
|
|
240
|
+
// redirects from this file; rules added in the dashboard stay. No file, no change.
|
|
236
241
|
const body = { pages: pagePayloads, assets: assetPayloads };
|
|
242
|
+
const redirectsPath = join(resolvedDir, "_redirects");
|
|
243
|
+
if (existsSync(redirectsPath)) body.redirects = readFileSync(redirectsPath, "utf8");
|
|
237
244
|
if (ttl) body.ttl = ttl;
|
|
238
245
|
if (slug) body.slug = slug;
|
|
239
246
|
if (title) body.title = title;
|
|
240
|
-
if (
|
|
247
|
+
if (isNewSite) body.isNewSite = true;
|
|
241
248
|
|
|
242
249
|
return await apiPost("/api/sites", body);
|
|
243
250
|
}
|
package/src/server.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
mimeFromExt,
|
|
27
27
|
} from "./api.mjs";
|
|
28
28
|
|
|
29
|
-
const VERSION = "1.
|
|
29
|
+
const VERSION = "1.2.0";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Helper: wraps a handler to return proper MCP content and catch errors.
|
|
@@ -59,7 +59,8 @@ export function createServer() {
|
|
|
59
59
|
description:
|
|
60
60
|
"Deploy a file or directory to htmlhost.co. " +
|
|
61
61
|
"Pass an absolute path to a directory (deploys all files) or a single HTML file. " +
|
|
62
|
-
"Optionally set slug (to update an existing site), title, and ttl."
|
|
62
|
+
"Optionally set slug (to update an existing site), title, and ttl. " +
|
|
63
|
+
"A `_redirects` file at the directory's root (one `/old /new [301|302]` per line) sets the site's redirects and isn't published.",
|
|
63
64
|
inputSchema: z.object({
|
|
64
65
|
path: z.string().describe("Absolute path to the file or directory to deploy"),
|
|
65
66
|
slug: z.string().optional().describe("Existing site slug to update (omit to create a new site)"),
|
|
@@ -92,6 +93,7 @@ export function createServer() {
|
|
|
92
93
|
pages: pc,
|
|
93
94
|
ttl: data.ttl,
|
|
94
95
|
expiresAt: data.expiresAt || null,
|
|
96
|
+
...(data.redirects ? { redirects: data.redirects } : {}),
|
|
95
97
|
};
|
|
96
98
|
})
|
|
97
99
|
);
|
|
@@ -219,6 +221,12 @@ export function createServer() {
|
|
|
219
221
|
written.push(asset.path);
|
|
220
222
|
}
|
|
221
223
|
|
|
224
|
+
// Redirects that came from a deployed _redirects file
|
|
225
|
+
if (data.redirectsFile) {
|
|
226
|
+
writeFileSync(join(destDir, "_redirects"), data.redirectsFile, "utf8");
|
|
227
|
+
written.push("_redirects");
|
|
228
|
+
}
|
|
229
|
+
|
|
222
230
|
return {
|
|
223
231
|
message: `Pulled ${written.length} files from ${cleanSlug}.htmlhost.co`,
|
|
224
232
|
directory: destDir,
|