htmlship 0.1.2 → 0.1.4
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 +3 -3
- package/dist/cli.js +53 -51
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ CLI + MCP server for [HTMLShip](https://htmlship.com) — host and share HTML pa
|
|
|
6
6
|
# Publish an HTML file
|
|
7
7
|
npx htmlship publish report.html
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
9
|
+
# Password-protect a page
|
|
10
|
+
npx htmlship publish report.html --password "demo-pass"
|
|
11
11
|
|
|
12
12
|
# Inspect / update / delete using the saved owner key
|
|
13
13
|
npx htmlship get <slug>
|
|
@@ -18,7 +18,7 @@ npx htmlship list-mine
|
|
|
18
18
|
|
|
19
19
|
## MCP server
|
|
20
20
|
|
|
21
|
-
`htmlship mcp` starts a stdio MCP server with three tools: `publish_html
|
|
21
|
+
`htmlship mcp` starts a stdio MCP server with three tools: `publish_html` (with optional `password`), `fetch_html`, `update_html`.
|
|
22
22
|
|
|
23
23
|
### Claude Desktop / Claude Code / Cursor
|
|
24
24
|
|
package/dist/cli.js
CHANGED
|
@@ -56,7 +56,7 @@ var VERSION;
|
|
|
56
56
|
var init_version = __esm({
|
|
57
57
|
"src/version.ts"() {
|
|
58
58
|
"use strict";
|
|
59
|
-
VERSION = "0.1.
|
|
59
|
+
VERSION = "0.1.4";
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -90,24 +90,24 @@ var init_client = __esm({
|
|
|
90
90
|
if (options.password != null) body["password"] = options.password;
|
|
91
91
|
if (options.expiresIn != null) body["expires_in"] = options.expiresIn;
|
|
92
92
|
if (options.parentSlug != null) body["parent_slug"] = options.parentSlug;
|
|
93
|
-
return await this.request("POST", "/api/v1/
|
|
93
|
+
return await this.request("POST", "/api/v1/pages", { body });
|
|
94
94
|
}
|
|
95
95
|
async get(slug) {
|
|
96
|
-
return await this.request("GET", `/api/v1/
|
|
96
|
+
return await this.request("GET", `/api/v1/pages/${encodeURIComponent(slug)}`);
|
|
97
97
|
}
|
|
98
98
|
async update(slug, html, ownerKey, options = {}) {
|
|
99
99
|
const body = { html };
|
|
100
100
|
if (options.title != null) body["title"] = options.title;
|
|
101
101
|
return await this.request(
|
|
102
102
|
"PATCH",
|
|
103
|
-
`/api/v1/
|
|
103
|
+
`/api/v1/pages/${encodeURIComponent(slug)}`,
|
|
104
104
|
{ body, headers: { "X-Owner-Key": ownerKey } }
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
async delete(slug, ownerKey) {
|
|
108
108
|
await this.request(
|
|
109
109
|
"DELETE",
|
|
110
|
-
`/api/v1/
|
|
110
|
+
`/api/v1/pages/${encodeURIComponent(slug)}`,
|
|
111
111
|
{ headers: { "X-Owner-Key": ownerKey }, expectNoBody: true }
|
|
112
112
|
);
|
|
113
113
|
}
|
|
@@ -121,7 +121,7 @@ var init_client = __esm({
|
|
|
121
121
|
if (options.expiresIn != null) body["expires_in"] = options.expiresIn;
|
|
122
122
|
return await this.request(
|
|
123
123
|
"POST",
|
|
124
|
-
`/api/v1/
|
|
124
|
+
`/api/v1/pages/${encodeURIComponent(parentSlug)}/version`,
|
|
125
125
|
{ body }
|
|
126
126
|
);
|
|
127
127
|
}
|
|
@@ -178,7 +178,7 @@ var init_client = __esm({
|
|
|
178
178
|
}
|
|
179
179
|
return text;
|
|
180
180
|
} catch {
|
|
181
|
-
return
|
|
181
|
+
return `HTTP ${response.status}`;
|
|
182
182
|
}
|
|
183
183
|
} catch {
|
|
184
184
|
return `HTTP ${response.status}`;
|
|
@@ -203,17 +203,19 @@ function buildMcpServer(client) {
|
|
|
203
203
|
server.registerTool(
|
|
204
204
|
"publish_html",
|
|
205
205
|
{
|
|
206
|
-
description: "Publish an HTML document and get a public URL. The owner_key returned is the only credential to update or delete this
|
|
206
|
+
description: "Publish an HTML document and get a public URL. The owner_key returned is the only credential to update or delete this page later \u2014 save it.",
|
|
207
207
|
inputSchema: {
|
|
208
208
|
html: z.string().describe("The HTML body to publish."),
|
|
209
209
|
title: z.string().optional().describe("Optional human-readable title."),
|
|
210
|
+
password: z.string().optional().describe("Optional password required before viewing the page."),
|
|
210
211
|
expires_in: z.number().int().min(1).max(60 * 24 * 7).optional().describe("Optional TTL in minutes (1\u201310080, i.e. up to 7 days).")
|
|
211
212
|
}
|
|
212
213
|
},
|
|
213
|
-
async ({ html, title, expires_in }) => {
|
|
214
|
+
async ({ html, title, password, expires_in }) => {
|
|
214
215
|
try {
|
|
215
|
-
const
|
|
216
|
+
const page = await c.publish(html, {
|
|
216
217
|
title: title ?? null,
|
|
218
|
+
password: password ?? null,
|
|
217
219
|
expiresIn: expires_in ?? null
|
|
218
220
|
});
|
|
219
221
|
return {
|
|
@@ -222,10 +224,10 @@ function buildMcpServer(client) {
|
|
|
222
224
|
type: "text",
|
|
223
225
|
text: JSON.stringify(
|
|
224
226
|
{
|
|
225
|
-
url:
|
|
226
|
-
slug:
|
|
227
|
-
owner_key:
|
|
228
|
-
expires_at:
|
|
227
|
+
url: page.url,
|
|
228
|
+
slug: page.slug,
|
|
229
|
+
owner_key: page.owner_key,
|
|
230
|
+
expires_at: page.expires_at
|
|
229
231
|
},
|
|
230
232
|
null,
|
|
231
233
|
2
|
|
@@ -241,20 +243,20 @@ function buildMcpServer(client) {
|
|
|
241
243
|
server.registerTool(
|
|
242
244
|
"fetch_html",
|
|
243
245
|
{
|
|
244
|
-
description: "Fetch a
|
|
246
|
+
description: "Fetch a page's metadata. To view the rendered HTML, open the `url` in a browser \u2014 the content is served from a sandboxed subdomain.",
|
|
245
247
|
inputSchema: {
|
|
246
|
-
slug: z.string().describe("The
|
|
248
|
+
slug: z.string().describe("The page's short identifier.")
|
|
247
249
|
}
|
|
248
250
|
},
|
|
249
251
|
async ({ slug }) => {
|
|
250
252
|
try {
|
|
251
|
-
const
|
|
253
|
+
const page = await c.get(slug);
|
|
252
254
|
return {
|
|
253
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
255
|
+
content: [{ type: "text", text: JSON.stringify(page, null, 2) }]
|
|
254
256
|
};
|
|
255
257
|
} catch (err) {
|
|
256
258
|
if (err instanceof NotFoundError) {
|
|
257
|
-
return errorResult("fetch_html", new HTMLShipError(`
|
|
259
|
+
return errorResult("fetch_html", new HTMLShipError(`page '${slug}' not found`));
|
|
258
260
|
}
|
|
259
261
|
return errorResult("fetch_html", err);
|
|
260
262
|
}
|
|
@@ -263,9 +265,9 @@ function buildMcpServer(client) {
|
|
|
263
265
|
server.registerTool(
|
|
264
266
|
"update_html",
|
|
265
267
|
{
|
|
266
|
-
description: "Replace the HTML for an existing
|
|
268
|
+
description: "Replace the HTML for an existing page. Requires the original owner_key.",
|
|
267
269
|
inputSchema: {
|
|
268
|
-
slug: z.string().describe("The
|
|
270
|
+
slug: z.string().describe("The page's short identifier."),
|
|
269
271
|
html: z.string().describe("The new HTML body."),
|
|
270
272
|
owner_key: z.string().describe("The owner key returned at publish time."),
|
|
271
273
|
title: z.string().optional().describe("Optional new title.")
|
|
@@ -273,13 +275,13 @@ function buildMcpServer(client) {
|
|
|
273
275
|
},
|
|
274
276
|
async ({ slug, html, owner_key, title }) => {
|
|
275
277
|
try {
|
|
276
|
-
const
|
|
278
|
+
const page = await c.update(slug, html, owner_key, { title: title ?? null });
|
|
277
279
|
return {
|
|
278
280
|
content: [
|
|
279
281
|
{
|
|
280
282
|
type: "text",
|
|
281
283
|
text: JSON.stringify(
|
|
282
|
-
{ url:
|
|
284
|
+
{ url: page.url, slug: page.slug, updated_at: page.updated_at },
|
|
283
285
|
null,
|
|
284
286
|
2
|
|
285
287
|
)
|
|
@@ -386,7 +388,7 @@ function createKeyStore(overrideDir) {
|
|
|
386
388
|
|
|
387
389
|
// src/commands/delete.ts
|
|
388
390
|
function registerDelete(program) {
|
|
389
|
-
program.command("delete").description("Soft-delete a
|
|
391
|
+
program.command("delete").description("Soft-delete a page.").argument("<slug>", "Page slug").option("--owner-key <key>", "Owner key (defaults to local store)").option("-y, --yes", "Skip confirmation").action(async function(slug, opts) {
|
|
390
392
|
const keys = createKeyStore();
|
|
391
393
|
const ownerKey = opts.ownerKey ?? keys.lookupOwnerKey(slug);
|
|
392
394
|
if (!ownerKey) {
|
|
@@ -396,7 +398,7 @@ function registerDelete(program) {
|
|
|
396
398
|
}
|
|
397
399
|
if (!opts.yes) {
|
|
398
400
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
399
|
-
const answer = await rl.question(`Delete
|
|
401
|
+
const answer = await rl.question(`Delete page '${slug}'? [y/N] `);
|
|
400
402
|
rl.close();
|
|
401
403
|
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
402
404
|
process.stderr.write("Aborted.\n");
|
|
@@ -416,16 +418,16 @@ function registerDelete(program) {
|
|
|
416
418
|
init_client();
|
|
417
419
|
init_errors();
|
|
418
420
|
function registerGet(program) {
|
|
419
|
-
program.command("get").description("Show metadata for a
|
|
421
|
+
program.command("get").description("Show metadata for a page.").argument("<slug>", "Page slug").action(async function(slug) {
|
|
420
422
|
const apiUrl = this.parent?.opts()?.apiUrl;
|
|
421
423
|
const client = new HTMLShipClient({ baseUrl: apiUrl });
|
|
422
424
|
try {
|
|
423
|
-
const
|
|
424
|
-
process.stdout.write(`${JSON.stringify(
|
|
425
|
+
const page = await client.get(slug);
|
|
426
|
+
process.stdout.write(`${JSON.stringify(page, null, 2)}
|
|
425
427
|
`);
|
|
426
428
|
} catch (err) {
|
|
427
429
|
if (err instanceof NotFoundError) {
|
|
428
|
-
throw new HTMLShipError(`
|
|
430
|
+
throw new HTMLShipError(`page '${slug}' not found`);
|
|
429
431
|
}
|
|
430
432
|
throw err;
|
|
431
433
|
}
|
|
@@ -436,12 +438,12 @@ function registerGet(program) {
|
|
|
436
438
|
init_client();
|
|
437
439
|
init_errors();
|
|
438
440
|
function registerListMine(program) {
|
|
439
|
-
program.command("list-mine").description("List
|
|
441
|
+
program.command("list-mine").description("List pages whose owner keys are saved locally.").option("--limit <n>", "max entries to show", "20").action(async function(opts) {
|
|
440
442
|
const keys = createKeyStore();
|
|
441
443
|
const data = keys.load();
|
|
442
444
|
const entries = Object.entries(data);
|
|
443
445
|
if (entries.length === 0) {
|
|
444
|
-
process.stdout.write(`No saved
|
|
446
|
+
process.stdout.write(`No saved pages in ${keys.file}.
|
|
445
447
|
`);
|
|
446
448
|
return;
|
|
447
449
|
}
|
|
@@ -452,13 +454,13 @@ function registerListMine(program) {
|
|
|
452
454
|
const rows = await Promise.all(
|
|
453
455
|
sorted.map(async ([slug, info]) => {
|
|
454
456
|
try {
|
|
455
|
-
const
|
|
457
|
+
const page = await client.get(slug);
|
|
456
458
|
return {
|
|
457
459
|
slug,
|
|
458
460
|
url: info.url,
|
|
459
|
-
title:
|
|
460
|
-
size: `${
|
|
461
|
-
views: String(
|
|
461
|
+
title: page.title ?? info.title ?? "",
|
|
462
|
+
size: `${page.size_bytes}B`,
|
|
463
|
+
views: String(page.view_count),
|
|
462
464
|
status: "ok"
|
|
463
465
|
};
|
|
464
466
|
} catch (err) {
|
|
@@ -530,13 +532,13 @@ async function tryClipboardCopy(text) {
|
|
|
530
532
|
|
|
531
533
|
// src/commands/publish.ts
|
|
532
534
|
function registerPublish(program) {
|
|
533
|
-
program.command("publish").description("Publish an HTML document and get a URL.").argument("[source]", "file path, '-' for stdin (default: stdin if piped)").option("-f, --file <path>", "HTML file path").option("--title <title>", "Optional title").option("--password <password>", "Password-protect the
|
|
535
|
+
program.command("publish").description("Publish an HTML document and get a URL.").argument("[source]", "file path, '-' for stdin (default: stdin if piped)").option("-f, --file <path>", "HTML file path").option("--title <title>", "Optional title").option("--password <password>", "Password-protect the page").option("--expires-in <minutes>", "Minutes until expiry (1\u201310080, i.e. up to 7 days)").option("--no-clipboard", "Don't copy URL to clipboard").option("-q, --quiet", "Print only the URL").action(async function(source, opts) {
|
|
534
536
|
const html = readHtmlFromSource(source, opts.file);
|
|
535
537
|
const apiUrl = this.parent?.opts()?.apiUrl;
|
|
536
538
|
const client = new HTMLShipClient({ baseUrl: apiUrl });
|
|
537
|
-
let
|
|
539
|
+
let page;
|
|
538
540
|
try {
|
|
539
|
-
|
|
541
|
+
page = await client.publish(html, {
|
|
540
542
|
title: opts.title ?? null,
|
|
541
543
|
password: opts.password ?? null,
|
|
542
544
|
expiresIn: opts.expiresIn ? Number.parseInt(opts.expiresIn, 10) : null
|
|
@@ -545,28 +547,28 @@ function registerPublish(program) {
|
|
|
545
547
|
throw new HTMLShipError(`publish failed: ${err.message}`);
|
|
546
548
|
}
|
|
547
549
|
const keys = createKeyStore();
|
|
548
|
-
keys.remember(
|
|
549
|
-
owner_key:
|
|
550
|
-
url:
|
|
550
|
+
keys.remember(page.slug, {
|
|
551
|
+
owner_key: page.owner_key,
|
|
552
|
+
url: page.url,
|
|
551
553
|
title: opts.title ?? null
|
|
552
554
|
});
|
|
553
555
|
if (opts.quiet) {
|
|
554
|
-
process.stdout.write(`${
|
|
556
|
+
process.stdout.write(`${page.url}
|
|
555
557
|
`);
|
|
556
558
|
return;
|
|
557
559
|
}
|
|
558
|
-
process.stdout.write(`${
|
|
560
|
+
process.stdout.write(`${page.url}
|
|
559
561
|
`);
|
|
560
|
-
process.stderr.write(`slug: ${
|
|
562
|
+
process.stderr.write(`slug: ${page.slug}
|
|
561
563
|
`);
|
|
562
|
-
process.stderr.write(`owner_key: ${
|
|
564
|
+
process.stderr.write(`owner_key: ${page.owner_key} (saved to ${keys.file})
|
|
563
565
|
`);
|
|
564
|
-
if (
|
|
565
|
-
process.stderr.write(`expires: ${
|
|
566
|
+
if (page.expires_at) {
|
|
567
|
+
process.stderr.write(`expires: ${page.expires_at}
|
|
566
568
|
`);
|
|
567
569
|
}
|
|
568
570
|
if (opts.noClipboard !== true) {
|
|
569
|
-
const copied = await tryClipboardCopy(
|
|
571
|
+
const copied = await tryClipboardCopy(page.url);
|
|
570
572
|
if (copied) process.stderr.write("(URL copied to clipboard)\n");
|
|
571
573
|
}
|
|
572
574
|
});
|
|
@@ -576,7 +578,7 @@ function registerPublish(program) {
|
|
|
576
578
|
init_client();
|
|
577
579
|
init_errors();
|
|
578
580
|
function registerUpdate(program) {
|
|
579
|
-
program.command("update").description("Replace HTML for an existing
|
|
581
|
+
program.command("update").description("Replace HTML for an existing page.").argument("<slug>", "Page slug").argument("[source]", "file path, '-' for stdin").option("-f, --file <path>", "HTML file path").option("--title <title>", "Optional new title").option("--owner-key <key>", "Owner key (defaults to local store)").action(async function(slug, source, opts) {
|
|
580
582
|
const html = readHtmlFromSource(source, opts.file);
|
|
581
583
|
const keys = createKeyStore();
|
|
582
584
|
const ownerKey = opts.ownerKey ?? keys.lookupOwnerKey(slug);
|
|
@@ -587,8 +589,8 @@ function registerUpdate(program) {
|
|
|
587
589
|
}
|
|
588
590
|
const apiUrl = this.parent?.opts()?.apiUrl;
|
|
589
591
|
const client = new HTMLShipClient({ baseUrl: apiUrl });
|
|
590
|
-
const
|
|
591
|
-
process.stdout.write(`${
|
|
592
|
+
const page = await client.update(slug, html, ownerKey, { title: opts.title ?? null });
|
|
593
|
+
process.stdout.write(`${page.url}
|
|
592
594
|
`);
|
|
593
595
|
});
|
|
594
596
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlship",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Host and share HTML pages from LLMs and coding agents in one line. CLI + MCP server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"html",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"homepage": "https://htmlship.com",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/htmlship/htmlship.git",
|
|
19
|
+
"url": "git+https://github.com/htmlship/htmlship.git",
|
|
20
20
|
"directory": "npm"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"node": ">=18"
|
|
27
27
|
},
|
|
28
28
|
"bin": {
|
|
29
|
-
"htmlship": "
|
|
29
|
+
"htmlship": "dist/cli.js"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|